教程中心MCP Server 开发实战
进阶

MCP Server 开发实战:从零构建 AI Agent 自定义工具链

2026.06.01· 10 个步骤 · 35 分钟阅读· 🔌 MCP

2026 年 MCP 协议已移交 Linux Foundation 开放治理,ClawHub 上 MCP Server 数量突破 5.2 万。无论你使用 Cursor、Claude Code 还是 OpenClaw,MCP Server 都是扩展 Agent 能力的核心接口。本教程从零到生产,带你走完完整开发流程。

为什么你需要自己写 MCP Server?

你可能已经在用 Agent 帮你写代码、查文档、发消息。但开箱即用的 MCP Server 只能覆盖通用场景——数据库查询、网页抓取、文件操作。当你需要 Agent 操作内部系统(CRM、ERP)、调用私有 API、或者执行特定业务逻辑时,就必须自己写 MCP Server。

MCP 的核心理念是:一次开发,多端复用。同一个 MCP Server 可以同时服务于 Claude Code、Cursor、OpenClaw、GPT 等不同 Agent,不需要为每个 Agent 适配不同的接口协议。

Step 1:理解三种原语——选对类型

MCP 协议定义了三种原语(Primitive),理解它们的区别是开发的第一步:

原语用途类比何时使用
Tool(工具)让 Agent 执行操作API POST 请求Agent 需要「做某件事」——发送邮件、创建工单、修改数据库
Resource(资源)让 Agent 读取数据API GET 请求Agent 需要「查某件事」——读取文档、查询报表、获取配置
Prompt(提示)提供预设模板函数默认参数需要引导 Agent 使用特定格式或上下文

开发原则:有副作用用 Tool,仅读取用 Resource。如果你的功能既需要读也需要写(比如「查询订单并修改状态」),拆成两个原语比合成一个更符合 MCP 的设计哲学。

Step 2:项目初始化

使用 TypeScript 和官方 SDK 初始化项目。TypeScript 的静态类型检查对 MCP 开发非常实用——接口定义、参数验证都能在编译时发现问题。

1 创建项目并安装依赖
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node ts-node
2 配置 TypeScript
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Step 3:声明 Server 及其能力

每个 MCP Server 都需要声明自己提供哪些工具、资源和提示,这样 Agent 在连接时就能自动发现可用能力。这就是 MCP 的「可发现性」设计——不像传统 API 需要查阅文档才知道有哪些接口。

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";

// 创建 Server 实例,声明名称和版本
const server = new Server(
  {
    name: "my-custom-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},      // 声明支持 Tool
      resources: {},  // 声明支持 Resource
    },
  }
);

Step 4:定义 Tool——Agent 的「手」

Tool 是 MCP 最常用、也是最有价值的部分。我们用 Zod 做参数校验,确保 Agent 传参不会出错。

4 实现 Tool 列表和调用处理器
// 定义参数 schema(使用 Zod 校验)
const QueryOrderSchema = z.object({
  orderId: z.string().min(1, "订单 ID 不能为空"),
  includeItems: z.boolean().optional().default(false),
});

// 注册 Tool 列表(Agent 启动时调用发现)
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "query_order",
      description: "根据订单 ID 查询订单详情,可选包含商品明细",
      inputSchema: {
        type: "object",
        properties: {
          orderId: { type: "string", description: "订单 ID" },
          includeItems: {
            type: "boolean",
            description: "是否包含商品明细",
            default: false,
          },
        },
        required: ["orderId"],
      },
    },
    {
      name: "update_order_status",
      description: "更新订单状态(发货/完成/取消)",
      inputSchema: {
        type: "object",
        properties: {
          orderId: { type: "string" },
          status: {
            type: "string",
            enum: ["shipped", "completed", "cancelled"],
          },
          trackingNumber: {
            type: "string",
            description: "物流单号(发货时必须)",
          },
        },
        required: ["orderId", "status"],
      },
    },
  ],
}));

// 注册 Tool 调用处理器(Agent 实际调用时触发)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case "query_order": {
        const { orderId, includeItems } = QueryOrderSchema.parse(args);
        // 这里调用实际的业务逻辑(数据库查询、API 调用等)
        const result = await queryOrderFromDB(orderId, includeItems);
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      }
      case "update_order_status": {
        // 类似实现...
        return {
          content: [{ type: "text", text: "订单状态已更新" }],
        };
      }
      default:
        throw new Error(`未知工具: ${name}`);
    }
  } catch (error) {
    // 这里不处理 Zod 验证错误——让框架自动返回校验失败信息
    if (error instanceof z.ZodError) {
      throw error;
    }
    return {
      content: [{ type: "text", text: `错误: ${error.message}` }],
      isError: true,
    };
  }
});

关于参数校验的思考:很多人会问「Agent 传参数又不可能传字符串以外的值,为什么要用 Zod?」我的看法是:Agent 的错误比你想象的更常见。实测中,Agent 调用 Tool 时约 5-8% 的调用会传错参数类型——比如把 number 传成 string。Zod 能自动捕获这些错误并向 Agent 返回清晰的错误信息,Agent 收到后会自动修正参数重试。

Step 5:实现 Resource——Agent 的「眼」

Resource 让 Agent 可以「看到」数据。它使用 URI 方案标识资源,天然支持结构化数据访问。

5 注册 Resource 列表和读取处理器
import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "dashboard://recent-orders",
      name: "最近订单",
      description: "最近 24 小时内的新订单列表",
      mimeType: "application/json",
    },
    {
      uri: "dashboard://daily-summary",
      name: "日汇总",
      description: "当日的订单汇总统计",
      mimeType: "application/json",
    },
  ],
}));

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

  switch (uri) {
    case "dashboard://recent-orders": {
      const orders = await getRecentOrders();
      return {
        contents: [
          {
            uri,
            mimeType: "application/json",
            text: JSON.stringify(orders),
          },
        ],
      };
    }
    case "dashboard://daily-summary": {
      const summary = await getDailySummary();
      return {
        contents: [
          {
            uri,
            mimeType: "application/json",
            text: JSON.stringify(summary),
          },
        ],
      };
    }
    default:
      throw new Error(`未知资源: ${uri}`);
  }
});

Step 6:启动传输——选择适合你的模式

MCP 支持两种传输方式,2026 年起 Streamable HTTP 已被推荐为唯一远程传输方式,stdio 适用于本地开发。

6 配置启动入口
// 方式 A:stdio 传输(推荐本地开发)
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server 已启动(stdio)");
}

// 方式 B:Streamable HTTP 传输(推荐远程部署)
// 需要额外安装 @modelcontextprotocol/sdk/server/http.js
import { HttpServerTransport } from "@modelcontextprotocol/sdk/server/http.js";

async function mainHttp() {
  const transport = new HttpServerTransport({ port: 3001 });
  await server.connect(transport);
  console.error("MCP Server 已启动(HTTP)");
}

main().catch(console.error);

Step 7:调试——最常见的三个问题和解决方案

问题 1:Agent 说「找不到工具」
原因:ListToolsRequestSchema 处理器未正确注册,或 Tool 定义的 name 与调用时不一致。
解决:在 ListToolsRequestSchema 的返回中打印一下工具列表,确认 Agent 端能够发现。

问题 2:Agent 反复调用同一个 Tool 但参数错误
原因:参数 schema 定义不清晰,Agent 不知道该传什么。
解决:给每个参数加上详细的 description 字段,并用 enum 约束可选值范围。

问题 3:Tool 执行成功了但 Agent 说「发生了错误」
原因:返回格式不符合 MCP 规范。Tool 的返回值必须是一个包含 content 数组的对象。
解决:检查返回值结构,确保是 { content: [{ type: "text", text: "..." }] } 格式。

7 本地测试命令
# 编译
npx tsc

# 启动 Server(stdio)
node dist/index.js

# 测试 HTTP 请求(如果是 HTTP 传输)
curl -X POST http://localhost:3001 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# 配合 MCP Inspector 图形化调试(官方推荐)
npx @modelcontextprotocol/inspector node dist/index.js

Step 8:连接到 Agent——配置 Client

开发完成后,你需要让 Agent 知道你的 MCP Server 在哪。以 Cursor 和 OpenClaw 为例:

8 配置示例
// .cursor/mcp.json
{
  "mcpServers": {
    "my-custom-server": {
      "command": "node",
      "args": ["path/to/dist/index.js"]
    }
  }
}

// OpenClaw 配置:在 mcpServers.yaml 中添加
my-custom-server:
  command: node
  args:
    - path/to/dist/index.js

Step 9:安全性考虑——给 MCP Server 上锁

MCP Server 是 Agent 与外部系统的桥梁,同时也是攻击的入口。以下三点必须在开发阶段纳入设计:

  • 输入验证:不要信任 Agent 传来的任何参数。Zod 校验是最低要求,更严格的场景需要额外的业务规则校验
  • 权限隔离:MCP Server 应使用最小权限原则——只拥有完成其功能所需的最小权限。例如,一个只读的 Resource Server 不应该有写入数据库的权限
  • 速率限制:Agent 的调用速度远超人类。某个 Agent 循环调用导致 API 超限是真实发生过的事件。建议在 MCP Server 内实现简单的速率限制

Step 10:从开发到生产——发布 Checklist

在将 MCP Server 部署到生产环境之前,确认以下事项:

检查项说明
✅ 输入校验所有参数使用 Zod schema 校验,超长/恶意输入有保护
✅ 错误处理所有可能抛出的异常都有 catch,返回格式规范的错误消息
✅ 超时机制每个 Tool 设置合理的执行超时,避免 Agent 长时间等待
✅ 日志输出关键操作记录到 stderr(Agent 读取 stdout,日志不影响通信)
✅ 幂等性写操作(如 update/create)是否支持重复调用不产生副作用?
✅ 并发安全Server 是否支持多个 Agent 同时调用?连接数有无限制?

MCP Server 是 AI Agent 时代最值得掌握的基础技能之一。它与具体模型无关——无论底层是 GPT、Claude 还是 Gemini,MCP 都是它们的通用「工具接口」。花一个下午完成一个 MCP Server 原型,能让你对 Agent 生态的理解提升一个层次。