MCP Server 架构设计与工具注册
本章进入自建 MCP Server 的实战。掌握这一章,你就能为任何内部系统、私有 API 或特殊场景构建定制化的 AI 工具。
何时值得自建 MCP Server
graph TD
A[需要新工具能力] --> B{有现成 Server?}
B -- 有,功能满足 --> C[直接使用,无需自建]
B -- 有,但功能不满足 --> D{修改成本高吗?}
D -- 低,可 fork --> E[Fork 后修改]
D -- 高 --> F[自建]
B -- 没有 --> F
F --> G{工具数量?}
G -- 1-3个简单工具 --> H["FastMCP
(5分钟上手)"] G -- 多工具,有状态 --> I["官方 Python SDK
(完整控制)"] G -- TypeScript 生态 --> J["官方 TypeScript SDK"] style H fill:#27AE60,color:#fff style I fill:#4A90D9,color:#fff style J fill:#4A90D9,color:#fff
(5分钟上手)"] G -- 多工具,有状态 --> I["官方 Python SDK
(完整控制)"] G -- TypeScript 生态 --> J["官方 TypeScript SDK"] style H fill:#27AE60,color:#fff style I fill:#4A90D9,color:#fff style J fill:#4A90D9,color:#fff
MCP Server 的生命周期
sequenceDiagram
participant H as Host (Claude Desktop)
participant S as MCP Server
H->>S: 启动进程(stdio 模式)
S->>H: 就绪信号
H->>S: initialize(协议版本协商)
S-->>H: 确认,返回 Server 元数据
H->>S: tools/list(获取工具列表)
S-->>H: 工具定义列表
loop 会话期间
H->>S: tools/call(调用工具)
S-->>H: 工具执行结果
end
H->>S: 关闭进程
Server 在整个会话期间保持运行,tools/list 只在启动时调用一次。这意味着你可以在 Server 初始化时做一些代价较高的操作(如建立数据库连接池、加载配置)。
工具注册的四个要素
每个 MCP 工具由四个核心要素定义:
from mcp import types
types.Tool(
# 1. 工具名:snake_case,动词开头,描述操作
name="search_products",
# 2. 工具描述:面向 LLM,说明什么时候用、返回什么
description="""
搜索商品目录。
适用场景:用户询问"有没有XXX商品"或"帮我找XXX类型的产品"。
返回:匹配的商品列表(名称、价格、库存、SKU)。
限制:一次最多返回50条,按相关性排序。
""",
# 3. 输入 Schema:JSON Schema 格式,精确定义参数
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词(支持商品名、品牌、类别)"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "other"],
"description": "按类别筛选,不填则搜索所有类别"
},
"max_price": {
"type": "number",
"description": "最高价格过滤(人民币)"
},
"in_stock_only": {
"type": "boolean",
"default": True,
"description": "是否只返回有库存的商品"
}
},
# 4. 必填字段声明
"required": ["query"]
}
)
工具命名规范
| 类型 | 命名模式 | 示例 |
|---|---|---|
| 查询操作 | get_ / list_ / search_ | get_order, list_products, search_customers |
| 创建操作 | create_ | create_ticket, create_report |
| 更新操作 | update_ | update_status, update_inventory |
| 删除操作 | archive_ / soft_delete_ | archive_record(避免用 delete_,更安全) |
| 执行动作 | send_ / run_ / process_ | send_notification, run_analysis |
| 检查状态 | check_ / verify_ | check_availability, verify_payment |
Schema 设计要点
使用 enum 限制有效值
"status": {
"type": "string",
"enum": ["pending", "processing", "shipped", "delivered", "cancelled"],
# LLM 只能选这几个值,不会猜测
}
用 description 给 LLM 提示
"date": {
"type": "string",
"description": "日期,ISO 8601 格式(YYYY-MM-DD),如 2026-03-22。不要用相对日期如'今天'"
}
设置合理的默认值
"limit": {
"type": "integer",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "返回条数,默认10,最大100"
}
工具粒度设计
太细粒度(一个操作一个工具):
get_order_basic_info ← 需要多次调用
get_order_line_items
get_order_shipping_info
get_order_payment_info
LLM 每次查询订单需要 4 次工具调用,效率低,且容易出错。
太粗粒度(一个工具包含所有操作):
manage_orders(action="...", ...) ← action 参数太复杂
LLM 很难正确构造 action 参数。
合适的粒度:
get_order_details(order_id) ← 一次调用获取完整订单
list_orders(filters...) ← 列表查询(分开)
update_order_status(...) ← 写操作(分开,有副作用)
一个完整的 Server 骨架
# server.py - MCP Server 骨架
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
# 初始化 Server,名字会在 Host 端显示
app = Server("my-custom-server")
# 工具列表(在这里集中管理所有工具定义)
TOOLS = [
types.Tool(
name="example_tool",
description="示例工具",
inputSchema={
"type": "object",
"properties": {
"input": {"type": "string"}
},
"required": ["input"]
}
)
]
@app.list_tools()
async def list_tools() -> list[types.Tool]:
"""返回所有可用工具的列表"""
return TOOLS
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
"""处理工具调用请求"""
if name == "example_tool":
result = f"处理结果: {arguments['input']}"
return [types.TextContent(type="text", text=result)]
# 未知工具
raise ValueError(f"Unknown tool: {name}")
async def main():
"""以 stdio 模式运行 Server"""
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
本节执行清单
- [ ] 确定自建 Server 的场景(私有系统/特殊需求)
- [ ] 选择合适的粒度:避免过细或过粗
- [ ] 工具描述面向 LLM 写:什么时候用、返回什么
- [ ] 用 enum 和 description 约束参数,减少 LLM 猜测