读完这篇能独立写一个 MCP Server / Client。基于 2026-07-28 协议规范 + 官方 TypeScript Schema。
一、协议基础
1.1 JSON-RPC 2.0 消息格式
MCP 所有消息必须遵循 JSON-RPC 2.0:
Request:
1 2 3 4 5 6
| { "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": { ... } }
|
Response:
1 2 3 4 5 6 7 8 9 10 11
| { "jsonrpc": "2.0", "id": 1, "result": { ... } }
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32601, "message": "Method not found" } }
|
Notification(无 id,无响应):
1
| { "jsonrpc": "2.0", "method": "notifications/cancelled", "params": {...} }
|
1.2 三种传输
| 传输 |
用途 |
适用 |
| stdio |
本地进程通信 |
Claude Desktop 等本地 IDE 调本地 server |
| HTTP + SSE |
远程通信 |
跨网络 server |
| Streamable HTTP |
新标准(2026) |
推荐,取代 HTTP+SSE |
二、3 大原语
MCP Server 暴露 3 类能力给 Client:
2.1 Resources(资源)
文件 / 数据库 / API 响应的只读数据。
1 2 3 4 5 6 7 8 9
| { "uri": "file:///path/to/doc.md", "name": "项目文档", "mimeType": "text/markdown" }
{ "method": "resources/read", "params": { "uri": "file:///path/to/doc.md" } }
|
可执行函数——能带副作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| { "name": "search_docs", "description": "搜索项目文档", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" }, "max_results": { "type": "integer", "default": 5 } }, "required": ["query"] } }
{ "method": "tools/call", "params": { "name": "search_docs", "arguments": { "query": "AI 编程", "max_results": 3 } } }
|
2.3 Prompts(提示词)
预制 prompt 模板,用户可在 Client 端触发。
1 2 3 4 5 6 7
| { "name": "code_review", "description": "代码审查模板", "arguments": [ { "name": "language", "description": "编程语言", "required": true } ] }
|
三、Python Server 实现(最简)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import asyncio from mcp.server import Server, stdio
app = Server("demo-server")
@app.tool() async def search_docs(query: str, max_results: int = 5) -> list[dict]: """搜索项目文档""" return [{"title": f"结果 {i}", "url": f"https://example.com/{i}"} for i in range(max_results)]
@app.resource("config://app") async def app_config() -> str: """应用配置""" return '{"version": "1.0", "debug": false}'
@app.prompt("code_review") async def code_review_prompt(language: str) -> str: """代码审查模板""" return f"请审查以下 {language} 代码的:可读性 / 性能 / 安全性..."
async def main(): await stdio.run_app(app, "demo-server")
asyncio.run(main())
|
四、TypeScript Server 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server( { name: "demo-server", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } } );
server.tool( "search_docs", { query: z.string(), max_results: z.number().default(5) }, async ({ query, max_results }) => ({ content: [{ type: "text", text: `搜索 ${query} 的 ${max_results} 个结果` }] }) );
server.resource( "config://app", async () => ({ contents: [{ uri: "config://app", text: '{"version": "1.0"}' }] }) );
const transport = new StdioServerTransport(); await server.connect(transport);
|
五、Client 端调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| from mcp import ClientSession, StdioServerParameters
async with ClientSession( StdioServerParameters(command="python", args=["server.py"]) ) as session: tools = await session.list_tools() print([t.name for t in tools.tools]) result = await session.call_tool("search_docs", {"query": "AI"}) print(result)
|
六、错误处理
| 错误码 |
含义 |
何时用 |
| -32700 |
Parse error |
JSON 解析失败 |
| -32600 |
Invalid request |
协议不匹配 |
| -32601 |
Method not found |
调不存在的 method |
| -32602 |
Invalid params |
参数类型错 |
| -32603 |
Internal error |
服务端 bug |
| -32000 |
Server error |
自定义服务器错 |
最佳实践:
- 永远不要让 server 崩溃(捕获所有异常 → 转 error 响应)
- error response 必须带人类可读 message
- 关键错误用自定义 code(-32000~-32099)
七、4 条实战建议
7.1 用 stdio 起步
- 本地开发用 stdio(简单)
- 部署再切 SSE 或 Streamable HTTP
1 2
| ✅ search_docs / read_file / create_issue ❌ docs / file / issue
|
7.3 错误用 human-readable message
1
| return error(-32601, "Method 'foo' not found. Available: search_docs, read_file")
|
7.4 资源用 URI 命名空间
1 2 3 4
| file:///abs/path # 文件 db://table/row # 数据库 api://endpoint # 远程 API config://key # 配置
|
八、3 条避坑
- 不要忘了 initialize:Server 启动后必须调
app.run() / server.connect(),否则 Client 收到空响应
- stdio 进程死锁:server stdout 只能写 JSON-RPC 消息,不能写 log(log 写 stderr)
- stdio 协议错配:server 用
print() 输出到 stdout 会破坏 JSON-RPC 解析
九、3 个相关项目
本文目标:看完能独立写一个能跑通的 MCP Server。SDK 把 JSON-RPC 2.0 包装得很好,你只需要关心 tool / resource / prompt 三个原语的业务逻辑。