[控场AI]
· 7 分钟阅读· 3,976 字

MCP服务器踩坑复盘:我重新设计内部工具时的8条经验

MCP服务器踩坑复盘:我重新设计内部工具时的8条经验

MCP 协议不负责安全治理,八条实践经验教你安全地暴露内部工具。

文章是一位开发者用 MCP(Model Context Protocol)将内部系统暴露给 AI 模型后的踩坑复盘。作者指出,用 Python SDK 的 FastMCP 做出可运行原型只需一个下午,但要让安全团队放行则耗时得多,因为协议本身不负责安全与治理。文章给出八条实践:工具应少而窄而非通用 run_sql;用户身份须贯穿每个请求并在工具内校验;工具 docstring 就是接口,需按 API 标准评审;报错应结构化、可行动;只读 Resources 同样是数据泄漏面;审计日志要落在服务端;服务器需像 API 一样做版本管理;并明确谁有权部署服务器。最后归纳出重来一次第一天就该搭好的六项底座。

MCP服务器踩坑复盘:我重新设计内部工具时的8条经验

MCP(Model Context Protocol)让把内部系统暴露给 AI 模型变得异常简单——原作者用一个下午就把第一个 MCP 服务器做成了能跑的原型。但要让安全团队点头放行,花的时间则要多得多。

MCP服务器内部工具架构示意

这不是一篇 MCP 入门教程,而是一份"如果重来一次我会怎么做"的复盘。作者用 Python SDK 的 FastMCP 展示了若干代码片段,核心观点很明确:协议本身不负责安全和治理,那部分责任仍然在开发者身上。 下文将其经验整理为 8 条可操作的实践建议。

先厘清:MCP 服务器到底暴露了什么

MCP 服务器向 Claude Desktop、IDE 或自建 Agent 这类客户端暴露三类东西:Tools(模型可调用的函数,带 JSON schema 参数定义)、Resources(客户端可加载进上下文的只读数据)、Prompts(可复用的提示模板)。

客户端负责发现可用能力,模型自己决定何时调用。协议把底层管道都处理好了——恰恰因为这样,人很容易产生"把所有东西都暴露出去"的冲动,而这正是第一个坑。

MCP 由 Anthropic 于 2024 年底提出并开源,目标是给 AI 应用提供一套统一协议去连接外部数据与工具,常被类比为「AI 世界的 USB-C」。它采用客户端—服务器架构:Claude Desktop、IDE 插件或自建 Agent 充当客户端,内部系统一侧运行 MCP 服务器,两者通过 JSON-RPC 2.0 消息通信,并支持 stdio 和基于 HTTP 的传输方式。协议规范定义了能力协商、工具调用、资源读取和提示模板等消息格式,但有意不规定认证、授权与审计的具体实现。这一点正是理解全文的关键:MCP 解决的是「怎么连」,而不是「谁能连、能做什么」。

经验一:工具要少而窄,不要大而全

作者的第一版服务器提供了一个通用的 run_sql 工具,演示效果很惊艳,但它无法做权限控制、无法做有意义的审计,模型还经常写出全表扫描的查询。

第二版拆成了具体工具:get_open_tickets(team, limit) 只返回某个团队的待处理工单,并强制 limit 上限为 50;get_customer_summary(customer_id) 只返回套餐、状态和最近 5 张发票。

@mcp.tool()
def get_open_tickets(team: str, limit: int = 20) -> list[dict]:
    """Return open support tickets for one team, newest first. Max 50."""
    limit = min(limit, 50)
    return tickets.find_open(team=team, limit=limit)

窄工具对模型更容易选择、对权限更容易约束、对审计人员也更容易解释。如果某个团队确实需要灵活查询,那应该做人用的报表工具,而不是塞进 Agent 的工具集里。

经验二:用户身份必须贯穿调用链

最省事的做法是用单一服务账号跑 MCP 服务器——所有用户、所有请求共享同一套权限。在企业里,这条路走不通。

作者现在的做法是在每个请求中传递终端用户身份,并在工具内部校验:

@mcp.tool()
def get_customer_summary(customer_id: str, ctx: Context) -> dict:
    user = resolve_user(ctx)  # 来自客户端的 auth token
    if not policy.can_view_customer(user, customer_id):
        return {"error": "forbidden", "detail": "You don't have access to this customer."}
    audit.log(user=user.id, tool="get_customer_summary", customer_id=customer_id)
    return crm.summary(customer_id)

身份如何解析取决于传输方式和认证架构,但原则一致:工具负责执行用户的实际权限,模型永远看不到用户本人无权直接查看的数据。

在 MCP 中,Context 对象是服务器代码获取调用上下文的主要入口,通常可从中拿到请求相关的元数据、会话信息以及向客户端回传进度或日志的能力。身份透传的常见做法有两种:一是由客户端在建立连接时通过 OAuth 等机制获取令牌,服务器在每次工具调用时解析该令牌并映射到内部用户;二是由网关层完成认证后,把已验证的用户标识注入请求上下文。无论哪种方式,服务器都不应信任模型自行提供的用户标识参数,因为模型可能被提示注入诱导而伪造身份。

经验三:工具描述就是接口的一部分

模型靠 docstring 决定调用哪个工具。描述含糊会导致选错工具,而这类错误不会抛出异常,排查起来格外隐蔽。作者总结了三条有效做法:

  • 写明返回内容和边界(如"最多 50 条""最近 5 张发票")
  • 写明何时不要用(如"不要用于账单争议,请改用 get_invoice_detail")
  • 参数名自解释:team 优于 t,customer_id 优于 id

作者在 code review 中审查工具描述的认真程度与审查 API 变更相同——因为对模型来说,描述就是接口。

经验四:返回模型能据此行动的报错

失败时不要抛出异常变成通用错误信息,而应返回结构化结果。比如:

return {"error": "not_found", "detail": "No customer with id CUST-123. Ask the user to confirm the ID."}

能力较强的模型会根据 detail 自我恢复,通常是向用户追问澄清信息;而一个堆栈跟踪对模型来说毫无可用之处。

经验五:别把只读资源当安全区

Resources 因为只读而显得无害,但一个返回"所有待处理工单"的资源,可以一次性把数百条客户记录塞进模型上下文。作者的建议是:对资源施加与工具相同的权限检查和大小限制,敏感数据优先做成带过滤参数的工具而非资源。这也是 MCP 场景下最容易被忽视的数据泄漏面之一。

上下文窗口是有限且昂贵的资源:一次把数百条记录塞进上下文,不仅可能造成敏感数据泄漏,还会挤占模型用于推理的空间,导致后续回答质量下降甚至关键指令被「淹没」。业界常说的「上下文污染」就包括这类情况——无关或过量数据进入上下文后干扰模型判断。因此对 Resources 设置分页、字段白名单和条数上限,本质上既是安全控制,也是效果控制。

经验六:审计日志要落在服务端

客户端日志告诉你模型"请求了什么",服务端日志才能告诉你系统里"实际发生了什么"。审计和事故复盘依赖的是后者。作者对每次调用记录:用户、工具名、参数(脱敏后)、返回大小、状态和耗时。

经验七:像维护 API 一样给服务器做版本管理

一旦有多个客户端依赖你的 MCP 服务器,改动工具就是破坏性变更。改参数名或收紧返回格式,会悄无声息地让所有使用该工具的 Agent 退化——因为模型已经习惯了旧的描述和示例。作者的实践包括:

  • 以兼容方式新增工具,而非直接修改旧工具,等客户端迁移后再废弃旧工具
  • 维护一份变更日志,记录工具新增、描述修改和 schema 变更
  • 每次发版跑一套小型行为测试:每个工具几条真实请求,既验证正确调用,也验证正确的拒绝逻辑
def test_summary_requires_access():
    ctx = fake_context(user="analyst-no-crm")
    result = get_customer_summary("CUST-123", ctx)
    assert result["error"] == "forbidden"

经验八:明确谁有权部署服务器

在客户端侧,MCP 让任何人连接一个新服务器都轻而易举。对开发者是好事,对安全团队却是噩梦。作者几个月内就见到过大量非官方服务器跑在个人笔记本上,各自带着自己的凭据。

一套轻量流程就够用:维护一份已批准服务器清单、每个服务器有明确负责人、任何接触生产数据的服务器在加入前要经过评审。流程不必繁重,但必须在清单变长之前就存在。

如果重来一次,第一天就该搭好的底座

作者的清单可以浓缩为六项:一组各自有负责人的窄工具;贯穿全部工具的用户身份校验;按 API 文档标准评审的 docstring;结构化、可行动的报错返回;覆盖所有工具和资源的大小限制;带脱敏的服务端审计日志。

MCP 确实是连接模型与系统的优秀标准,但它不决定谁有权做什么。那部分,仍然要靠我们自己设计和落实。

#MCP服务器#Model Context Protocol#MCP安全#AI Agent工具#FastMCP#MCP最佳实践#内部工具集成#AI权限控制
分享:

相关推荐