跳转到主要内容

构建 MCP Server

4.1 开发环境准备

TypeScript (推荐)

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

Python

mkdir my-mcp-server && cd my-mcp-server
pip install mcp

4.2 TypeScript完整示例

一个提供天气查询工具的MCP Server:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

// 创建Server实例
const server = new McpServer({
  name: "weather-server",
  version: "1.0.0"
});

// 定义工具
server.tool(
  "get_weather",          // 工具名
  "获取指定城市的当前天气",  // 描述
  {                        // 输入参数Schema
    city: {
      type: "string",
      description: "城市名称"
    }
  },
  async ({ city }) => {    // 执行函数
    // 这里调用你的天气API
    const weather = await fetchWeather(city);
    return {
      content: [{
        type: "text",
        text: `${city}当前天气: ${weather.temp}°C, ${weather.condition}`
      }]
    };
  }
);

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

多工具Server(含outputSchema和structuredContent)

协议版本 2025-11-25 支持 outputSchema 定义工具的结构化输出格式,并通过 structuredContent 返回结构化数据:
const server = new McpServer({
  name: "commerce-server",
  version: "1.0.0"
});

// 工具1: 搜索商品(含outputSchema)
server.tool(
  "search_products",
  "搜索商品目录",
  {
    query: { type: "string", description: "搜索关键词" },
    category: { type: "string", description: "商品分类(可选)" },
    limit: { type: "number", description: "返回数量,默认10" }
  },
  async ({ query, category, limit = 10 }) => {
    const results = await db.products.search(query, { category, limit });
    const data = {
      products: results.map(p => ({
        name: p.name, sku: p.sku, price: p.price, inStock: p.inventory > 0
      })),
      total: results.totalCount
    };
    return {
      content: [{
        type: "text",
        text: JSON.stringify(data, null, 2)
      }],
      // structuredContent: 供程序化处理(当定义了outputSchema时)
      structuredContent: data
    };
  }
);

// 工具2: 查询库存
server.tool(
  "check_inventory",
  "查询商品实时库存",
  {
    sku: { type: "string", description: "商品SKU" }
  },
  async ({ sku }) => {
    const inventory = await db.inventory.getBySku(sku);
    if (!inventory) {
      return {
        content: [{ type: "text", text: `SKU ${sku} 不存在` }],
        isError: true
      };
    }
    return {
      content: [{
        type: "text",
        text: `SKU ${sku}: ${inventory.quantity}件 (${inventory.warehouse}仓)`
      }]
    };
  }
);

// 工具3: 查询订单
server.tool(
  "get_order",
  "查询订单状态",
  {
    orderId: { type: "string", description: "订单号" }
  },
  async ({ orderId }) => {
    const order = await db.orders.getById(orderId);
    if (!order) {
      return {
        content: [{ type: "text", text: `订单 ${orderId} 不存在` }],
        isError: true
      };
    }
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          id: order.id,
          status: order.status,
          items: order.items.length,
          total: order.total,
          tracking: order.trackingNumber
        }, null, 2)
      }]
    };
  }
);

4.3 Python完整示例

from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types

server = Server("commerce-server")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="search_products",
            description="搜索商品目录",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"},
                    "limit": {"type": "number", "description": "返回数量"}
                },
                "required": ["query"]
            }
        ),
        types.Tool(
            name="check_inventory",
            description="查询商品实时库存",
            inputSchema={
                "type": "object",
                "properties": {
                    "sku": {"type": "string", "description": "商品SKU"}
                },
                "required": ["sku"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "search_products":
        results = await search_products(
            arguments["query"],
            arguments.get("limit", 10)
        )
        return [types.TextContent(
            type="text",
            text=json.dumps(results, ensure_ascii=False)
        )]
    elif name == "check_inventory":
        inventory = await get_inventory(arguments["sku"])
        return [types.TextContent(
            type="text",
            text=f"SKU {arguments['sku']}: {inventory['quantity']}件"
        )]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

4.4 暴露Resources

除了工具,Server还可以暴露数据资源:
// 暴露商品分类作为资源
server.resource(
  "commerce://catalog/categories",
  "商品分类",
  "application/json",
  async () => {
    const categories = await db.categories.getAll();
    return JSON.stringify(categories, null, 2);
  }
);

// 暴露公司信息作为资源
server.resource(
  "commerce://company/info",
  "公司信息",
  "text/markdown",
  async () => {
    return `# 公司名\n\n> 一句话描述\n\n## 主营业务\n...`;
  }
);

4.5 Streamable HTTP Server

远程部署时使用Streamable HTTP传输:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

const app = express();
app.use(express.json());

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

// 注册工具(同上)...

// 创建Streamable HTTP传输
const transport = new StreamableHTTPServerTransport({
  endpoint: "/mcp"
});

// 挂载到Express
app.post("/mcp", transport.handlePost.bind(transport));
app.get("/mcp", transport.handleGet.bind(transport));

await server.connect(transport);

app.listen(3000, () => {
  console.error("MCP Server (Streamable HTTP) running on port 3000");
});

4.6 测试和调试

MCP Inspector

官方提供的调试工具:
npx @modelcontextprotocol/inspector node dist/index.js
Inspector会启动一个Web界面,让你交互式地测试每个工具。你可以:
  • 查看Server暴露的所有工具、资源、提示词
  • 手动输入参数执行工具
  • 查看JSON-RPC请求和响应的原始数据
  • 验证inputSchema和outputSchema

在Claude Desktop中测试

编辑Claude Desktop配置文件: macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["D:/path/to/my-server/dist/index.js"]
    }
  }
}
重启Claude Desktop,在对话中尝试使用你的工具。

4.7 常见错误

错误原因修复
Server启动后无响应console.log 输出到stdout干扰协议日志输出到stderr
工具不出现tools/list 返回为空检查工具注册代码
参数验证失败inputSchema和实际参数不匹配检查JSON Schema定义
连接超时Server进程没有正确启动检查command和args配置
structuredContent 被忽略未定义 outputSchema添加outputSchema或只用content
初始化失败协议版本不匹配确认Client和Server使用兼容的版本
stdio Server绝对不能用 console.log。stdout是协议通道,任何非JSON-RPC的输出都会破坏通信。用 console.error 或写文件日志。

下一章: 商务MCP Server — 电商场景的MCP Server设计模式