第 1 课
第一个 MCP 服务器:工具、资源、提示词与 Inspector
用 uv 安装 mcp 2.3.0,写出同时含工具、资源模板、提示词的 server.py,先用内存 Client 把三种原语各调一次,再用 MCP Inspector 点一遍,最后改用 Streamable HTTP 运行并读出服务器声明的能力。
课程目录第 1 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课依据:MCP Python SDK 官方文档「Installation」https://py.sdk.modelcontextprotocol.io/get-started/installation/ 、「First steps」https://py.sdk.modelcontextprotocol.io/get-started/first-steps/ 、「Testing」https://py.sdk.modelcontextprotocol.io/get-started/testing/ 、「Running your server」https://py.sdk.modelcontextprotocol.io/run/ (2026-10-25 核对,mcp 2.3.0)。
这一课做完你会得到什么
一个用 uv 管理的 Python 项目,里面的 server.py 同时提供了 MCP 的三种原语:工具、资源和提示词。你会用三种方式确认它能用:不起进程的内存客户端、官方调试界面 MCP Inspector,以及通过 HTTP 连上来的客户端。
先分清三种原语
一个 MCP 服务器只能对外提供三类东西。区分它们的关键是由谁决定使用:
| 原语 | 由谁决定使用 | 典型例子 |
|---|---|---|
| 工具(Tools) | 模型 | 调用 API、写数据库 |
| 资源(Resources) | 宿主应用 | 文件内容、一段 API 返回 |
| 提示词(Prompts) | 用户 | 斜杠命令、菜单里的模板 |
官方给的类比:资源像 HTTP 的 GET,只读取、不改东西;工具像 POST,会做事,可能有副作用;提示词更像用户按名字调用的「保存好的查询」。另外三个词也要分清:宿主是 Claude Code 这类用户直接使用的应用,客户端是宿主里负责讲 MCP 协议的那部分,服务器是你用 SDK 写的程序。
第 1 步:建项目并安装 SDK
SDK 文档要求 Python 3.11 及以上(PyPI 元数据写的是 3.10+,按文档来更稳妥)。官方推荐用 uv 管理项目。没装 uv 的话,先按 https://docs.astral.sh/uv/ 装好。
uv init mcp-demo
cd mcp-demo
uv add "mcp[cli]"
uv run mcp version
对照结果(2026-10-25 实测):
MCP version 2.3.0
[cli] 这个扩展会装上 mcp 命令行工具,后面的 mcp dev、mcp run、mcp install 都靠它。如果你维护的包依赖 mcp 但还没迁到 v2,官方建议先锁定 mcp>=1.28,<2,迁移细节见官方 Migration Guide。
第 2 步:写一个包含三种原语的服务器
在项目文件夹里新建 server.py,内容与官方 First steps 页一致:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.prompt()
def summarize(text: str) -> str:
"""Summarize a piece of text in one sentence."""
return f"Summarize the following text in one sentence:\n\n{text}"
三个装饰器就完成了全部注册:
@mcp.tool()让add成为工具。工具名取函数名,描述取 docstring,参数的 JSON Schema 由类型注解a: int, b: int自动生成。@mcp.resource("greeting://{name}")定义了一个资源模板。URI 里的{name}对应函数参数,所以它出现在「资源模板」列表里,而不是普通资源列表里。@mcp.prompt()让summarize成为提示词,返回的字符串会变成一条role: user的消息。
注意导入路径:服务器用 from mcp.server import MCPServer,客户端用 from mcp import Client,并不存在 from mcp import MCPServer 这种写法。旧教程里的 from mcp.server.fastmcp import FastMCP 是 v1 的写法,本课不再使用。
第 3 步:用内存客户端把三种原语都调一遍
SDK 自带的 Client 可以直接接收服务器对象,在同一个进程里通信,不需要起子进程或开端口,所以最适合拿来自测。新建 try_inmemory.py:
import anyio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
tools = await client.list_tools()
print("工具:", [t.name for t in tools.tools])
print("必填参数:", tools.tools[0].input_schema["required"])
result = await client.call_tool("add", {"a": 1, "b": 2})
print("content:", result.content[0].text)
print("structured_content:", result.structured_content)
templates = await client.list_resource_templates()
print("资源模板:", [t.uri_template for t in templates.resource_templates])
res = await client.read_resource("greeting://World")
print("读资源:", res.contents[0].text)
prompt = await client.get_prompt("summarize", {"text": "MCP 是一个开放协议。"})
msg = prompt.messages[0]
print("提示词:", msg.role, repr(msg.content.text))
if __name__ == "__main__":
anyio.run(main)
uv run python try_inmemory.py
对照结果(2026-10-25 实测):
工具: ['add']
必填参数: ['a', 'b']
content: 3
structured_content: {'result': 3}
资源模板: ['greeting://{name}']
读资源: Hello, World!
提示词: user 'Summarize the following text in one sentence:\n\nMCP 是一个开放协议。'
工具的返回值会出现两次:content 里的文本是给模型看的,structured_content 是给程序用的。int 本身不是 JSON 对象,所以 SDK 把它包成了 {"result": 3}。第 3 课会细讲这一点。
第 4 步:用 MCP Inspector 点一遍
Inspector 是官方的网页调试工具,mcp dev 会通过 npx 下载并启动它。当前版本要求 Node.js 22.19 及以上;Node 20 会出现 EBADENGINE 警告,建议先升级 Node。
uv run mcp dev server.py
对照结果(2026-10-25 实测,token 每次不同):
Starting MCP inspector...
MCP Inspector Web is up and running at:
http://127.0.0.1:6274?MCP_INSPECTOR_API_TOKEN=<每次启动随机生成>
用浏览器打开终端打印的地址,Inspector 会为每种原语显示一个标签页:
- Tools:只有
add一项,表单里有两个必填整数框a和b,填 1 和 2 后调用,结果是3。 - Resources:普通资源列表是空的,
greeting出现在 Resource Templates 下。name填World后读取,得到Hello, World!。 - Prompts:只有
summarize一项,要求填一个text参数,返回一条role: user的消息。
Inspector 通过 stdio 连接服务器:它把 server.py 当作子进程启动,用标准输入输出交换消息,所以整个过程不需要端口。看完后在终端按 Ctrl-C 退出。
第 5 步:改用 HTTP 运行,看看服务器声明的能力
stdio 适合本机,要把服务器部署出去给别人用,就换成 Streamable HTTP。不用改代码,在一个终端运行:
uv run mcp run server.py --transport streamable-http
对照结果(2026-10-25 实测,节选):
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
另开一个终端,在同一文件夹新建 client.py:
import anyio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
caps = client.server_capabilities.model_dump(exclude_none=True)
for name, detail in caps.items():
print(name, detail)
if __name__ == "__main__":
anyio.run(main)
uv run python client.py
对照结果(2026-10-25 实测;官方 First steps 页把同一个字典整行打印,内容一致):
prompts {'list_changed': True}
resources {'subscribe': True, 'list_changed': True}
tools {'list_changed': True}
客户端连上服务器后,服务器会先声明自己支持哪些请求(capabilities),客户端只会请求声明过的那些。MCPServer 支持全部三种原语,所以三项都在。你没有注册补全处理函数,所以这里没有 completions。
两个细节:
- 默认地址是
127.0.0.1:8000,路径是/mcp。直接用浏览器或 curl GET 这个地址,会得到400 Bad Request,这是正常现象,因为 MCP 客户端走的是 POST。 - 老教程里的
sse传输在 2025-03-26 版协议中已被 Streamable HTTP 取代,新项目不要再用。
实验结束后,在第一个终端按 Ctrl-C 停掉服务器。
自查清单
uv run mcp version输出MCP version 2.3.0或更高try_inmemory.py的 7 行输出与对照结果一致- Inspector 的三个标签页都能调用成功
client.py打印出包含prompts、resources、tools的能力字典- 你能说出工具、资源、提示词分别由谁决定使用
