如何搭建自己的 MCP Server(完整教程)

从零开始,30 分钟实现一个可用于生产的自定义 MCP Server

返回教程列表
高级30 分钟

如何搭建自己的 MCP Server(完整教程)

从零开始,30 分钟实现一个可用于生产的自定义 MCP Server

市面上有 500+ 现成 MCP Server,但总有一天你会遇到找不到现成 Server 的场景——这时候就需要自己写一个。本教程从零开始,用 TypeScript 实现一个能查询内部 API 的 MCP Server,包含工具注册、错误处理和部署到生产环境的完整步骤。

MCP Server开发教程TypeScript自定义工具生产部署

如何搭建自己的 MCP Server(完整教程)

前置条件:Node.js 18+,有基础的 TypeScript/JavaScript 知识


为什么需要自定义 MCP Server?

现有的 500+ MCP Server 覆盖了大多数通用场景,但你可能需要:

  • 查询公司内部 API(如 ERP、CRM 系统)
  • 封装私有数据库的访问逻辑
  • 集成不在公开列表里的 SaaS 工具
  • 为特定业务流程定制专属工具
  • 这些场景都需要自己写 MCP Server。好消息是:MCP SDK 设计得非常简洁,写一个基础 Server 只需要 ~50 行代码。


    MCP Server 的核心概念

    MCP Server 可以暴露三种类型的能力:

    类型说明示例

    Tools(工具)AI 可以调用的函数搜索、查询、执行操作 Resources(资源)AI 可以读取的数据文件、数据库记录、API 响应 Prompts(提示词)预定义的提示词模板代码审查模板、报告格式

    本教程专注于最常用的 Tools


    第一步:初始化项目

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

    创建 tsconfig.json

    json
    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./dist",
        "strict": true
      }
    }
    

    更新 package.json

    json
    {
      "type": "module",
      "bin": { "my-mcp-server": "./dist/index.js" },
      "scripts": {
        "build": "tsc",
        "dev": "tsx src/index.ts"
      }
    }
    


    第二步:实现最小化 MCP Server

    创建 src/index.ts

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

    // 1. 创建 Server 实例 const server = new McpServer({ name: 'my-mcp-server', version: '1.0.0' });

    // 2. 注册工具 server.tool( 'get_weather', // 工具名(AI 会用这个名字调用) '获取指定城市的实时天气', // 工具描述(AI 用来判断何时调用) { city: z.string().describe('城市名称,如"北京"、"上海"') }, async ({ city }) => { // 这里调用你的实际 API const res = await fetch(https://api.weather.example.com/v1/current?city=${city}); const data = await res.json(); return { content: [{ type: 'text', text: ${city}当前天气:${data.condition},气温 ${data.temp}°C,湿度 ${data.humidity}% }] }; } );

    // 3. 启动 Server const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP Server 已启动'); // 注意:日志要用 stderr,stdout 留给协议通信


    第三步:添加多个工具(实战示例)

    以下是一个查询公司内部数据的实战示例:

    typescript
    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: 'company-internal-api', version: '1.0.0', description: '公司内部 API 集成 MCP Server' });

    const API_BASE = process.env.INTERNAL_API_URL ?? 'https://api.company.internal'; const API_TOKEN = process.env.INTERNAL_API_TOKEN ?? '';

    // 辅助函数:带认证的 fetch async function apiFetch(path: string, options?: RequestInit) { const res = await fetch(${API_BASE}${path}, { ...options, headers: { 'Authorization': Bearer ${API_TOKEN}, 'Content-Type': 'application/json', ...options?.headers } }); if (!res.ok) throw new Error(API 错误 ${res.status}: ${await res.text()}); return res.json(); }

    // 工具 1:查询客户信息 server.tool( 'get_customer', '根据客户 ID 或姓名查询客户详细信息', { query: z.string().describe('客户 ID(如 C001)或客户姓名'), include_orders: z.boolean().optional().default(false).describe('是否包含最近订单') }, async ({ query, include_orders }) => { const customer = await apiFetch(/customers/search?q=${encodeURIComponent(query)}); let result = `客户信息:

  • 姓名:${customer.name}
  • 公司:${customer.company}
  • 联系方式:${customer.email} / ${customer.phone}
  • 客户等级:${customer.tier}
  • 注册时间:${customer.created_at}`;
  • if (include_orders && customer.id) { const orders = await apiFetch(/customers/${customer.id}/orders?limit=5); result += `

    最近 5 笔订单: ${orders.map((o: any) => - #${o.id} ${o.created_at}: ${o.amount} 元(${o.status}) ).join(' ')}`; }

    return { content: [{ type: 'text', text: result }] }; } );

    // 工具 2:查询库存 server.tool( 'check_inventory', '查询指定商品的库存数量和预计到货时间', { sku: z.string().describe('商品 SKU 编号'), warehouse: z.enum(['beijing', 'shanghai', 'guangzhou', 'all']).default('all').describe('查询的仓库') }, async ({ sku, warehouse }) => { const inventory = await apiFetch(/inventory/${sku}?warehouse=${warehouse}); const lines = warehouse === 'all' ? inventory.warehouses.map((w: any) => - ${w.name}:${w.stock} 件) : [- 当前库存:${inventory.stock} 件]; return { content: [{ type: 'text', text: `商品 ${sku} 库存情况: ${lines.join(' ')} 总库存:${inventory.total} 件 预计补货日期:${inventory.restock_date ?? '暂无计划'}` }] }; } );

    // 工具 3:创建工单 server.tool( 'create_ticket', '创建客服工单,适合需要人工跟进的问题', { customer_id: z.string().describe('客户 ID'), category: z.enum(['refund', 'delivery', 'product', 'other']).describe('工单类型'), description: z.string().min(10).describe('问题描述(至少 10 个字)'), priority: z.enum(['low', 'normal', 'high', 'urgent']).default('normal') }, async ({ customer_id, category, description, priority }) => { const ticket = await apiFetch('/tickets', { method: 'POST', body: JSON.stringify({ customer_id, category, description, priority }) }); return { content: [{ type: 'text', text: `✅ 工单创建成功!

  • 工单号:${ticket.id}
  • 优先级:${priority}
  • 预计响应时间:${ticket.sla_deadline}
  • 负责人:${ticket.assigned_to ?? '待分配'}`
  • }] }; } );

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


    第四步:错误处理最佳实践

    typescript
    // ✅ 好的错误处理:给 AI 友好的错误信息
    server.tool('get_customer', '...', { query: z.string() }, async ({ query }) => {
      try {
        const data = await apiFetch(/customers/search?q=${query});
        if (!data || data.length === 0) {
          return {
            content: [{ type: 'text', text: 未找到匹配"${query}"的客户。请检查拼写或尝试其他关键词。 }],
            isError: true
          };
        }
        return { content: [{ type: 'text', text: formatCustomer(data[0]) }] };
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return {
          content: [{ type: 'text', text: 查询失败:${message}。请稍后重试。 }],
          isError: true  // 标记为错误,AI 会知道这次调用失败了
        };
      }
    });
    


    第五步:在 Claude Desktop 中测试

  • 构建项目:npm run build
  • 添加到 Claude Desktop 配置(~/Library/Application Support/Claude/claude_desktop_config.json):
  • json
    {
      "mcpServers": {
        "company-api": {
          "command": "node",
          "args": ["/绝对路径/my-mcp-server/dist/index.js"],
          "env": {
            "INTERNAL_API_URL": "https://api.company.internal",
            "INTERNAL_API_TOKEN": "your-token-here"
          }
        }
      }
    }
    

  • 重启 Claude Desktop
  • 测试:
  • - "帮我查一下客户张三的信息" - "检查 SKU-12345 在上海仓库的库存" - "为客户 C001 创建一个关于退款的高优先级工单"


    第六步:发布为 npm 包(可选)

    如果你想让团队成员也能用:

    bash
    

    修改 package.json

    { "name": "@your-org/mcp-server-company", "version": "1.0.0", "bin": { "mcp-server-company": "./dist/index.js" } }

    npm publish --access restricted # 发布到私有 npm Registry

    团队成员配置:

    json
    {
      "mcpServers": {
        "company-api": {
          "command": "npx",
          "args": ["@your-org/mcp-server-company"],
          "env": { "INTERNAL_API_TOKEN": "..." }
        }
      }
    }
    


    生产部署注意事项

    事项建议

    密钥管理使用环境变量,绝不硬编码在代码里 权限最小化只开放 AI 真正需要的接口 日志记录console.error 记录到 stderr,便于调试 超时控制每个工具调用设置 30s 超时 输入验证用 Zod Schema 严格验证所有参数 只读优先查询类工具不要有副作用 操作确认写操作(创建、删除)在工具描述中说明会修改数据


    常见问题

    Q:MCP Server 必须用 TypeScript 吗? A:不是,官方提供了 Python SDK(mcp 包),Go SDK 也在开发中。

    Q:MCP Server 能处理并发请求吗? A:stdio 模式是串行的(一次一个请求)。高并发场景可以用 SSE(Server-Sent Events)模式。

    Q:如何调试 MCP Server? A:推荐用 MCP Inspector,官方提供的可视化调试工具,可以手动测试工具调用。

    bash
    npx @modelcontextprotocol/inspector dist/index.js
    

    打开浏览器访问 http://localhost:5173

    相关工具

    filesystemgithubsequential-thinking
    如何搭建自己的 MCP Server(完整教程) | 教程中心 | AI Skill Navigation