逐步讲解用官方 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 开发者的影响,因此你可以现在开始构建,同时为迁移做好规划。
创建项目、安装 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;整体结构相同,只有语法不同。
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 步再次讨论这一点。
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 中保持行为可预测的原因。
实际使用中,你会用到两种 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-Id header 被移除,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 复用,而不绑定于某个特定应用。
需要测试契约的两端——发现(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 限制为最小权限,并让不可逆操作必须经过人工确认。这部分往往被快速入门指南略过,但生产环境绝不能忽视。
上面的所有内容都以 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/initialized handshake,使 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-Id header 也随之移除。
协议层没有任何机制用来取代 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 SDK,创建一个具名 server 对象,将 tools 声明为带类型的函数(类型提示会成为输入 schema),根据需要暴露 resources 和 prompts,通过 stdio 或 Streamable HTTP 运行 server,然后连接 Claude 等 MCP client,测试发现和执行能力。一个最小 server 只有十行左右;真正的工作在于决定要暴露哪些能力,并验证每一项输入。
任何拥有 MCP SDK 的语言都可以。Python 和 TypeScript 的生态最成熟、文档也最完善,其他多种语言同样拥有 SDK。MCP 是一种协议,而不是一个库,因此以一种语言编写的 server 可以与任何其他语言编写的 client 互操作——选择现有 tools 和 API 所使用的语言即可。
使用 uv add "mcp[cli]" 安装 SDK,然后创建一个 FastMCP("name") server。使用 @mcp.tool() 装饰带类型的操作函数,使用 @mcp.resource("uri") 暴露可读取的数据,使用 @mcp.prompt() 定义模板,然后调用 mcp.run(transport="stdio")。类型提示和 docstring 会自动成为 tool 的 schema 和描述。SDK 会持续演进,因此请对照最新 SDK 文档确认函数签名。
对于本地 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。
使用 SDK 自带的 MCP Inspector——mcp dev server.py——列出 server 公布的能力,并在不涉及任何模型的情况下使用测试参数调用 tools。需要同时验证发现过程(client 能看到你的能力)和执行过程(调用能够正确运行并返回结果)。然后连接 Claude 之类的真实 client,对相同路径进行端到端测试。
要把 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。
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。