MCP 服务器开发完全指南:从零到工作原型
逐步讲解用官方 SDK(Python/TypeScript)构建 MCP 服务器的方法,包括声明工具、资源、提示,连接 Claude 等客户端测试。
完整中文译文
要构建一个 MCP server:安装官方 MCP SDK,使用带类型的输入声明工具,可选择暴露 resources 和 prompts,通过 stdio 或 HTTP 运行 server,然后连接 Claude 之类的 MCP client 并进行测试。一个最小的 Python server 只需十行左右;真正的工作在于决定要暴露哪些能力,并验证每一项输入。
本文专注于实际构建。如果你想了解 MCP 是什么、它的三种原语,以及它与 API 有何区别,请先阅读“什么是 Model Context Protocol”——本文假设你已经了解这些内容,将直接进入代码。
要在本地运行一个 server,你只需要准备很少的东西:
-
一门拥有官方 SDK 的编程语言。Python 和 TypeScript 的生态最成熟;其他语言也实现了相同的协议。本指南使用 Python SDK(这是大多数人会搜索的第二选择),并会说明 TypeScript SDK 中对应的实现。
-
Python 3.10 或更高版本,以及用于管理环境的 uv(推荐)或 pip。
-
一个用于测试的 MCP client——Claude Desktop,或者 SDK 自带的 MCP Inspector。构建或运行 server 本身不需要云服务凭据。
从概念上讲,一个 server 会暴露三类能力——tools(模型可调用的函数)、resources(可读取的数据)和 prompts(可复用的模板)。下面将按照这个顺序逐步添加它们。SDK 的确切签名会不断演进,因此请把这些代码片段视为当前的写法,并在正式发布前查阅最新文档。
本文面向哪个规范修订版?这里的代码以 MCP 2025-11-25 修订版为目标——规范的版本页面仍将其称为当前协议版本。2026-07-28 修订版已经发布,并对线上传输格式进行了大幅重构。基于 2025-11-25 构建的 server 目前仍然符合规范;下文会说明新修订版对 server 开发者的影响,因此你可以现在开始构建,同时为迁移做好规划。
第 1 步:搭建 server 骨架
创建项目、安装 SDK,并编写一个能够运行的最小 server。使用 uv:
uv init weather
cd weather
uv venv
source .venv/bin/activate
# Install the MCP SDK (with the CLI extras) plus anything your tools need
uv add "mcp[cli]" httpx
然后创建 server.py,其中包含 server 对象和入口点。FastMCP 是 Python 的高层 API:你为 server 命名,然后通过某种 transport 运行它。
from mcp.server.fastmcp import FastMCP
# Name the server; clients see this name on connect
mcp = FastMCP("weather")
if __name__ == "__main__":
# stdio is the default local transport
mcp.run(transport="stdio")
这时它已经可以运行了——只不过还没有暴露任何能力。在 TypeScript SDK 中,对应做法是从 @modelcontextprotocol/sdk 创建一个 McpServer,并将其连接到某种 transport;整体结构相同,只有语法不同。
第 2 步:定义一个 tool
tool 是模型可以自行决定是否调用的函数。在 Python SDK 中,你只需装饰一个普通的带类型函数:类型提示会转换为输入 JSON schema,而 docstring 会成为模型用于判断何时调用它的描述。
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers and return the sum."""
return a + b
实际的 tools 会封装一些有用的能力——数据库查询、内部 API 或文件操作。handler 本质上只是一个函数,因此你可以在这里调用现有服务,并返回模型可以使用的结果:
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get the weather forecast for a location."""
data = await call_weather_api(latitude, longitude)
return format_forecast(data)
这里有两个架构层面的重点:schema 是模型负责填写的契约,因此参数名称和描述必须准确;函数会以当前进程拥有的权限运行,因此应将其权限限制在完成任务所需的最小范围。我们会在第 5 步再次讨论这一点。
第 3 步:添加 resources 和 prompts
tools 用于执行操作;resources 暴露模型可以读取的数据,而 prompts 则是可复用的模板。并非每个 server 都需要同时支持这三种能力——根据你的使用场景添加即可。
resource 通过 URI 寻址。它可以是静态资源,也可以是由 client 填写参数的模板:
@mcp.resource("config://app")
def get_config() -> str:
"""Static configuration the model can read."""
return "weather-app v1.0"
@mcp.resource("weather://{city}/current")
def current_weather(city: str) -> str:
"""Current conditions for a named city."""
return load_conditions(city)
prompt 是由 server 发布的参数化模板,让常见工作流只需编写一次,而不必在每个 client 中重复编写:
@mcp.prompt()
def summarize_forecast(city: str) -> str:
return f"Summarize the weather outlook for {city} in two sentences."
这种区分是有意为之:client 可以向用户展示 resources 和 prompts,让用户将其引入上下文或从菜单中选择,同时允许模型自主调用 tools。将三者分开,正是 server 能够在不同 client 中保持行为可预测的原因。
第 4 步:运行并连接 server
实际使用中,你会用到两种 transport:
-
stdio——server 作为由 client 启动的本地子进程运行。这是桌面 client 和本地开发的默认方式:
mcp.run(transport="stdio")。 -
Streamable HTTP——server 作为 Web 服务运行,远程 client 可以连接它,适用于托管或共享 server:
mcp.run(transport="streamable-http")。这是 2026-07-28 修订版改动最大的 transport:协议级 session 和Mcp-Session-Idheader 被移除,Mcp-Method、Mcp-Name和MCP-Protocol-Version成为必需的请求 header。你的 2025-11-25 server 仍可继续工作——在规划远程部署之前,请先了解 2026-07-28 修订版带来的变化。
要将本地 stdio server 连接到 Claude Desktop,需要在配置中指定用于启动 server 的命令。最快的方法是让 SDK 自动完成安装:
uv run mcp install server.py
也可以在 claude_desktop_config.json 中手动编写配置项(请使用绝对路径):
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/weather", "run", "server.py"]
}
}
}
重启 client 后,你的 tools、resources 和 prompts 就会显示出来。此时,这个 server 已经成为所有连接到它的 Agent 的工具/操作层的一部分——它可以被任何兼容 MCP 的 client 复用,而不绑定于某个特定应用。
第 5 步:测试并保护 server
需要测试契约的两端——发现(client 能否看到你提供的能力?)和执行(调用能否正确运行并返回结果?)。SDK 自带了专门用于此目的的 Inspector,不需要配置任何 client:
uv run mcp dev server.py
它会打开 MCP Inspector。你可以在任何模型接触这些能力之前,列出 server 公布的 tools、resources 和 prompts,并使用测试参数调用它们。
接下来,要把 server 当作安全关键组件来对待,因为它代表一个不受信任的调用方执行操作——模型提供的参数可能是错误的、格式不合法的,也可能受到模型刚刚读取的 prompt injection 内容操纵。以下原则没有妥协余地:
-
验证每一项输入。在执行操作前,根据 schema 和你自己的约束检查参数。按照定义,模型的输出就是不受信任的输入。
-
最小权限。将每个 tool 的凭据和访问范围限制在完成任务所需的最小范围,这样即使调用出现混乱或被劫持,也无法访问超出预期的内容。
-
对不可逆操作引入 Human-in-the-loop。任何无法轻易撤销的操作——删除、付款、向外发送消息——都必须获得明确批准。
tool 边界就是攻击面。模型一旦能够执行某项操作,遭到操纵的输入同样能触发这项操作。根据 schema 和你自己的规则验证每个 tool 的输入,将每个 tool 限制为最小权限,并让不可逆操作必须经过人工确认。这部分往往被快速入门指南略过,但生产环境绝不能忽视。
2026-07-28 修订版给 server 开发者带来了哪些变化
上面的所有内容都以 2025-11-25 修订版为目标。2026-07-28 修订版已经发布,而首先值得理解的是“已发布”与“当前版本”之间的差异:规范仓库中的 schema 常量为 LATEST_PROTOCOL_VERSION = "2026-07-28",但文档站点的版本页面仍逐字写着:“The current protocol version is 2025-11-25”。只支持旧修订版的 server 目前仍然符合规范。迁移时将发生以下变化:
-
handshake 消失了。该修订版移除了
initialize/notifications/initializedhandshake,使 MCP 变为无状态协议:“Every request now carries its protocol version and client capabilities in _meta”,并且 server “MUST NOT rely on prior requests over the same connection”。对于 Streamable HTTP,协议级 session 和Mcp-Session-Idheader 也随之移除。 -
协议层没有任何机制用来取代 session。跨调用状态会变成显式状态,即由 server 创建 handle,并将其作为普通 tool 参数传递。如果你的某个 tool 依赖 client 先前在连接中建立的内容,这项依赖就必须成为该 tool schema 中的参数。
-
你必须实现
server/discover。server 必须实现这个新 RPC;client 可以选择先调用它,也可以直接调用任何其他 RPC。无论特定 client 是否使用它,这都是 server 端必须履行的义务。 -
结果需要携带缓存提示。缓存页面属于规范性内容:对于六种操作——
server/discover、tools/list、prompts/list、resources/list、resources/templates/list和resources/read——server 必须在完整结果中包含ttlMs和cacheScope。如果省略ttlMs,client 会将其视为0,因此后果是无法利用缓存,而不是调用失败。所有结果还新增了必需的resultType。 -
一些方法被移除,另一些只是被弃用。
ping、logging/setLevel和notifications/roots/list_changed被彻底移除;HTTP GET stream 以及resources/subscribe、resources/unsubscribe被subscriptions/listen取代;tasks 则从核心规范移入扩展。Roots、Sampling 和 Logging 只是被弃用,并未移除——它们仍属于规范的一部分,也仍然可以正常工作。 -
JSON-RPC 错误码被重新编号。
HeaderMismatch从-32001改为-32020,MissingRequiredClientCapability从-32003改为-32021,UnsupportedProtocolVersion从-32004改为-32022。按照旧版说明编写的错误处理代码会发送错误的错误码。
这些变化都不会让你本周发布的 server 过时。该修订版发布了兼容性矩阵,其中“双时代 client 访问旧版 server”这一行的结果是“Works”。被弃用的功能“remain fully functional during the deprecation window”,而 Roots、Sampling、Logging 和动态 client 注册各自公布的最早移除时间都是“First revision released on or after 2027-07-28”——至少还有十二个月,并且这个日期只是时间下限,而不是既定计划。
如果你同时维护 client 端,那么向后兼容探测方式会因 transport 而异,这也是大多数文章容易一笔带过的细节。对于 stdio,你需要使用 server/discover 进行探测,并在遇到任何无法识别的现代错误时回退。对于 Streamable HTTP,则不存在这样的探测:你需要尝试发送现代请求,并在回退之前检查 400 Bad Request 的响应正文,因为现代 server 在遇到版本和能力错误时同样会返回 400。读取响应正文是整个机制不可或缺的一半——丢弃正文的 client 会在不知不觉中发生降级。你需要牢牢记住这种不对称性:旧版 server 可以继续工作,旧版 client 却不行。兼容性矩阵中,“旧版 client 对接现代 server”这一行的结果是“Fails”,因为旧版 client “no fall-forward mechanism”。
接下来,自然的一步是将这个 server 组合进一个完整的 Agent:关于工具/操作层如何与编排、memory 和 retrieval 配合,可以参阅 Agentic AI 架构;更全面的技能体系则可以在课程中找到。
常见问题
如何构建 MCP server?
安装官方 MCP SDK,创建一个具名 server 对象,将 tools 声明为带类型的函数(类型提示会成为输入 schema),根据需要暴露 resources 和 prompts,通过 stdio 或 Streamable HTTP 运行 server,然后连接 Claude 等 MCP client,测试发现和执行能力。一个最小 server 只有十行左右;真正的工作在于决定要暴露哪些能力,并验证每一项输入。
可以使用什么语言构建 MCP server?
任何拥有 MCP SDK 的语言都可以。Python 和 TypeScript 的生态最成熟、文档也最完善,其他多种语言同样拥有 SDK。MCP 是一种协议,而不是一个库,因此以一种语言编写的 server 可以与任何其他语言编写的 client 互操作——选择现有 tools 和 API 所使用的语言即可。
如何使用 Python 构建 MCP server?
使用 uv add "mcp[cli]" 安装 SDK,然后创建一个 FastMCP("name") server。使用 @mcp.tool() 装饰带类型的操作函数,使用 @mcp.resource("uri") 暴露可读取的数据,使用 @mcp.prompt() 定义模板,然后调用 mcp.run(transport="stdio")。类型提示和 docstring 会自动成为 tool 的 schema 和描述。SDK 会持续演进,因此请对照最新 SDK 文档确认函数签名。
如何将 MCP server 连接到 Claude?
对于本地 stdio server,在 Claude Desktop 的 claude_desktop_config.json 中,将其添加到 mcpServers 下,并通过 command 和 args 指定 server 的启动方式——也可以运行 mcp install server.py,让 SDK 自动写入配置项。重启 client 后,它就会发现你的 tools、resources 和 prompts。远程 server 则使用 Streamable HTTP transport。
如何测试 MCP server?
使用 SDK 自带的 MCP Inspector——mcp dev server.py——列出 server 公布的能力,并在不涉及任何模型的情况下使用测试参数调用 tools。需要同时验证发现过程(client 能看到你的能力)和执行过程(调用能够正确运行并返回结果)。然后连接 Claude 之类的真实 client,对相同路径进行端到端测试。
如何保护 MCP server?
要把 server 当成代表不受信任调用方运行的代码。根据 schema 和你自己的约束验证每个 tool 的输入,绝不能信任模型提供的参数。应用最小权限原则,确保每个 tool 只能访问它必须访问的内容,并要求不可逆操作必须经过人工批准。具体威胁就是 prompt injection:模型读取的内容可能试图操纵它所调用的 tools。
MCP Python SDK——server 构建步骤、FastMCP API 和 mcp CLI(Inspector / install):modelcontextprotocol.io/docs/develop/build-server 和 github.com/modelcontextprotocol/python-sdk(已于 2026 年 6 月 26 日核验)。
协议、原语和 transports:modelcontextprotocol.io 上的 Model Context Protocol 规范。TypeScript 对应实现:github.com/modelcontextprotocol/typescript-sdk。
安全框架(验证输入、最小权限、Human-in-the-loop、prompt injection)综合自规范的安全指南和 aiArch 课程(tool 的使用与安全集成)。
2026-07-28 修订版——无状态模型、被移除的 handshake 和 sessions、server/discover、重新编号的错误码以及被移除的方法:changelog 和 Streamable HTTP transport。时代分类、兼容性矩阵以及针对不同 transport 的向后兼容探测:versioning and compatibility。必须携带 ttlMs/cacheScope 的六种操作:caching。弃用窗口和最早移除日期:deprecated features。以上内容均已于 2026 年 7 月 28 日核验。
“The current protocol version is 2025-11-25”,引用内容与本次更新日期时页面上的文字一致:modelcontextprotocol.io/docs/2026-07-28/learn/versioning(已于 2026 年 7 月 28 日核验)。
SDK 的确切签名和 transport 选项会持续演进,而且目前同时存在两个规范修订版——正式发布前,请同时检查当前规范和你所针对的修订版。如需更正,请联系:hello@aiarch.dev。
本文最初发布于 aiarch.dev/how-to-build-an-mcp-server,并会在那里持续更新。
不想读长文,只想要骨架?aiarch-templates 提供了 src/lib/ 的接口结构、一个基于阈值触发的 eval stub,以及一个成本模型骨架。它被刻意留空——只固定整体形状,具体实现由你来编写。许可证为 Apache-2.0。
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。