• AI
  • MCP
  • 协议

MCP 实践:把本地工具接进 LLM 客户端

MCP 解决的是工具接入的重复劳动。本文用最小 server 讲清传输方式、三个原语、权限边界和一套可复用的调试顺序。

每接一个新客户端就把工具重写一遍,是 LLM 应用里最浪费的工程之一。MCP(Model Context Protocol)用一套协议把这个矩阵拆开:客户端实现一次协议,工具方写一个服务端,两边就能对接。

一、它解决的是一张矩阵

没有统一协议时,N 个客户端要接 M 个工具,就得写 N×M 套适配:每个客户端一套函数定义、一套参数校验、一套错误处理。MCP 把它拆成两层,客户端只实现协议,工具方只实现服务端,两者用 JSON-RPC 通信,接入成本从 N×M 降到 N+M。

这也是它在 2026 年迅速铺开的原因:桌面客户端、编辑器插件和自研 Agent 大多已经支持 MCP,所以“写一个 server”往往比“在每个客户端里塞一份函数定义”更划算。

二、三个原语,先想清楚用哪个

  • tools:模型可以主动调用的操作,有输入 schema,可能产生副作用。绝大多数集成用的是它。
  • resources:可被读取的数据,比如文件、数据库行、日志片段,通常由用户或客户端挑选后注入上下文。
  • prompts:预置的提示词模板,适合把团队常用的工作流固化下来。

简单判断:需要模型做决定并执行动作,用 tools;只是把数据喂给模型,用 resources。把纯只读数据做成 tool,模型不得不多调一次,还平白多一次失败机会。

三、两种传输

stdio:客户端把 server 作为子进程启动,通过标准输入输出收发 JSON-RPC。适合本地工具,不需要端口和鉴权,但 server 的权限等于当前用户,写文件、读密钥都挡不住。

HTTP + SSE:server 独立部署,客户端通过 HTTP 发送请求、用 SSE 接收服务端推送。适合团队共享的工具,需要自己处理鉴权、限流和超时。

选择标准很直接:工具跑在谁的数据上、要不要多人共用。本地笔记、本地代码库走 stdio;公司内部的查询服务走 HTTP。

四、最小 server 长什么样

协议本身只是一组 JSON-RPC 方法:握手 initialize,能力声明 tools/list,调用 tools/call。用官方 SDK 写,核心就是注册两个回调:

# python 伪代码,SDK 版本不同,接口以官方文档为准
server = Server("notes")

@server.list_tools()
def list_tools():
    return [Tool(
        name="search_notes",
        description="按关键词搜索本地笔记,只读。",
        inputSchema={"type": "object",
                     "properties": {"q": {"type": "string"}},
                     "required": ["q"]},
    )]

@server.call_tool()
def call_tool(name, args):
    if name != "search_notes":
        raise ValueError(f"unknown tool: {name}")
    hits = index.search(args["q"], limit=5)
    return [TextContent(type="text", text=json.dumps(hits, ensure_ascii=False))]

server.run_stdio()

两个容易踩的点:stdout 只能输出协议消息,任何日志、print 都会污染 JSON-RPC 流,必须写到 stderr;tools/list 要返回完整 schema,客户端靠它生成给模型的函数定义,description 写不好,模型就不会用。

五、客户端侧配置

大多数客户端用一份 JSON 描述 server 怎么启动:

{
  "mcpServers": {
    "notes": {
      "command": "/usr/local/bin/notes-mcp",
      "args": ["--index", "/home/me/notes"],
      "env": { "NOTES_READONLY": "1" }
    }
  }
}

stdio 模式下,这份配置就是安全边界:能启动什么程序、带什么参数、注入什么环境变量,全在这里决定。不要让它变成用户随手可改、却没有任何校验的文件。

六、安全:stdio 等于把 shell 交出去

MCP server 是被客户端拉起的本地进程,权限就是启动它的用户。边界要提前想清楚:

  • 只读优先。先做检索类工具,确认稳定后再考虑写操作。
  • 工具白名单加参数校验,和普通 API 一样做,别因为是“本地”就放松。
  • 写操作要二次确认,尤其涉及删除、覆盖、对外发请求。
  • 资源内容不可信。网页、邮件、第三方接口返回的文字里如果出现指令,模型可能当真,执行层要按数据对待。
  • 记录调用日志,出问题能回溯是哪一次调用改了什么。

七、调试顺序

协议问题常常表现为“客户端里就是看不到工具”,按这个顺序查最快:

  1. 手动跑 server,确认进程能启动、不崩溃、stdout 没有多余输出。
  2. 发一次 initialize,看返回的 capabilities 是否包含 tools。
  3. 调 tools/list,确认 schema 合法、JSON 可解析。
  4. 再调 tools/call,确认返回结构符合 content 数组约定。
  5. 最后接客户端,看它的日志里有没有握手失败或工具名冲突。

JSON-RPC 的 id 类型(数字还是字符串)和错误字段的位置,是最常见的两处不一致。对着规范用最简脚本调一遍,比在客户端里反复猜快得多。

八、什么时候不该用

如果只有一两个固定调用、链路要求毫秒级延迟,或者在强事务流程里,直接把函数调用写死在应用里更简单:少一层进程、少一次握手、少一处日志拼接。协议的价值在复用和生态,而不是“更先进”。

本文基于公开文档与本地实验整理,示例命令请在测试环境验证后再用于生产。

  • AI
  • MCP
  • 协议