MCP 协议深入实战:从入门到编写自定义 MCP Server

引言

2024 年底,Anthropic 开源的 Model Context Protocol (MCP) 迅速成为 AI 开发社区的热门话题。它定义了 AI 客户端与大模型之间标准化的工具调用和上下文交换协议,被业界称为"AI 领域的 USB-C 接口"。

截至 2026 年中期,MCP 已经发展到了 v1.2 版本,获得了 OpenAI、Google、JetBrains 等主流厂商的官方支持。VS Code、Cursor、Windsurf、JetBrains IDE 等开发工具都已原生集成 MCP 协议。

本文将带你从零开始构建一个完整的 MCP Server,涵盖协议核心概念、SDK 使用、调试技巧和生产部署的最佳实践。

MCP 协议核心概念

协议架构

MCP 采用客户端-服务端架构,通信基于 JSON-RPC 2.0 协议:

┌─────────────────┐          JSON-RPC 2.0         ┌─────────────────┐
│                 │  ════════════════════════       │                 │
│  AI 客户端        │  ← request/response →         │  MCP Server     │
│  (Cursor/IDE)    │  ← notification/event →        │  (自定义工具)     │
│                 │                                │                 │
└─────────────────┘                                └─────────────────┘

核心概念

概念说明
Tool可被 LLM 调用的函数,类似 Function Calling
Resource可被读取的数据源(文件、API、数据库)
Prompt预定义的可复用提示词模板
Transport传输层,支持 stdio(本地)和 SSE(远程)
Session客户端与服务端之间的连接会话

环境准备

安装 MCP SDK

MCP 官方 SDK 支持 TypeScript 和 Python。我们以 TypeScript 为例:

# 创建项目
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod

# 也可以使用 pnpm
pnpm add @modelcontextprotocol/sdk zod

项目结构

my-mcp-server/
├── src/
│   ├── index.ts          # 入口
│   ├── tools/            # 工具实现
│   │   ├── git.ts
│   │   ├── weather.ts
│   │   └── search.ts
│   └── utils.ts          # 工具函数
├── package.json
├── tsconfig.json
└── README.md

编写第一个 MCP Server

1. 基础骨架

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

// 创建 MCP Server 实例
const server = new McpServer({
  name: "my-tools",
  version: "1.0.0",
  description: "我的第一个 MCP 工具服务",
});

// 启动服务
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server 已启动");
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

2. 注册工具(Tool)

Tool 是最核心的功能。让我们注册一个代码审查工具:

server.tool(
  "code-review",
  "对指定代码进行自动化代码审查",
  {
    code: z.string().describe("待审查的代码"),
    language: z.string().describe("编程语言,如 typescript, python, rust"),
    severity: z.enum(["all", "error", "warning"]).default("all").describe("审查级别"),
  },
  async ({ code, language, severity }) => {
    // 模拟代码审查逻辑
    const issues = [];

    if (code.includes("var ")) {
      issues.push({ line: "N/A", type: "warning", message: `使用 let/const 替代 var` });
    }
    if (code.length > 500) {
      issues.push({ line: "N/A", type: "warning", message: "函数过长,建议拆分为多个小函数" });
    }
    if (code.includes("console.log")) {
      issues.push({ line: "N/A", type: "info", message: "生产环境建议移除 console.log" });
    }
    if (code.includes("any") && language === "typescript") {
      issues.push({ line: "N/A", type: "error", message: "避免使用 any 类型,改用具体类型" });
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            language,
            totalIssues: issues.length,
            issues: severity === "all" ? issues : issues.filter(i => i.type === severity),
            score: Math.max(0, 100 - issues.length * 15),
          }, null, 2),
        },
      ],
    };
  }
);

3. 添加 Resource(资源)

Resource 让 LLM 可以直接读取数据,类似可读的文件系统:

server.resource(
  "project-config",
  "project://config",
  {
    description: "项目配置文件",
    mimeType: "application/json",
  },
  async (uri) => ({
    contents: [
      {
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify({
          name: "my-project",
          version: "2.0.0",
          engines: { node: ">=18" },
          scripts: {
            dev: "vite",
            build: "tsc && vite build",
            test: "vitest",
          },
        }, null, 2),
      },
    ],
  })
);

4. 注册 Prompt 模板

Prompt 可以让用户一键生成高质量的上下文:

server.prompt(
  "commit-message",
  "生成符合 Conventional Commits 规范的提交信息",
  {
    type: z.enum(["feat", "fix", "chore", "docs", "refactor", "test"]).describe("提交类型"),
    scope: z.string().optional().describe("影响范围"),
    description: z.string().describe("变更描述"),
  },
  ({ type, scope, description }) => ({
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: `生成一个 Conventional Commits 提交信息:\n类型: ${type}\n范围: ${scope || "无"}\n描述: ${description}\n\n格式: (): `,
        },
      },
    ],
  })
);

本地调试与测试

方案一:MCP Inspector(官方调试工具)

# 全局安装 MCP Inspector
npx @modelcontextprotocol/inspector \
  node dist/index.js

# 或指定 Node 参数
npx @modelcontextprotocol/inspector \
  --env NODE_OPTIONS="--loader ts-node/esm" \
  node src/index.ts

Inspector 提供 Web 界面,可以:

  • 列出所有注册的 Tool / Resource / Prompt
  • 手动调用工具并查看返回结果
  • 查看 JSON-RPC 通信日志
  • 模拟客户端连接行为

方案二:使用 curl 测试(自建 HTTP 服务)

如果使用 SSE Transport,可以直接用 curl 测试:

# 发送工具调用请求
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "code-review",
      "arguments": {
        "code": "var x = 1",
        "language": "typescript"
      }
    }
  }'

集成到 AI 客户端

Cursor 配置

// .cursor/mcp.json
{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["dist/index.js"],
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}

VS Code 配置

// .vscode/mcp.json
{
  "servers": {
    "my-tools": {
      "type": "stdio",
      "command": "node",
      "args": ["dist/index.js"]
    }
  }
}

Claude Desktop 配置

// claude_desktop_config.json
{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["-e", "require('./dist/index.js')"],
      "env": {}
    }
  }
}

高级模式:SSE Transport

本地开发常用 stdio,但生产环境需要远程调用。SSE(Server-Sent Events)Transport 支持远程连接:

import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

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

// SSE 端点
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

// 消息端点
app.post("/messages", async (req, res) => {
  // 处理传入的 JSON-RPC 消息
  res.json({ ok: true });
});

app.listen(3000);
console.log("MCP Server (SSE) running on port 3000");

生产部署最佳实践

1. 日志与监控

  • 所有 stderr 输出自动被客户端捕获,利用这个特性做日志
  • 推荐使用 pino 等结构化日志库
  • 监控调用次数、延迟、错误率

2. 输入验证

  • 使用 Zod 进行严格的输入校验
  • 不要信任 LLM 传入的参数
  • 设置合理的超时和限流

3. 错误处理

server.tool(
  "fetch-url",
  "获取 URL 内容",
  { url: z.string().url() },
  async ({ url }) => {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const text = await response.text();
      return { content: [{ type: "text", text }] };
    } catch (error) {
      return {
        isError: true,
        content: [{ type: "text", text: `错误: ${error.message}` }],
      };
    }
  }
);

4. 安全审计

  • 最小权限原则:只暴露必要的能力
  • 对文件系统操作添加路径白名单
  • 对网络请求限制 IP 和域名范围
  • 敏感操作添加二次确认(通过 Prompt)
const ALLOWED_PATHS = ["/home/user/projects", "/tmp"];
const ALLOWED_DOMAINS = ["api.github.com", "registry.npmjs.org"];

server.tool(
  "read-file",
  "安全的文件读取",
  {
    path: z.string().refine(p => ALLOWED_PATHS.some(a => p.startsWith(a)), {
      message: "路径不在白名单中",
    }),
  },
  async ({ path }) => {
    // 安全读取
  }
);

实战案例:Git 工作流工具

让我们实现一个实用的 Git MCP Server:

import { execSync } from "child_process";

server.tool(
  "git-log",
  "查看最近 Git 提交记录",
  {
    count: z.number().min(1).max(50).default(10),
    branch: z.string().optional(),
  },
  async ({ count, branch }) => {
    const branchOpt = branch ? ` ${branch}` : "";
    const output = execSync(`git log --oneline --max-count=${count}${branchOpt}`, {
      encoding: "utf-8",
    });
    return {
      content: [{ type: "text", text: output }],
    };
  }
);

server.tool(
  "git-diff",
  "查看文件变更差异",
  {
    file: z.string().optional(),
    staged: z.boolean().default(false),
  },
  async ({ file, staged }) => {
    const stagedOpt = staged ? "--staged" : "";
    const fileOpt = file ? ` -- "${file}"` : "";
    const output = execSync(`git diff ${stagedOpt}${fileOpt}`, {
      encoding: "utf-8",
    });
    return {
      content: [{ type: "text", text: output }],
    };
  }
);

常见问题与调试

问题原因解决
Tool 返回空结果 参数校验失败导致异常被吞 检查 Zod schema 是否正确
Client 连接后无响应 Transport 未正确初始化 确认走 stdio 还是 SSE,检查端口占用
Tool 调用超时 异步操作时间过长 设置合理超时,或者将耗时操作拆分为多步
类型错误 参数类型与 Zod 定义不匹配 LLM 可能生成错误类型,用 zod 做兜底解析
SSE 连接断开 HTTP 连接超时或代理问题 开启 keep-alive,审查反向代理配置

总结

MCP 协议正在成为 AI 工具调用的标准协议,掌握 MCP Server 开发能力意味着能够:

  • 将任意业务逻辑暴露给 AI 客户端调用
  • 构建自定义的代码分析、CI/CD、运维工具链
  • 将私有数据源安全地接入 AI 工作流

随着更多厂商接入 MCP 协议,它的生态系统会越来越丰富。现在开始编写自己的 MCP Server,就是为 AI 时代的工具生态做好准备。

相关阅读: