MCP 到底解决了什么问题:从协议设计到官方 Python SDK 最小实现
把大模型接到外部工具上,过去是件重复劳动。每个应用要为每个数据源单独写一套对接逻辑:N 个模型应用乘以 M 个工具,就是 N×M 份互不复用的胶水代码。Model Context Protocol(MCP)想做的事很朴素------定一套标准协议,工具方只实现一次 Server,模型应用只实现一次 Client,把 N×M 降成 N+M。
MCP 由 Anthropic 在 2024 年 11 月开源,底层用 JSON-RPC 2.0。它不是某个模型的私有接口,而是一层传输格式与消息语义的约定。
三个角色,两种传输
一次 MCP 交互里有三方:
- Host:用户实际使用的应用,比如某个 IDE 或桌面客户端。
- Client:Host 内部为每个 Server 维护的连接器,一对一。
- Server:你要暴露能力的一方,也是本文要写的东西。
Server 能向外暴露三类原语。Tools 是可被模型调用的函数,通常带副作用,比如查数据库、发请求;Resources 是可被读取的数据,只读、类似 HTTP 的 GET;Prompts 是预置的提示词模板。
传输层目前两种:stdio 走本地进程的标准输入输出,适合把 Server 当子进程拉起;streamable-http 走 HTTP,用于远程。消息格式统一是 JSON-RPC 2.0。
官方 Python SDK 里的 FastMCP
官方 Python SDK 包名就叫 mcp。2024 年 FastMCP 1.0 被并入这个官方 SDK,成了写 Server 的标准方式。它的核心思路是:你只写带类型标注和 docstring 的普通函数,schema 生成和参数校验交给框架。
装包:
bash
pip install "mcp[cli]"
一个最小可运行的 Server:
python
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""把两个整数相加"""
return a + b
if __name__ == "__main__":
mcp.run() # 默认使用 stdio 传输
就这些。@mcp.tool() 会读函数的类型标注自动生成 JSON Schema,读 docstring 作为工具描述交给模型,你不用手写任何参数解析或校验代码。
类型标注是怎么变成协议里的接口契约的
模型要决定「该不该调这个工具、传什么参数」,靠的是 Server 声明的 name、description 和 inputSchema。FastMCP 的映射很直接:函数名变成 tool name,docstring 变成 description,类型标注变成 inputSchema。
所以类型标注在这里不是可有可无的注解,它直接决定了模型看到的接口。写 a: int 和不写,对模型是两种信息量。参数复杂时可以用 Pydantic 模型或 typing 里的结构类型,FastMCP 一样能转成对应 schema。
再加一个只读资源
Tools 用来做事,Resources 用来读数据。区别在于 Resource 应当无副作用、可缓存,语义更接近 GET:
python
@mcp.resource("config://app")
def get_config() -> str:
"""返回应用配置"""
return "log_level=INFO"
Resource 用 URI 标识,这里是 config://app,Client 按 URI 来拉。把读和写分开,是为了让客户端能对只读数据做缓存和权限控制。
跑起来,连上,调试
stdio 模式下,Server 本质就是个从 stdin 读、往 stdout 写 JSON-RPC 的进程,由 Client 拉起。本地调试可以用官方 CLI:
bash
mcp dev server.py
它会起一个可视化的 Inspector,让你在浏览器里手动列出 tools、填参数、看返回,不用先接真实模型。排查 schema 对不对,这一步很省事。
要做成远程 Server,换传输即可:
python
mcp = FastMCP("demo", host="0.0.0.0", port=8000)
mcp.run(transport="streamable-http")
几个容易被忽略的客观事实
MCP 不会让模型变聪明。它只标准化了工具接入这一层。模型该不该调、调得对不对,仍取决于工具描述写得好不好,以及模型本身的能力。
Tools 带副作用,安全边界在 Server 侧。协议不替你鉴权。一个能删库的 tool 暴露出去,模型------或者诱导模型的恶意输入------就可能触发它。危险操作要在 Server 里自己加确认和权限校验,别指望客户端替你拦。
stdio 只适合本地。跨机必须用 HTTP 传输,并自己解决认证,协议本身不规定鉴权方案。
描述即接口。同一个函数,docstring 和类型标注写清楚与否,直接影响模型的调用准确率。这部分投入的回报是最高的。
小结
MCP 的价值不在花哨,而在只实现一次。用官方 Python SDK,一个可用的 Server 就是几个带类型标注的函数,加一行 mcp.run()。真正要花心思的地方不在框架,而在两点:工具描述写得够不够准、副作用工具的权限边界守得够不够严。这两点做扎实,比接多少个工具都重要。
参考:MCP 官方 Python SDK(github.com/modelcontextprotocol/python-sdk)、官方文档 py.sdk.modelcontextprotocol.io。