MCP 工具安全设计:为什么 stdio 也要做最小权限与输出过滤

MCP 工具安全设计:为什么 stdio 也要做最小权限与输出过滤

设计 MCP Server 时有个很诱人的捷径:选 stdio,复用应用内部函数,再把函数结果完整返回。没有监听端口,代码也很短,看起来已经足够安全。

我的取舍是把 stdio 只当成传输层收缩,而不把它当成权限边界。工具能力、输入范围、服务端授权、输出字段和重要副作用分别设门禁,所有入口复用同一应用服务。

工程场景与决策冲突

stdio 的优势很明确:本地宿主启动子进程即可通信,默认不暴露远程服务,也不需要为每个工具建立 HTTP 路由。但 Server 进程仍继承自己的文件、网络和凭据权限。

因此真正的冲突不是"stdio 还是 HTTP",而是:为了开发效率,我们希望直接复用内部能力;为了安全和可维护性,我们又不能把内部能力原封不动暴露给模型。

方案拆解与关键权衡

我把边界拆成五个决策:

  1. 工具保持窄动作,不提供万能命令;
  2. 输入使用类型模型,限制路径、数量和长度;
  3. 授权在服务端用例内执行,不相信客户端声明;
  4. 输出通过展示层白名单过滤;
  5. 发布、删除、支付等副作用使用独立一次性批准。

这会比"函数直接注册成工具"多一些样板代码,但换来的收益是入口一致性:CLI、API、MCP 不会各自复制业务规则,也不会因为一个入口漏掉校验而出现能力侧门。

实现链路与最小示例

先定义窄输入:

python 复制代码
class ReadArticleInput(BaseModel):
    article_id: str = Field(min_length=1, max_length=64)
    include_metrics: bool = False

工具层只负责解析和展示:

python 复制代码
def read_article_tool(value: ReadArticleInput, service: ArticleService) -> dict:
    article = service.get_visible_article(
        value.article_id,
        include_metrics=value.include_metrics,
    )
    return {
        "articleId": article.article_id,
        "title": article.title,
        "status": article.status,
    }

不要返回整个领域对象。对象以后新增内部路径、凭据状态或审计字段时,白名单不会跟着自动外泄。

写操作再拆一层:

text 复制代码
prepare -> approve(id, revision, hash, expiry) -> execute once -> verify

批准不是一个长期布尔值,而是绑定精确对象和版本的短期能力。执行超时后先回读事实,不能直接重复调用。

证据、限制与自动化边界

RuyiBookCourse 的 FastAPI MCP 网关章节把"Web 应用功能变成 MCP 工具"定义为能力映射,而不是内部函数裸暴露。真实项目的实现也验证了一个重要结构:stdio MCP、loopback API 与 CLI 都调用同一个服务层;API 默认只绑定回环地址,远程绑定必须显式开启并配置 Token。

这套方案不能把一个本来就高权限的进程变成操作系统级沙箱。如果工具确实需要执行不可信代码,还要加容器、低权限账户、文件系统隔离或专用执行环境。应用层门禁与系统隔离是互补关系。

可复用检查清单

  • 是否存在任意命令、任意 SQL、任意路径工具;
  • 参数是否有类型、长度、数量和目录边界;
  • 服务端是否重新校验身份和对象状态;
  • 输出是否只保留白名单字段;
  • 日志是否可能出现 Token、Cookie、正文或绝对路径;
  • CLI、API、MCP 是否复用同一用例;
  • 重要写操作是否有短期一次性批准;
  • 超时恢复是否先读后写;
  • 测试是否覆盖越权、路径逃逸、敏感输出和重复批准。

收束

stdio 是一个值得采用的默认传输,但不要让"没有端口"替代安全设计。真正可复用的 MCP 边界,是窄工具、服务端授权、输出过滤和可验证副作用共同组成的。

你现在工具列表里权限最宽的是哪一个?如果只能先改一处,我会先把它从万能入口拆成有限动作。

发布前门禁

  • 减少重复背景
  • 突出工程选择而非概念堆砌
  • 不给未经验证的数据结论
  • 本轮无已认领实验,不自行补写实验结论
相关推荐
大鸡腿同学8 小时前
手心向上,手心向下
后端
CCblandy9 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
凤山老林11 小时前
Spring Boot 集成 Sa-Token 实现高可用权限认证:登录鉴权、动态路由权限与接口防重放实战
spring boot·后端·sa-token·bootstrap
Bazingga12 小时前
一次推理的完整旅程:ReAct推理深度解析
后端
姜鱼问生12 小时前
Nginx gzip 压缩:一个配置让页面加载快 3 倍
后端·算法·架构
颜酱12 小时前
一栋商业大厦,讲清 InnoDB 的表空间、段、区和页
后端·mysql
码事漫谈12 小时前
1956 亿行数据该怎么存:金仓时序数据库的三个实测
后端
Wx-bishekaifayuan12 小时前
springboot桂味美食分享系统40540-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
彭亚川Allen13 小时前
从零手搓 RAG 之检索全流程,给大模型接上自己的知识库
前端·后端·ai编程
IT_陈寒13 小时前
别让Java字符串拼接偷偷吃掉你的内存
前端·人工智能·后端