返回资料库 AI Agent 基础

MCP Server 开发实战:从零构建你的第一个工具服务

MCP 生态已有数千个社区服务器,但很多场景需要定制:

来源:modelcontextprotocol.io | MCP TypeScript SDK | MCP Python SDK | 整理时间:2026-04-06


为什么需要自己开发 MCP Server?

MCP 生态已有数千个社区服务器,但很多场景需要定制:

  • 内部 API 的私有接口没有现成服务器
  • 公司内部系统需要自定义权限控制
  • 现有服务器不完全符合业务需求
  • 学习 MCP Server 开发是理解 Agent 工具生态的最佳方式

MCP Server 核心概念

三大原语(Primitives)

原语 方向 说明 类比
Tools 客户端 → 服务器 模型可调用的函数,类似 API endpoint REST API 调用
Resources 服务器 → 客户端 应用提供的上下文数据,类似 GET 请求 静态文件
Prompts 服务器 → 用户 用户可调用的模板,类似快捷指令 Slash Command
Sampling 服务器 → LLM 服务器向 LLM 发送补全请求 回调

传输方式

传输 场景 说明
stdio 本地进程 客户端启动服务器进程,通过 stdin/stdout 通信
Streamable HTTP 远程服务 HTTP POST 发送请求,支持 SSE 流式响应
SSE(旧版) 远程服务 已被 Streamable HTTP 取代

TypeScript 实战

理解 Claude Code 遇到的场景

Claude Code 连接 MCP Server 时:

Claude Code ←stdio→ 你的 MCP Server
                    ↕stdin↕
                    ←stdout←

最小可用示例

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "my-tools",
  version: "1.0.0",
});

// 注册一个 Tool
server.tool(
  "query_database",
  "查询数据库并返回结果",
  { sql: z.string().describe("SQL 查询语句") },
  async ({ sql }) => {
    const results = await executeQuery(sql);
    return {
      content: [{ type: "text", text: JSON.stringify(results) }],
    };
  }
);

// 注册一个 Resource
server.resource(
  "app-config",
  "应用配置信息",
  async () => ({
    contents: [{
      uri: "config://app",
      text: JSON.stringify({ version: "1.0", env: "production" }),
    }],
  });
);

// 注册一个 Prompt 模板
server.prompt(
  "review-code",
  { code: z.string().describe("要审查的代码") },
  async ({ code }) => ({
    messages: [{
      role: "user",
      content: `请审查以下代码的安全性和性能:\\n\\n${code}`,
    }],
  });
);

// 启动
const transport = new StdioServerTransport();
await server.connect(transport);

带参数校验的 Tool

server.tool(
  "create_user",
  "创建新用户",
  {
    name: z.string().min(1).max(50),
    email: z.string().email(),
    role: z.enum(["admin", "user", "viewer"]).default("user"),
  },
  async ({ name, email, role }) => {
    const user = await createUser({ name, email, role });
    return {
      content: [{ type: "text", text: `用户 ${name} 已创建,角色: ${role}` }],
    };
  }
);

Streamable HTTP 传输(远程部署)

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js";

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID(),
});

await server.connect(transport);

// 配合 Express 使用
// app.post('/mcp', expressHandler);

Python 实战(FastMCP)

Python 用 FastMCP 衔库更简洁:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-tools")

@mcp.tool()
def query_database(sql: str) -> str:
    """查询数据库并返回结果"""
    results = execute_query(sql)
    return f"查询结果: {results}"

@mcp.resource("config://app")
def get_config() -> str:
    """获取应用配置"""
    return json.dumps({"version": "1.0", "env": "production"})

@mcp.prompt()
def review_code(code: str) -> str:
    """代码审查提示模板"""
    return f"请审查以下代码的安全性和性能:\\n\\n{code}"

mcp.run()

FastMCP 进阶:结构化输出

from pydantic import BaseModel

class UserResult(BaseModel):
    name: str
    email: str
    role: str
    success: bool

@mcp.tool()
def create_user(name: str, email: str, role: str = "user") -> UserResult:
    """创建新用户,返回结构化结果"""
    user = await create_user(name, email, role)
    return UserResult(
        name=user.name,
        email=user.email,
        role=user.role,
        success=True,
    )

JSON-RPC 协议详解

理解协议细节有助于调试:

请求(Client → Server)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "sql": "SELECT * FROM users LIMIT 10"
    }
  }
}

响应(Server → Client)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "[{\"id\": 1, \"name\": \"Alice\"}]" }
    ]
  }
}

能力协商

连接建立时,客户端和服务器交换各自支持的能力:

Client: 我支持 tools, resources, sampling
Server: 我提供 3 个 tools, 5 个 resources, 2 个 prompts

测试与调试

MCP Inspector

官方提供的调试工具:

# 使用 npx 直接运行
npx @modelcontextprotocol/inspector

# 或在项目中安装
npm install -D @modelcontextprotocol/inspector

Inspector 提供:

  • 可视化查看服务器的能力(Tools/Resources/Prompts)
  • 实时测试工具调用
  • 查看请求/响应日志
  • 测试不同的传输方式

在 Claude Code 中调试

# 在 Claude Code 中连接 MCP Server
# 编辑 .claude/settings.json 或项目 .mcp.json

{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["path/to/server.js"]
    }
  }
}

常见调试技巧

  • 日志输出:stdio 模式下用 console.error 输出调试信息(stdout 被 MCP 协议占用)
  • 错误处理:Tool 函数中 try/catch 捕获错误,返回有意义的错误消息
  • 类型检查:用 Zod Schema 确保参数格式正确
  • Inspector 验证:发布前先用 Inspector 测试所有 Tool 和 Resource

实战案例:构建文档查询 MCP Server

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";

const server = new McpServer({
  name: "doc-query",
  version: "1.0.0",
});

// Tool: 搜索文档
server.tool(
  "search_docs",
  "在文档目录中搜索关键词",
  {
    query: z.string().describe("搜索关键词"),
    directory: z.string().default("./docs").describe("文档目录路径"),
    maxResults: z.number().default(10).describe("最大结果数"),
  },
  async ({ query, directory, maxResults }) => {
    const results = await searchDocuments(directory, query, maxResults);
    return {
      content: [{ type: "text", text: formatResults(results) }],
    };
  }
);

// Tool: 读取文档
server.tool(
  "read_doc",
  "读取指定文档的内容",
  { path: z.string().describe("文档路径") },
  async ({ path: docPath }) => {
    const content = await fs.readFile(docPath, "utf-8");
    return {
      content: [{ type: "text", text: content }],
    };
  }
);

// Resource: 文档列表
server.resource(
  "docs://index",
  "文档索引",
  async () => ({
    contents: [{
      uri: "docs://index",
      text: await buildDocIndex("./docs"),
    }],
  })
);

// Prompt: 文档总结
server.prompt(
  "summarize-doc",
  { topic: z.string().describe("要总结的主题") },
  async ({ topic }) => ({
    messages: [{
      role: "user",
      content: `请总结关于 "${topic}" 的所有文档要点。使用 docs://index 资源获取文档列表,然后用 search_docs 工具搜索相关内容。`,
    }],
  })
);

async function searchDocuments(dir: string, query: string, max: number) {
  // 实现文件搜索逻辑
  const files = await fs.readdir(dir);
  return files.slice(0, max);
}

async function buildDocIndex(dir: string) {
  const files = await fs.readdir(dir);
  return files.join("\\n");
}

function formatResults(results: string[]) {
  return results.map((r, i) => `${i + 1}. ${r}`).join("\\n");
}

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

部署建议

部署方式 适用场景 说明
stdio + 本地 Claude Code、桌面应用 通过进程启动,最快最简单
Streamable HTTP 远程服务、Web 应用 支持 SSE 流式响应,可部署到服务器
Docker 企业部署 容器化部署,便于管理和扩展

企业级部署清单

  • 认证和授权机制
  • 速率限制和配额管理
  • 日志和监控
  • 错误处理和重试
  • API 版本管理

安全最佳实践

风险 缓解措施
恶意工具执行 只使用信任的 MCP 服务器,审查源码
数据泄露 限制服务器可访问的数据范围
API Key 暴露 通过环境变量传递,不硬编码
供应链攻击 锁定依赖版本,审查第三方代码
SQL 注入 Tool 参数用 Zod 校验,参数化查询

相关链接

常见问题

MCP Server 开发实战:从零构建你的第一个工具服务 适合什么读者?

MCP Server 开发实战:从零构建你的第一个工具服务 适合希望系统掌握 AI Agent 基础 的读者,尤其是需要从概念快速过渡到实践的人。页面包含主题摘要、相关阅读和来源链接,便于形成可执行的学习路径。

阅读 MCP Server 开发实战:从零构建你的第一个工具服务 需要多久?

当前页面预估阅读时长约 13 分钟。建议先读正文结论,再根据“同专题延伸”继续阅读,通常 20 到 40 分钟可以建立完整主题框架。

如何把 MCP Server 开发实战:从零构建你的第一个工具服务 的内容用于实际项目?

先按正文中的关键概念完成最小可运行示例,再把示例嵌入你当前项目流程。你可以结合来源链接验证细节,并使用同专题文章补齐部署、协作和评估步骤。