云选科普

用 Python 搭建 MCP Server:从本地工具到云端服务

用 Python FastMCP 写 MCP Server,区分 tool、resource、prompt,并按本地或远程场景选择 stdio 与 Streamable HTTP。

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")

这里有三个容易被忽略的细节:

  1. 类型注解要与真实输入一致。 SDK 会利用函数签名生成参数模式,类型写得过于宽泛会让客户端难以判断应该传什么。
  2. 描述文字要具体。 tool 的名称和 docstring 会影响客户端展示以及模型选择工具的判断,应该说明用途、输入限制和副作用。
  3. 日志不要写到 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.py

Streamable 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 服务。

上线前的检查清单

完成最小示例后,可以按以下顺序验证:

  1. 在隔离环境安装依赖,确认 Server 能正常启动。
  2. 用 MCP Inspector 或目标客户端检查 tools、resources 是否被正确发现。
  3. 为每个 tool 编写正常输入、缺少参数、越权参数和超时场景的测试。
  4. 在没有真实生产凭据的环境中验证下游调用,确认日志不会泄露密钥或个人数据。
  5. 远程部署时先接入 HTTPS、认证、限流和审计,再开放给模型使用。
  6. 为有副作用的操作增加确认机制或审批流,不要让模型凭一次自然语言请求直接执行高风险动作。

MCP Server 的代码量通常不是难点,真正决定可用性的,是工具边界、授权策略和部署方式。先用 stdio 把工具契约跑通,再根据调用范围迁移到 Streamable HTTP,通常比一开始就搭建复杂的远程服务更容易定位问题。

继续浏览

还想继续看,可以再看这些文章

适合想继续在同一主题下横向阅读、对比不同切入角度的用户。