MCP(Model Context Protocol)解决的不是“让模型更聪明”,而是把模型与外部工具、数据源之间的连接方式标准化。一个 MCP Server 可以把查询数据库、调用业务 API、读取文件或触发部署等能力包装成模型能够发现和调用的工具。
如果把模型看成负责决策的应用层,MCP Server 更像一层受控的适配器:它定义可用能力、参数格式和访问边界,再由客户端负责发现工具并发起调用。这样做的价值在于,业务系统不必为每一种模型客户端重复实现一套集成协议,也不需要把数据库凭据直接交给模型。
先区分 tool、resource 和 prompt
编写 Server 前,先确定你要暴露的内容属于哪一类:
- Tool:供模型按需调用的动作,例如查询订单、发送通知或执行一次受控的数据处理。Tool 可能产生副作用,需要做权限校验、参数校验和幂等设计。
- Resource:供客户端或模型读取的上下文数据,例如配置、文档或某个 URI 对应的只读内容。Resource 不应被设计成任意写入接口。
- Prompt:可复用的提示模板,用于把一类任务的指令组织起来。它不是后端 API,也不应替代业务授权。
这种划分并不只是命名问题。把“删除数据”注册成一个没有边界的 tool,和把“读取订单摘要”注册成只读 resource,风险模型完全不同。面向生产环境时,建议先列出允许的动作,再决定哪些动作值得暴露给模型。
用 Python 写一个最小 MCP Server
官方 Python SDK 提供了 FastMCP 高层接口,可以用装饰器把普通 Python 函数注册为 tool 或 resource。下面的例子只实现加法,适合先验证工程、客户端和协议链路是否正常:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Return a greeting for a name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="stdio")这里有三个容易被忽略的细节:
- 类型注解要与真实输入一致。 SDK 会利用函数签名生成参数模式,类型写得过于宽泛会让客户端难以判断应该传什么。
- 描述文字要具体。 tool 的名称和 docstring 会影响客户端展示以及模型选择工具的判断,应该说明用途、输入限制和副作用。
- 日志不要写到 stdout。 stdio 传输把标准输入输出用于 MCP 消息,调试日志应写到 stderr,否则普通文本可能破坏协议通信。
依赖管理可以使用 uv、venv 或项目已有的 Python 工具链。关键不是包管理器本身,而是把 SDK 固定在项目的依赖文件中,并在本地使用隔离环境运行,避免不同教程使用的版本互相覆盖。
本地开发用 stdio,远程服务用 Streamable HTTP
MCP 的传输方式决定 Server 如何被部署。官方规范目前将 stdio 和 Streamable HTTP 作为标准传输;旧的 HTTP+SSE 方案仍可用于兼容旧客户端,但新项目不宜再按旧接口搭建。
stdio:本机工具的起点
stdio 下,客户端启动 MCP Server 子进程,双方通过标准输入输出交换 JSON-RPC 消息。它适合以下场景:
- Server 只服务当前开发者或当前桌面客户端;
- 工具需要读取本机文件、执行本机命令;
- 你正在调试工具定义,还没有准备域名、网关和鉴权系统。
它的边界也很清楚:进程生命周期由客户端管理,跨机器访问和多租户隔离不是它要解决的问题。不要为了“先跑起来”把生产数据库凭据硬编码在本地配置里。
启动方式通常是:
python server.pyStreamable HTTP:部署到云服务器
当 MCP Server 需要被 Web 应用、多个 Agent 或团队成员共享时,可以将它作为独立的 HTTP 服务部署到云服务器、容器平台或其他受控运行环境。Streamable HTTP 使用一个 MCP endpoint 接收 HTTP 请求,服务端可以返回普通 JSON,也可以用 SSE 发送流式响应或通知。
一个最小的启动方式如下:
if __name__ == "__main__":
mcp.run(transport="streamable-http")真正上线时,还需要补齐这些外围能力:
- 身份认证:至少区分调用者身份,不要把“能访问 endpoint”当成业务授权。
- 工具级授权:不同用户可以调用的 tool 不应完全相同,写操作还要单独校验资源归属。
- 输入与输出限制:限制字符串长度、文件范围、查询条件和下游 API 的可调用范围。
- 限流与超时:模型可能重复尝试同一个调用,应设置请求超时、并发上限和下游熔断。
- 审计日志:记录调用者、tool 名称、关键参数摘要、结果状态和耗时,敏感内容要脱敏。
- 反向代理配置:在云上通常由 Nginx、网关或负载均衡器处理 TLS、域名和基础限流,MCP Server 本身只监听内网地址。
如果服务会调用数据库或云资源,建议把 MCP Server 放在私有网络中,通过最小权限的账号访问下游服务;公网只暴露经过认证的 MCP endpoint。这样比把数据库端口直接开放给模型客户端更容易控制风险。
传输方式怎么选
可以用下面的判断路径减少返工:
| 需求 | 建议 | 原因 |
|---|---|---|
| 本地开发、桌面客户端、访问本机文件 | stdio | 部署简单,客户端管理子进程 |
| 多个客户端共享、需要域名和统一鉴权 | Streamable HTTP | 适合独立部署和网关接入 |
| 维护已有旧客户端 | 兼容旧 HTTP+SSE | 仅作为迁移过渡,避免新项目继续依赖 |
不要把“远程”简单理解成“把 stdio 放到一台服务器上”。stdio 的通信双方是同一台机器上的进程;远程场景需要考虑身份、会话、网络超时、负载均衡和日志,这些都更接近一个正式 Web 服务。
上线前的检查清单
完成最小示例后,可以按以下顺序验证:
- 在隔离环境安装依赖,确认 Server 能正常启动。
- 用 MCP Inspector 或目标客户端检查 tools、resources 是否被正确发现。
- 为每个 tool 编写正常输入、缺少参数、越权参数和超时场景的测试。
- 在没有真实生产凭据的环境中验证下游调用,确认日志不会泄露密钥或个人数据。
- 远程部署时先接入 HTTPS、认证、限流和审计,再开放给模型使用。
- 为有副作用的操作增加确认机制或审批流,不要让模型凭一次自然语言请求直接执行高风险动作。
MCP Server 的代码量通常不是难点,真正决定可用性的,是工具边界、授权策略和部署方式。先用 stdio 把工具契约跑通,再根据调用范围迁移到 Streamable HTTP,通常比一开始就搭建复杂的远程服务更容易定位问题。