安全团队问你:Claude 通过 MCP 读订单库,怎么保证只读、可追溯、离职即失效?2026 年做 MCP 接入,这个问题绕不过去。下面用一个只读订单查询 Server 走完全程:本地跑通、接客户端、加 OAuth 和沙箱、补审计、换模型测试。规范以 modelcontextprotocol.io/specification 上的当前版本为准,示例按 2025-06-18 及之后的 Streamable HTTP、OAuth 2.1 路线写。
MCP 核心概念
MCP 是 JSON-RPC 2.0 协议。Host 是用户用的客户端,Claude Desktop、Cursor、VS Code Copilot Chat 都算。Host 里的 MCP Client 负责和 Server 建连接、协商能力、转发调用。Server 暴露三类原语:
| 原语 | 用途 | 例子 |
|---|---|---|
| Tools | 可执行动作 | 查订单、发退款、创建工单 |
| Resources | 只读数据 | 订单 JSON、日志文件、数据库 schema |
| Prompts | 预置提示模板 | 退款审核话术、故障排查 checklist |
| 传输 | 适用 | 注意 |
|---|---|---|
| stdio | 本地进程 | 客户端拉起 Server 子进程,权限跟本机用户走 |
| Streamable HTTP | 远程共享 | 要处理认证、限流、会话、审计,旧 HTTP+SSE 已不推荐新项目使用 |
快速搭建 Server
用官方 Python SDK。包名 mcp,FastMCP 封装了工具注册和传输。2026 年建新项目,建议锁 SDK 版本,别直接依赖浮动 latest;MCP 规范还在演进。官方仓库:modelcontextprotocol/python-sdk。
python -m venv .venv
source .venv/bin/activate # Windows: .venv\\Scripts\\activate
pip install 'mcp[cli]'
server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP('order-service')
@mcp.tool()
def get_order(order_id: str) -> dict:
'''按订单号查询订单摘要。只读,不返回手机号和地址。'''
# 替换成你的内部 API 或数据库查询
return {
'order_id': order_id,
'status': 'paid',
'amount': 199.00,
'updated_at': '2026-01-15T10:23:45Z',
}
if __name__ == '__main__':
mcp.run()
mcp.run() 默认 stdio。跑之前先用 MCP Inspector 验证:
npx @modelcontextprotocol/inspector python server.py
Inspector 里重点看两件事:tools/list 能不能列出 get_order,tools/call 传 {"order_id":"A123"} 能不能返回结构化结果。如果 Inspector 报 schema 错误,通常是 Python 类型注解和返回类型不匹配;把返回值限制为 dict、list、str、int 这类 JSON 可序列化类型。
接入主流客户端
| 客户端 | 配置文件 | 传输 | 实操注意 |
|---|---|---|---|
| Claude Desktop | macOS: ~/Library/Application Support/Claude/claude_desktop_config.json;Windows: %APPDATA%\\Claude\\claude_desktop_config.json | stdio | 改完完全退出再启动;路径写绝对路径 |
| Cursor | ~/.cursor/mcp.json 或项目 .cursor/mcp.json | stdio / Streamable HTTP | 项目级配置放进仓库前先删密钥 |
| VS Code + GitHub Copilot | .vscode/mcp.json 或用户设置 | stdio / HTTP | 不同版本字段可能变化,以 VS Code 文档为准 |
| 自研 Agent | 用官方 SDK 写 Client | 任意 | 要处理初始化、能力协商、超时、重连 |
{
"mcpServers": {
"order-service": {
"command": "python",
"args": ["/绝对路径/server.py"],
"env": {
"ORDER_API_BASE": "https://internal-api.corp.local"
}
}
}
}
Cursor 和 VS Code 的字段名接近,但 VS Code 有些版本用 servers 而不是 mcpServers。别复制粘贴就完事,打开客户端设置页确认当前字段。远程 Streamable HTTP 则把 command/args 换成 URL 和认证配置;具体字段看客户端文档。
OAuth、沙箱与审计
stdio 模式下,Server 权限等于启动它的用户权限。安全团队通常不接受“本机用户能读数据库就能读全部订单”这种模型。远程 MCP Server 要按 OAuth 2.1 资源服务器做。
流程:
实现时抓住四个检查点:
- token 的
aud必须等于 MCP Server 的资源标识,不能拿别的 API token 直接复用。 - 工具级 scope 单独定义,例如
orders.read、orders.refund,别给一个mcp.all。 - 每次
tools/call都重新校验 scope,不要只在连接建立时查一次。 - 下游 API 用独立凭证,MCP Server 不保存用户密码,也不把用户 token 直接透传给内部 API。
沙箱不是把 Server 丢进 Docker 就完事。只读查询 Server 的容器参数可以这样:
docker run --rm -i --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --cap-drop=ALL --security-opt no-new-privileges --network=none mcp-order:0.1
--network=none 适合完全本地计算的工具。要访问内网 API,就换成受限网络或出口代理,并在代理层做域名白名单和审计。别用 --privileged,也别挂载 Docker socket。
审计日志至少留这些字段:
| 字段 | 说明 |
|---|---|
| ts | UTC 时间 |
| request_id | JSON-RPC 请求 ID,方便串联 |
| client_id | 哪个客户端 |
| subject | 用户或服务身份 |
| tool | 工具名 |
| scopes | 本次调用命中的 scope |
| args_digest | 参数摘要,不存完整 PII |
| status | ok / denied / error |
| latency_ms | 耗时 |
跨模型兼容实践
MCP 本身不绑定模型。兼容性取决于客户端怎么把工具描述塞进上下文,以及模型怎么生成参数。减少踩坑的做法:
- 工具名用动词加名词:
get_order、search_invoice,别用query、handle。 - 描述写清楚“什么时候用、什么时候别用、参数含义、返回结构”。模型看不到你的内部代码。
- 参数 schema 尽量扁平,少用深层嵌套和
oneOf。必填字段控制在 3 个以内。 - 返回结果别超长。列表默认 20 条,给
next_cursor。大段文本让模型自己截断,容易丢关键信息。 - 错误信息给可行动提示,比如“order_id 格式应为 A 开头加 6 位数字”,别返回 Python 堆栈。
- 在真实客户端里跑测试矩阵:Claude Desktop、Cursor、VS Code Copilot、你们自研 Agent。同一工具在不同客户端里表现可能不同。
下一步
挑一个只读查询接口,用 FastMCP 写 30 行 Server,在 MCP Inspector 里跑通 tools/list 和 tools/call。然后把 OAuth 的 audience 校验和工具级 scope 加在入口,再决定要不要接生产客户端。检查项只有一个:把 token 换成无 orders.read 的凭证,调用必须返回 denied,并且审计日志里能看到这次拒绝。