一个MCP运维工具最容易被忽略的承诺是“只读”究竟由谁保证。工具名叫read_logs并不妨碍参数指向敏感目录数据库账号只读也不意味着返回结果适合完整交给模型。开源之前我更愿意先审查这条边界再考虑命令行和封面是否漂亮。10月9日Anthropic公布了评测与内部使用中出现的意外操作案例。这些案例给工具开发者的提醒很具体模型想完成任务不能替代工具拥有执行权限。MCP协议负责交互真正的访问范围仍需服务实现和基础设施共同约束。先分清协议契约与业务权限现行MCP传输应区分stdio和Streamable HTTP。旧HTTPSSE是兼容场景不能继续把它写成远程接入的唯一现行方案。stdio上的stdout只输出协议消息诊断日志走stderr。业务层还要再回答三个问题允许读哪个项目、一次最多返回多少条、哪些字段不能回传。JSON Schema解决参数形状不能代替仓库归属检查、数据库授权或文件系统权限。让拒绝路径和正常路径一样明确下图把请求检查串起来。下方箭头只表示受约束结果的回传方向具体实现仍要在网关中完成输出裁剪和字段过滤。请求被拒绝时应返回稳定错误类别别把底层路径、凭证或原始SQL错误直接展示出来。这个最小例子检查了什么保存代码为example.mjs运行node example.mjs。它只接受已知的logs.read工具、固定项目和整数行数并按字符边界限制示例返回文本的UTF-8字节数。错误请求在读取数据前就被拒绝。这里选择显式分支目的是让维护者读得懂拒绝条件。参数检查不是完整的JSON Schema验证器复杂对象应使用正式验证库并为未知字段、嵌套深度和载荷上限设计清晰规则。import assert from node:assert/strict; function check(req) { if (req.tool ! logs.read) throw Error(unknown_tool); if (req.project ! demo) throw Error(project_denied); if (!Number.isInteger(req.lines) || req.lines 1 || req.lines 20) throw Error(invalid_lines); return req; } function capUTF8(text, maxBytes) { let out ; for (const ch of text) { if (Buffer.byteLength(out ch, utf8) maxBytes) break; out ch; } return out; } assert.equal(check({tool:logs.read,project:demo,lines:2}).lines, 2); assert.throws(() check({tool:shell,project:demo,lines:2}), /unknown_tool/); assert.throws(() check({tool:logs.read,project:prod,lines:2}), /project_denied/); assert.throws(() check({tool:logs.read,project:demo,lines:21}), /invalid_lines/); assert.equal(capUTF8(日志abc, 7), 日志a); console.log(contract checks: ok);本地运行输出contract checks: ok开源发布时还缺哪些材料把协议版本、权限模型、示例输出和拒绝示例放在README前半段。工具如果要读生产日志应另给脱敏规则和撤销方式而不是只提供一个“安装后即可使用”的命令。发布材料应区分教学代码和已上线实现并给出对应仓库、Release与运行条件让读者能核查支持范围。适用范围与未测部分离线验证只覆盖输入拒绝与UTF-8裁剪没有启动MCP客户端、数据库或集群也没有验证日志脱敏规则的生产完整性。参考资料MCP transports2025-06-18Investigating unintended model actions2026-10-09