MCP-Server设计模式
核对日期:2026-10-27(按 MCP 2026-07-28 规范更新)。
1. 定义与边界
MCP Server 是通过 Model Context Protocol 暴露工具、资源、提示词等能力的服务端组件。它可以是本地 stdio 进程,也可以是远程 Streamable HTTP 服务。
MCP Server 不应被设计成“把整个系统无差别暴露给模型”。好的 Server 是一个受限、可审计、语义清晰的能力边界。
2. 为什么重要
MCP Server 的设计质量决定了 Host 和 Agent 能否安全、稳定地使用外部能力。糟糕设计会导致:
- 工具粒度过粗,无法审批。
- 资源范围过宽,越权读取。
- schema 不稳定,模型选择错误。
- 远程授权混乱,token 被误用。
- 日志和错误污染协议通道。
3. 核心机制
3.1 Server 能力
| 能力 | 设计重点 |
|---|---|
| Tools | 语义清晰、输入输出 schema、错误码、幂等 |
| Resources | URI 设计、分页、访问控制、内容类型 |
| Prompts | 参数化模板、版本、适用场景 |
| server/discover | 2026-07-28 起必须实现,声明支持的协议版本、能力和 Server 身份 |
| Logging(已废弃) | 新实现不要接入;stdio 写 stderr,远程服务用 OpenTelemetry |
| Roots(已废弃) | 新实现不要依赖;文件边界改由工具参数、resource URI 或 Server 配置声明 |
3.2 传输选择
| 传输 | 适用场景 | 注意点 |
|---|---|---|
| stdio | 本地开发工具、IDE 插件、本机文件访问 | stdout 只输出协议消息,日志走 stderr |
| Streamable HTTP | 远程 SaaS、企业共享服务、多用户访问 | HTTPS、OAuth、无状态、限流、审计 |
| HTTP+SSE(已废弃) | 仅兼容存量 | 迁移到 Streamable HTTP |
MCP 2026-07-28 规范移除了协议级会话(Mcp-Session-Id)和 initialize 握手,远程 Server 可以像普通 HTTP 服务一样放在负载均衡后面横向扩展。设计上的直接影响:
- 跨调用状态(如分页游标、草稿、长任务)用 Server 签发的 handle 作为工具参数传递,不绑定连接。
- Streamable HTTP POST 必须带
Mcp-Method、Mcp-Name请求头,网关可以据此按方法和工具名做路由、限流和审计,无需解析 body。 - SSE 断点续传已移除,流断开后 Client 会用新 request ID 重发,写工具必须幂等。
- 需要用户补充输入时,不再由 Server 主动发起请求,而是返回
resultType: "input_required",跨重试的关联信息放进requestState。 - 长耗时任务使用官方 Tasks 扩展(
io.modelcontextprotocol/tasks),Client 通过tasks/get轮询。
4. 设计模式
4.1 Thin Wrapper
一个 MCP 工具包装一个已有 API。
适用:API 已经稳定,权限清晰。
风险:直接暴露内部 API 细节。应通过 adapter 隔离字段。
4.2 Workflow Facade
一个工具触发受控工作流,而不是直接执行多步副作用。
适用:审批、异步任务、长耗时流程。
示例:
deploy.preview -> deploy.request_approval -> deploy.execute
4.3 Read/Write Split
读工具和写工具分离。不要让 customer.manage 同时查询、修改、删除。
适用:几乎所有生产 Server。
4.4 Resource + Tool Pair
Resource 用于读取上下文,Tool 用于执行动作。
示例:
resource: repo://owner/name/pull/123
tool: repo.create_review_comment
4.5 Registry Gateway
企业内部用一个网关管理多个 MCP Server:
- Server allowlist。
- OAuth 和 SSO。
- 工具元数据审查。
- 审计与限流。
- 租户隔离。
5. 工程实现
5.1 Server 目录结构
mcp-server-crm/
src/
server.ts
tools/
search-customer.ts
update-customer-status.ts
resources/
customer.ts
auth/
policy.ts
audit/
logger.ts
schemas/
crm.search_customer.json
crm.update_customer_status.json
tests/
contract.test.ts
security.test.ts
5.2 工具实现边界
server.registerTool("crm.search_customer", {
description: "按名称或客户 ID 查询客户摘要。",
inputSchema: SearchCustomerInput,
outputSchema: SearchCustomerOutput
}, async (args, ctx) => {
await assertScope(ctx.auth, "crm:read", args.tenant_id);
const data = await crm.search(args);
return redactCustomer(data);
});
6. 生产实践
- 每个工具都有 owner、版本、风险等级和错误码表。
- 远程 Server 默认 HTTPS,使用 OAuth scope 限制能力。
- Server 不信任 Host 传来的用户声明,需验证 token 和 audience。
tools/list等列表结果必须返回ttlMs和cacheScope;含租户或用户特定内容时用private,避免共享网关缓存串数据。- 工具列表按确定顺序返回,便于 Client 缓存,也能提高模型 prompt cache 命中率。
- 工具变更通过
subscriptions/listen推送toolsListChanged,Host 据此触发重新审查。 - 透传
_meta中的traceparent/tracestate,让 Server 内部 span 挂到 Agent trace 下。 - Server 返回结果要限制大小,并提供分页。
- 对高风险写操作支持 dry-run、preview、idempotency key。
- 本地 stdio Server 的日志写 stderr,避免破坏协议消息。
7. 常见反模式
| 反模式 | 后果 |
|---|---|
暴露 run_sql 给通用 Agent | 数据泄露和破坏风险 |
| 暴露整个 home 目录为 resource | 文件边界失去权限意义 |
| 在内存里按连接保存会话状态 | 无状态部署后请求落到其他实例即丢失 |
列表结果用 public 缓存但内容随用户变化 | 共享缓存泄露其他用户的工具或资源 |
| 用一个工具代理所有内部 API | Host 无法审批和审计 |
| 忽视多租户隔离 | 用户数据串租户 |
| Server 自行决定调用外部模型且不可见 | Host 失去成本和数据控制 |
8. 评测方法
- 协议契约测试:tools/list、tools/call、错误格式、schema。
- 权限测试:不同用户、租户、scope 的访问矩阵。
- 安全测试:prompt injection、路径穿越、越权参数、恶意返回。
- 稳定性测试:超时、并发、限流、Server 重启。
- 兼容性测试:目标 Host、SDK、Inspector。
9. 安全与治理
- 工具描述和返回内容都可能成为攻击面。
- 远程 Server 必须校验 OAuth token audience,不能接受发给其他资源服务器的 token。
- 授权服务器应在授权响应中返回
iss(RFC 9207);客户端注册优先支持 Client ID Metadata Documents,OAuth 动态客户端注册已废弃。 - 不把第三方 API token 原样返回 Host 或模型。
- 对 Server 依赖做供应链扫描。
- 重大工具新增和权限扩大需要安全评审。
10. Server 安全设计清单
| 项 | 要求 | 失败后果 |
|---|---|---|
| 工具粒度 | 读写分离、语义明确 | Host 无法审批 |
| 输入 schema | 禁止宽泛参数和任意命令 | 参数注入、越权 |
| 输出 schema | structuredContent 优先 | 下游解析不稳定 |
| 授权 | 校验 token、audience、scope | confused deputy |
| 租户隔离 | token、缓存、日志隔离 | 数据串租户 |
| 日志 | 结构化日志写 stderr 或日志系统 | stdio 协议污染 |
| 版本 | server/discover 声明版本;tools/list 带 ttlMs,变化可订阅 | Host 使用过期 schema |
| 状态 | 不依赖连接会话,跨调用状态用显式 handle | 横向扩展后状态丢失 |
| 供应链 | 依赖扫描、发布签名或来源验证 | 恶意 Server 风险 |
11. 工程示例:工具返回结构
{
"content": [
{
"type": "text",
"text": "找到 2 条客户记录,详细字段见 structuredContent。"
}
],
"structuredContent": {
"items": [
{"customer_id": "cus_001", "name": "Acme", "tier": "enterprise"},
{"customer_id": "cus_002", "name": "Acme China", "tier": "standard"}
],
"has_more": false
},
"isError": false
}
返回给模型的自然语言内容应只做摘要;稳定消费依赖 structuredContent。
12. Server 评测门禁
server/discover、tools/list契约测试通过,结果带resultType、ttlMs、cacheScope。- 多实例部署下,同一用户的连续请求落到不同实例结果一致。
- 所有 tool input/output schema 有 JSON Schema 校验。
- 未授权用户、错误租户、过期 token 全部拒绝。
- 写工具具备 dry-run 或 idempotency key。
- prompt injection 工具返回不会被包装成 Host 指令。
- stdio 模式 stdout 没有非 JSON-RPC 日志。
- 远程 HTTP 模式启用 HTTPS、OAuth、限流和审计。
13. 权威资料
- MCP Build a Server: https://modelcontextprotocol.io/docs/develop/build-server
- MCP Specification 2026-07-28 Key Changes: https://modelcontextprotocol.io/specification/2026-07-28/changelog
- MCP Tools specification 2026-07-28: https://modelcontextprotocol.io/specification/2026-07-28/server/tools
- MCP Streamable HTTP transport 2026-07-28: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- MCP Tasks extension: https://modelcontextprotocol.io/extensions/tasks/overview
- MCP Security Best Practices: https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices
- MCP official GitHub: https://github.com/modelcontextprotocol