跳转至

MCP Authoring Guide

重要性:⭐⭐ 目标读者:MCP server 作者 关联MCP_PROTOCOL.mdtopics/deep-dive-mcp-client.mdtutorials/build-mcp-server.md


1. 概览

写一个生产级 MCP server 的指南。

5 大主题: - 设计 - 实现 - 测试 - 发布 - 维护


2. 5 个设计原则

2.1 单一职责

✅ 1 个 server = 1 个功能域
❌ 1 个 server 干所有事

单一

2.2 描述清晰

// ✅ 描述让 LLM 知道何时调
{
  name: 'get_weather',
  description: 'Get the current weather for a city. Returns temperature and conditions.',
  // 不是:'weather tool'
}

清晰

2.3 输入严格

// ✅ 用 zod
const schema = z.object({
  city: z.string().min(1).max(100),
  units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
})

严格

2.4 错误友好

// ✅ throw with hint
throw new Error('City not found. Try a major city like "Tokyo" or "London".')

友好

2.5 超时控制

// ✅ 30s 默认
const TIMEOUT = 30_000
const timeoutPromise = new Promise((_, reject) => 
  setTimeout(() => reject(new Error('Timeout')), TIMEOUT)
)
const result = await Promise.race([fetchData(), timeoutPromise])

超时


3. 5 步实现

3.1 Step 1: 项目初始化

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

npm

3.2 Step 2: tsconfig

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist"
  }
}

TS

3.3 Step 3: server 实现

// src/index.ts
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', version: '1.0.0' },
  { capabilities: { tools: {} } }
)

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'add',
      description: 'Add two numbers and return the sum',
      inputSchema: {
        type: 'object',
        properties: {
          a: { type: 'number', description: 'First number' },
          b: { type: 'number', description: 'Second number' },
        },
        required: ['a', 'b'],
      },
    },
  ],
}))

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
  if (name === 'add') {
    const { a, b } = z.object({
      a: z.number(),
      b: z.number(),
    }).parse(args)
    return {
      content: [{ type: 'text', text: String(a + b) }],
    }
  }
  throw new Error(`Unknown tool: ${name}`)
})

const transport = new StdioServerTransport()
await server.connect(transport)

完整

3.4 Step 4: package.json

{
  "name": "@my-org/my-mcp",
  "version": "1.0.0",
  "bin": { "my-mcp": "dist/index.js" },
  "scripts": { "build": "tsc" },
  "files": ["dist"]
}

npm

3.5 Step 5: build & test

npm run build
chmod +x dist/index.js
# 接到 Claude Code
claude mcp add --transport stdio -- npx @my-org/my-mcp

build + test


4. 5 个核心 API

4.1 ListToolsRequestSchema

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    { name, description, inputSchema }
  ],
}))

list tools

4.2 CallToolRequestSchema

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
  // 处理
  return { content: [...] }
})

call tool

4.3 ListResourcesRequestSchema

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    { uri, name, mimeType }
  ],
}))

list resources

4.4 ReadResourceRequestSchema

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params
  // 读
  return { contents: [{ uri, mimeType, text }] }
})

read resource

4.5 ListPromptsRequestSchema

server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  prompts: [
    { name, description, arguments }
  ],
}))

list prompts


5. 4 种 transport 实现

5.1 stdio(最常见)

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const transport = new StdioServerTransport()
await server.connect(transport)

stdio

5.2 SSE

import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
const transport = new SSEServerTransport('/endpoint', response)

SSE

5.3 Streamable HTTP

import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'

Streamable

5.4 WebSocket

// 自实现
const ws = new WebSocketServer({ port: 8080 })
ws.on('connection', (socket) => { /* ... */ })

WS


6. 5 个 OAuth 步骤

6.1 Step 1: 暴露 OAuth endpoint

app.get('/.well-known/oauth-authorization-server', ...)
app.post('/oauth/register', ...)  // Dynamic client registration
app.get('/oauth/authorize', ...)
app.post('/oauth/token', ...)

endpoint

6.2 Step 2: Dynamic Client Registration

// client 不存在时
// server 自动注册

DCR

6.3 Step 3: Authorize

// 重定向到 OAuth provider

authorize

6.4 Step 4: Token

// 用 code 换 access_token

token

6.5 Step 5: Refresh

// 401 → refresh

refresh


7. 5 种 Tool 设计

7.1 文件读取

{
  name: 'read_file',
  description: 'Read a file and return its contents',
  inputSchema: {
    type: 'object',
    properties: {
      path: { type: 'string' },
      encoding: { type: 'string', enum: ['utf-8', 'base64'] }
    },
    required: ['path']
  }
}

read

7.2 数据查询

{
  name: 'query_database',
  description: 'Run a SQL query against the database',
  inputSchema: {
    type: 'object',
    properties: {
      sql: { type: 'string' },
      params: { type: 'array' }
    },
    required: ['sql']
  }
}

query

7.3 API 调用

{
  name: 'fetch_url',
  description: 'Fetch a URL and return its content',
  inputSchema: {
    type: 'object',
    properties: { url: { type: 'string' } },
    required: ['url']
  }
}

API

7.4 计算

{
  name: 'calculate',
  description: 'Perform a calculation',
  inputSchema: {
    type: 'object',
    properties: {
      expression: { type: 'string' }
    },
    required: ['expression']
  }
}

compute

7.5 写操作

{
  name: 'create_issue',
  description: 'Create a GitHub issue',
  inputSchema: {
    type: 'object',
    properties: {
      repo: { type: 'string' },
      title: { type: 'string' },
      body: { type: 'string' }
    },
    required: ['repo', 'title']
  }
}

write


8. 3 种 Resource 设计

8.1 文件型

{ uri: 'file:///path/to/file', mimeType: 'text/plain' }

file

8.2 API 型

{ uri: 'https://api.example.com/data', mimeType: 'application/json' }

API

8.3 动态型

{ uri: 'db://table/users', mimeType: 'application/json' }

dynamic


9. 5 个测试技巧

9.1 单元测试

import { describe, it, expect } from 'vitest'

describe('add tool', () => {
  it('returns sum', async () => {
    const result = await handleCallTool('add', { a: 1, b: 2 })
    expect(result.content[0].text).toBe('3')
  })
})

unit

9.2 集成测试

// 用 mcp-cli 测
npx @modelcontextprotocol/inspector /path/to/server.js

integration

9.3 真实 client

// 用 Claude Code 测
claude mcp add --transport stdio -- node /path/to/server.js
> Use my tool

real

9.4 错误路径

// 测错误
await handleCallTool('add', {})  // 缺参数

error

9.5 性能

// 测超时
const start = Date.now()
await handleCallTool('slow_tool', {})
const elapsed = Date.now() - start
expect(elapsed).toBeLessThan(30000)

perf


10. 5 个发布步骤

10.1 Step 1: 测试

npm test
claude mcp add --transport stdio -- npx @my-org/my-mcp
# 实际用

test

10.2 Step 2: 文档

# README
- 描述
- 安装
- 配置
- 示例

docs

10.3 Step 3: 打包

npm run build
npm pack

build

10.4 Step 4: npm publish

npm login
npm publish --access public

publish

10.5 Step 5: 注册市场

# 提交到 https://github.com/modelcontextprotocol/servers
# PR

marketplace


11. 5 个维护任务

11.1 监控

// 推测:logEvent
logEvent('tengu_mcp_called', { tool: 'add' })

monitor

11.2 错误追踪

catch (e) {
  logError(e)
  throw new McpError('Tool execution failed', e)
}

track

11.3 性能

// 测 token / 时间

perf

11.4 升级 SDK

npm update @modelcontextprotocol/sdk
# 测兼容性

upgrade

11.5 用户支持

# GitHub issues

support


12. 5 个常见错误

12.1 没描述

// ❌
{ name: 'add', inputSchema: {...} }
// 没有 description → Claude 不知道何时调

无 description

12.2 描述太长

// ❌
description: 'A very long description that explains everything in detail...'
// LLM 困惑

过长

12.3 错误信息无 hint

// ❌
throw new Error('Invalid input')
// ✅
throw new Error('City not found. Provide a real city name.')

无 hint

12.4 长操作无进度

// ❌ 30s 静默
await longOperation()
// ✅
sendProgress(0, 100)
await step1()
sendProgress(50, 100)
await step2()

无 progress

12.5 资源泄漏

// ❌ 文件句柄未关
const file = await openFile(path)
return await file.read()
// 句柄泄漏

leak


13. 完整示例:GitHub MCP Server

import { Octokit } from '@octokit/rest'
import { z } from 'zod'

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

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

  if (name === 'create_issue') {
    const { repo, title, body } = z.object({
      repo: z.string().regex(/^[\w-]+\/[\w-]+$/),
      title: z.string().min(1).max(256),
      body: z.string().optional(),
    }).parse(args)

    const [owner, repoName] = repo.split('/')
    const issue = await octokit.issues.create({
      owner, repo: repoName, title, body: body || '',
    })

    return {
      content: [{
        type: 'text',
        text: `Issue created: ${issue.data.html_url}`,
      }],
    }
  }
  // ... 其他工具
})

完整


14. 总结

MCP Authoring = 设计 + 实现 + 测试 + 发布 + 维护

核心: - 5 设计原则 - 5 实现步骤 - 5 核心 API - 4 transport - 5 OAuth 步骤 - 5 测试技巧 - 5 发布步骤

下一步: - 看 MCP_PROTOCOL.md - 看 deep-dive-mcp-client.md - 看 tutorials/build-mcp-server.md - 写第一个 server