一个MCP服务器,本质上是把内部能力暴露给语言模型调用的接口。问题在于,调用方是模型——它可能被读到的文本带偏,可能理解错,也可能单纯犯糊涂。所以"能跑通"不是标准,"跑通之后,一个失控的agent最多能造成的破坏很小、而且看得见",才是标准。
这篇拆解用TypeScript搭一个小型MCP服务器,暴露两个内部能力:订单查询和退款申请。安全模式从第一行代码就嵌进去——严格的输入schema、默认只读、写操作只生成待审批请求、每次调用留审计日志。
MCP服务器到底暴露什么
一个MCP服务器可以向客户端提供三类东西:工具,即模型可以调用的函数,带JSON Schema描述输入,输出可选;资源,即客户端可以加载进上下文的只读数据,用URI寻址;提示模板,即用户可以调用的可复用提示词。
通信走JSON-RPC,两种标准传输方式二选一:stdio,客户端把你的服务器作为本地子进程拉起;或者Streamable HTTP,服务器作为远程服务运行。开发测试阶段先用stdio最快,远程部署要改什么后面再说。
只读工具的几个刻意选择
订单查询工具注册时,输入schema里给orderId加了正则约束,格式必须是ORD加六位数字。这不是建议,是契约——SDK会在处理函数执行前按schema校验输入,模型没法传星号、SQL片段或者别人的邮箱进来。
输出做了投影。只返回四个字段:订单号、状态、总金额、商品件数。不返回带地址和支付元数据的完整订单记录。你返回什么,什么就进入模型的上下文,再从这里可能流向用户或其他工具。
错误作为工具结果返回,而不是抛异常。返回isError: true加一句可读的说明,模型能自己纠正——"这个订单不存在,要不要核对一下ID"——而不是整轮对话直接失败。
注解只是给客户端的提示。readOnlyHint告诉客户端这个工具不改任何东西,有些客户端据此跳过确认弹窗。但注解永远不能当作安全边界。
日志走stderr。stdio传输下,stdout就是协议通道,一句随手写的console.log就能把JSON-RPC流冲坏。这是第一天最容易踩的坑。
审计日志:每次调用都留痕
每个工具调用都应该留下记录,而且要结构化,方便以后查询。审计函数接收工具名、参数和结果,对email、phone、cardLast4这类字段做脱敏替换,然后以JSONL格式追加写入文件。
生产环境会把这些条目送到日志平台,并附上请求或会话ID以及已认证用户。但哪怕只是一个本地JSONL文件,也足够回答"agent昨天干了什么"这个问题。
一个自己造不成伤害的写工具
退款申请工具的设计思路是:让agent帮忙处理退款,但不让模型直接退款。工具只创建一条待审批请求,由人在现有系统里批准。
代码里强制的东西包括:金额上限写死在服务端,20000分;退款金额不能超过订单总额;幂等性保证一个订单只有一条未关闭的退款请求;以及钱永远不会动这个事实。
工具描述帮助模型表现良好,处理函数保证坏行为无害。响应里也明确告诉模型"还没有付款",免得它转头对用户说"您的退款已完成"。
用官方检查器做敌意测试
编译后用MCP Inspector启动服务器,它会列出工具、展示schema、支持手动调用。在接入真实模型之前,先试一遍糊涂或被操纵的agent可能发来的输入:错误的ID格式、负数金额、超过上限的金额、同一笔退款提交两次、超长备注。每一个都应该干净地失败,或者无害地通过。
然后接客户端。多数桌面MCP客户端接受一段配置:命令是node,参数指向编译产物的绝对路径,环境变量里指定审计日志路径。
转远程之后要补的课
stdio服务器以启动者的权限运行。一旦用Streamable HTTP部署给团队用,认证和授权就得来真的:
- 每个请求都要认证。MCP规范定义了基于OAuth 2.1的授权流程,服务器作为资源服务器,校验身份提供方签发的访问令牌。
- 校验令牌受众。只接受签发给本服务器的令牌,不要把客户端的令牌转发给下游API,规范明确禁止这种令牌透传。
- 按工具、按用户授权。把scope或角色映射到工具上,客服会话可能拿到查询和退款申请,分析师的会话只给只读工具。
- 数据按调用者划范围。查询工具只返回已认证用户有权看到的订单,agent继承用户权限,绝不多一分。
- 限流和超时。agent会循环,按会话和按分钟设调用上限,后端调用设超时。
- 校验Origin头,测试时绑定本地,防DNS重绑定攻击。
MCP特有的几个坑
工具结果里的提示注入:如果工具返回用户生成的文本,比如工单正文、邮件、网页,这些文本里可能藏着针对模型的指令。别让工具输出成为模型和有害动作之间的唯一屏障,限制必须在处理函数里执行。
工具投毒:工具描述是给模型读的。只安装你信任的服务器,审查描述,锁定第三方服务器版本。
过宽的工具:一个run_sql或者call_any_api,等于把服务器变成模型的远程shell。宁可要很多窄工具,不要一个万能工具。
输出泄漏:返回最小集,把agent不需要的个人数据脱敏。密钥放环境变量或密钥管理器,永远不要写进工具描述或响应里。
上线前的清单
每个工具都有严格的输入schema,格式、枚举、最大长度、数值边界齐全;输出只投影agent需要的字段;默认只读,每个写工具单独评审;有后果的动作只生成待审批请求;限制和业务规则在处理函数里执行,不在提示词里;每次调用都记录用户、工具、脱敏参数和结果;远程部署用OAuth、受众校验和按工具授权;限流和超时到位;在检查器里用敌意和畸形输入测过。
MCP服务器很小,但它站在一个概率系统和你的真实数据之间。像设计公开API那样设计它,再多假设一件事:调用方可能被说服去做你没打算让它做的事。窄工具、硬限制、写操作默认待审批、加上一份好的审计日志,基本就能撑起一个可以信任的agent集成。
热门跟贴