Claude Code Plugins

Community-maintained marketplace

Feedback

mcp-server-development

@majiayu000/claude-arsenal
0
0

MCP (Model Context Protocol) Server development guide. Use when building Claude Code extensions. Covers Tools, Resources, Prompts capabilities.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name mcp-server-development
description MCP (Model Context Protocol) Server development guide. Use when building Claude Code extensions. Covers Tools, Resources, Prompts capabilities.

MCP Server Development

Core Principles

  • Single Responsibility — Each server focuses on one domain
  • Minimal Permissions — Request only necessary access
  • Input Validation — Strictly validate all user input
  • Error Handling — Return meaningful error messages
  • Idempotent Design — Tools should be idempotent when possible
  • Clear Documentation — Every Tool/Resource has a clear description

MCP Architecture

┌─────────────────────────────────────────────────┐
│                   MCP Host                       │
│            (Claude Desktop / IDE)                │
└─────────────────────────────────────────────────┘
                        │
                   JSON-RPC 2.0
                   (stdio / HTTP)
                        │
        ┌───────────────┼───────────────┐
        ▼               ▼               ▼
   ┌─────────┐    ┌─────────┐    ┌─────────┐
   │ Server A│    │ Server B│    │ Server C│
   │ (Files) │    │ (GitHub)│    │  (DB)   │
   └─────────┘    └─────────┘    └─────────┘

Three Capabilities

Type Description Example
Tools Functions LLM can call Execute commands, API calls
Resources Data sources to read File contents, DB records
Prompts Pre-defined templates Code review, doc generation

Quick Start

1. Initialize Project

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

2. Project Structure

my-mcp-server/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts          # Entry point
│   ├── tools/            # Tool definitions
│   │   └── search.ts
│   ├── resources/        # Resource definitions
│   │   └── files.ts
│   └── prompts/          # Prompt templates
│       └── review.ts
└── tests/
    └── server.test.ts

3. Basic Server

#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';

const server = new Server(
  { name: 'my-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'hello',
      description: 'Say hello to someone',
      inputSchema: {
        type: 'object',
        properties: {
          name: { type: 'string', description: 'Name to greet' },
        },
        required: ['name'],
      },
    },
  ],
}));

// Handle tool calls
const HelloInput = z.object({ name: z.string().min(1) });

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'hello') {
    const { name } = HelloInput.parse(request.params.arguments);
    return {
      content: [{ type: 'text', text: `Hello, ${name}!` }],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

// Start server
const transport = new StdioServerTransport();
server.connect(transport);

Tools

Definition

{
  name: 'search_files',
  description: 'Search for files matching a glob pattern',
  inputSchema: {
    type: 'object',
    properties: {
      pattern: {
        type: 'string',
        description: 'Glob pattern (e.g., "**/*.ts")',
      },
      directory: {
        type: 'string',
        description: 'Directory to search in',
        default: '.',
      },
    },
    required: ['pattern'],
  },
}

Input Validation

import { z } from 'zod';

const SearchFilesInput = z.object({
  pattern: z.string().min(1).max(500),
  directory: z.string().default('.')
    .refine(d => !d.includes('..'), 'Path traversal not allowed'),
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'search_files': {
      const input = SearchFilesInput.parse(args);
      const results = await glob(input.pattern, { cwd: input.directory });
      return {
        content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
      };
    }
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

Response Types

// Text response
return {
  content: [{ type: 'text', text: 'Result here' }],
};

// Multiple content blocks
return {
  content: [
    { type: 'text', text: 'File contents:' },
    { type: 'text', text: fileContent },
  ],
};

// Image (base64)
return {
  content: [{
    type: 'image',
    data: base64EncodedImage,
    mimeType: 'image/png',
  }],
};

// Error (use isError flag)
return {
  content: [{ type: 'text', text: 'Error: File not found' }],
  isError: true,
};

Resources

Definition

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: 'file:///config.json',
      name: 'Configuration',
      description: 'Application configuration file',
      mimeType: 'application/json',
    },
    {
      uri: 'db://users',
      name: 'Users Table',
      description: 'Database users table',
      mimeType: 'application/json',
    },
  ],
}));

Reading Resources

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  if (uri === 'file:///config.json') {
    const content = await fs.readFile('config.json', 'utf-8');
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: content,
      }],
    };
  }

  if (uri.startsWith('db://')) {
    const table = uri.replace('db://', '');
    const data = await db.query(`SELECT * FROM ${table} LIMIT 100`);
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: JSON.stringify(data, null, 2),
      }],
    };
  }

  throw new Error(`Unknown resource: ${uri}`);
});

Resource Templates

server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
  resourceTemplates: [
    {
      uriTemplate: 'file:///{path}',
      name: 'File',
      description: 'Read any file by path',
      mimeType: 'text/plain',
    },
  ],
}));

Prompts

Definition

server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  prompts: [
    {
      name: 'code_review',
      description: 'Generate a code review for the given file',
      arguments: [
        {
          name: 'file_path',
          description: 'Path to the file to review',
          required: true,
        },
        {
          name: 'focus',
          description: 'Areas to focus on (security, performance, etc.)',
          required: false,
        },
      ],
    },
  ],
}));

Getting Prompts

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'code_review') {
    const filePath = args?.file_path as string;
    const focus = args?.focus as string || 'general';
    const code = await fs.readFile(filePath, 'utf-8');

    return {
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Please review the following code with a focus on ${focus}:

\`\`\`
${code}
\`\`\`

Provide feedback on:
1. Code quality
2. Potential bugs
3. Suggestions for improvement`,
          },
        },
      ],
    };
  }

  throw new Error(`Unknown prompt: ${name}`);
});

Security

Path Traversal Prevention

import path from 'path';

const ALLOWED_DIRS = ['/workspace', '/tmp'];

function validatePath(userPath: string): string {
  // Resolve to absolute path
  const resolved = path.resolve(userPath);

  // Check against whitelist
  const allowed = ALLOWED_DIRS.some(dir => resolved.startsWith(dir));
  if (!allowed) {
    throw new Error('Access denied: path outside allowed directories');
  }

  // Check for traversal attempts
  if (userPath.includes('..')) {
    throw new Error('Path traversal not allowed');
  }

  return resolved;
}

Input Sanitization

const SafeInput = z.object({
  query: z.string()
    .max(1000)
    .refine(s => !s.includes('DROP'), 'Invalid query'),

  filename: z.string()
    .regex(/^[\w\-\.]+$/, 'Invalid filename'),

  command: z.string()
    .refine(s => !['rm', 'sudo', 'chmod'].some(c => s.includes(c)),
      'Dangerous command not allowed'),
});

Secrets Handling

// Never expose secrets in responses
function sanitizeOutput(data: Record<string, unknown>): Record<string, unknown> {
  const sensitiveKeys = ['password', 'apiKey', 'secret', 'token', 'credentials'];

  return Object.fromEntries(
    Object.entries(data).filter(([key]) =>
      !sensitiveKeys.some(s => key.toLowerCase().includes(s))
    )
  );
}

Testing

MCP Inspector

# Install and run
npx @modelcontextprotocol/inspector node dist/index.js

Unit Tests

import { describe, it, expect, beforeAll } from 'vitest';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

describe('MCP Server', () => {
  let client: Client;

  beforeAll(async () => {
    // Setup test client connected to server
    client = await createTestClient();
  });

  it('lists tools', async () => {
    const { tools } = await client.listTools();
    expect(tools).toContainEqual(
      expect.objectContaining({ name: 'search_files' })
    );
  });

  it('executes search_files tool', async () => {
    const result = await client.callTool('search_files', {
      pattern: '*.ts',
      directory: 'src',
    });
    expect(result.content[0].text).toContain('index.ts');
  });

  it('rejects path traversal', async () => {
    await expect(
      client.callTool('search_files', {
        pattern: '*.ts',
        directory: '../../../etc',
      })
    ).rejects.toThrow('Path traversal not allowed');
  });
});

Deployment

Claude Desktop Config

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server"],
      "env": {
        "API_KEY": "your-key"
      }
    }
  }
}

NPM Publishing

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "my-mcp-server": "dist/index.js"
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  }
}
npm publish

Checklist

## Implementation
- [ ] Tools have clear descriptions
- [ ] Input validated with Zod
- [ ] Path traversal protection
- [ ] Meaningful error messages
- [ ] No sensitive data in responses

## Testing
- [ ] MCP Inspector tested
- [ ] Unit tests for tools
- [ ] Security tests (path traversal, etc.)

## Documentation
- [ ] README with usage examples
- [ ] Tool parameters documented
- [ ] Installation instructions

## Deployment
- [ ] package.json bin configured
- [ ] TypeScript compiled
- [ ] NPM published (optional)

See Also