Skills DirectorySearch all skills ↗

AI agent skill · by @Leanmcp

Mcp Builder

Build MCP (Model Context Protocol) servers using the official @modelcontextprotocol/sdk. Use this skill when users ask to create an MCP server, MCP tools, MCP resources, or want to expose APIs/functionality to AI agents. This skill covers vanilla MCP implementation with Streamable HTTP transport, session management, tools, resources, and prompts.

Archived 2026-10-23 · Source updated 2026-10-07 · License: MIT. Check the original repository for the latest version.

SKILL.md instructions

# MCP Builder Skill

This skill guides you in building MCP (Model Context Protocol) servers using the official `@modelcontextprotocol/sdk` package with TypeScript.

## When to Use This Skill

- User asks to "create an MCP server"
- User wants to "expose an API to AI agents"
- User needs "tools for Claude/LLM"
- User mentions "Model Context Protocol"
- User wants to build "AI agent integrations"

## MCP Core Concepts

### What is MCP?

Model Context Protocol (MCP) is an open standard that enables structured, secure interactions between Large Language Models (LLMs) and external systems. MCP acts as a bridge allowing AI agents to:

- Access external data sources safely
- Execute tools and functions
- Maintain consistent communication protocols
- Integrate with APIs and databases

### Key Components

**Tools** - Interactive functions that perform actions or computations:
- Execute operations and computations
- Accept parameters from the LLM
- Return structured responses
- Can change system state (side effects allowed)
- Examples: calculations, database operations, API calls, sending emails

**Resources** - Read-only data endpoints:
- Passive data sources (no side effects)
- Loaded into AI context for reasoning
- URI-based identification
- Examples: documentation, config files, status data, user profiles

**Prompts** - Reusable prompt templates:
- Pre-defined conversation starters
- Can accept arguments for customization
- Return structured messages for the LLM

### Transport Options

| Transport | Best For | Pros | Cons |
|-----------|----------|------|------|
| **Streamable HTTP** | Production, cloud, multiple clients | Scalable, remote access | Requires session management |
| **SSE** | Real-time apps, live updates | Streaming, auto-reconnect | Connection limits |
| **STDIO** | Local desktop, development | Simple, low latency | Single session, local only |

**Recommendation**: Use Streamable HTTP for most production servers.

## Project Structure

```
my-mcp-server/
├── src/
│   ├── server.ts          # Express server with transport setup
│   ├── mcp-server.ts      # MCP server with tools/resources/prompts
│   └── minimal/           # Optional: organized components
│       ├── tool.ts
│       ├── resource.ts
│       └── prompt.ts
├── package.json
├── tsconfig.json
└── .env
```

## Required Dependencies

```json
{
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.0",
    "express": "^4.21.0",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/express": "^4.17.21",
    "@types/cors": "^2.8.17",
    "typescript": "^5.6.3",
    "tsx": "^4.20.3"
  }
}
```

## TypeScript Configuration

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
```

## Implementation Pattern

### 1. MCP Server Setup (mcp-server.ts)

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

export function createMCPServer() {
  const server = new McpServer({
    name: 'my-mcp-server',
    version: '1.0.0',
  });

  // Register tools
  server.tool('toolName', 'Tool description', {
    param1: { type: 'string', description: 'Parameter description' }
  }, async (args) => {
    // Implementation
    return {
      content: [{ type: 'text', text: JSON.stringify(result) }]
    };
  });

  // Register resources
  server.resource('resource://uri', 'Resource description', async () => ({
    contents: [{
      uri: 'resource://uri',
      mimeType: 'application/json',
      text: JSON.stringify(data)
    }]
  }));

  // Register prompts
  server.prompt('promptName', 'Prompt description', async (args) => ({
    messages: [{
      role: 'user',
      content: { type: 'text', text: `Prompt content with ${args.param}` }
    }]
  }));

  return server;
}
```

### 2. HTTP Server with Session Management (server.ts)

```typescript
import express from 'express';
import cors from 'cors';
import { randomUUID } from 'node:crypto';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { createMCPServer } from './mcp-server.js';

const app = express();
const PORT = process.env.PORT || 3001;

// CORS for browser clients
app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'mcp-session-id', 'mcp-protocol-version', 'Authorization'],
  exposedHeaders: ['mcp-session-id'],
  credentials: true,
}));
app.use(express.json());

// Session storage
const transports: Record<string, StreamableHTTPServerTransport> = {};

// MCP endpoint handler
const handleMCPRequest = async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  let transport: StreamableHTTPServerTransport;

  if (sessionId && transports[sessionId]) {
    transport = transports[sessionId];
  } else if (!sessionId && isInitializeRequest(req.body)) {
    transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (newSessionId) => {
        transports[newSessionId] = transport;
      },
    });

    transport.onclose = () => {
      if (transport.sessionId) delete transports[transport.sessionId];
    };

    const server = createMCPServer();
    await server.connect(transport);
  } else {
    res.status(400).json({
      jsonrpc: '2.0',
      error: { code: -32000, message: 'No valid session ID or not an init request' },
      id: null,
    });
    return;
  }

  await transport.handleRequest(req, res, req.body);
};

app.post('/', handleMCPRequest);
app.post('/mcp', handleMCPRequest);

app.get('/health', (req, res) => {
  res.json({ status: 'ok', server: 'my-mcp-server', version: '1.0.0' });
});

app.listen(PORT, () => console.log(`MCP Server running on port ${PORT}`));
```

## Tool Implementation Examples

### Simple Tool

```typescript
server.tool('echo', 'Echo a message back', {
  message: { type: 'string', description: 'Message to echo' }
}, async ({ message }) => ({
  content: [{ type: 'text', text: `Echo: ${message}` }]
}));
```

### Tool with Multiple Parameters

```typescript
server.tool('calculate', 'Perform arithmetic operations', {
  a: { type: 'number', description: 'First number' },
  b: { type: 'number', description: 'Second number' },
  operation: { type: 'string', description: 'Operation: add, subtract, multiply, divide' }
}, async ({ a, b, operation }) => {
  let result: number;
  switch (operation) {
    case 'add': result = a + b; break;
    case 'subtract': result = a - b; break;
    case 'multiply': result = a * b; break;
    case 'divide': 
      if (b === 0) throw new Error('Division by zero');
      result = a / b; 
      break;
    default: throw new Error('Invalid operation');
  }
  return { content: [{ type: 'text', text: JSON.stringify({ result }) }] };
});
```

### External API Tool

```typescript
server.tool('getWeather', 'Get current weather for a city', {
  city: { type: 'string', description: 'City name' }
}, async ({ city }) => {
  const response = await fetch(`https://api.weather.com/v1/current?city=${encodeURIComponent(city)}&key=${process.env.WEATHER_API_KEY}`);
  const data = await response.json();
  return {
    content: [{ type: 'text', text: JSON.stringify({
      city,
      temperature: data.main.temp,
      conditions: data.weather[0].description
    }) }]
  };
});
```

## Resource Implementation Examples

```typescript
// Static resource
server.resource('config://settings', 'Application settings', async () => ({
  contents: [{
    uri: 'config://settings',
    mimeType: 'application/json',
    text: JSON.stringify({ theme: 'dark', language: 'en' })
  }]
}));

// Dynamic resource
server.resource('server://info', 'Server information', async () => ({
  contents: [{
    uri: 'server://info',
    mimeType: 'application/json',
    text: JSON.stringify({
      name: 'my-server',
      version: '1.0.0',
      uptime: process.uptime(),
      timestamp: new Date().toISOString()
    })
  }]
}));
```

## Editing Guidelines

### Core Principles

1. **Edit existing files, don't recreate** - Template files are provided; modify them
2. **Preserve existing structure** - Don't change fundamental architecture
3. **Minimal necessary changes** - Make smallest changes to achieve requirements
4. **Maintain backward compatibility** - Don't break existing functionality

### DO

- Add new tools/resources/prompts to existing service files
- Extend existing functionality
- Follow existing code patterns
- Use TypeScript for type safety

### DON'T

- Completely rewrite template files
- Remove essential boilerplate code
- Create unnecessary new files
- Add terminal/command instructions (user will run commands themselves)

## Running the Server

The user will handle running commands. Your job is to provide the code files only.

Standard commands the user might run:
- `npm install` - Install dependencies
- `npm run dev` or `npx tsx src/server.ts` - Development mode
- `npm run build` - Build for production
- `npm start` - Run production build

Related skills