构建 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
- 查看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"]
}
}
}
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设计模式