第 1 课

第一个 MCP 服务器:工具、资源、提示词与 Inspector

用 uv 安装 mcp 2.3.0,写出同时含工具、资源模板、提示词的 server.py,先用内存 Client 把三种原语各调一次,再用 MCP Inspector 点一遍,最后改用 Streamable HTTP 运行并读出服务器声明的能力。

图文20 分钟Claude Code 官方文档 ↗

课程目录第 1 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课依据: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 的能力字典
  • 你能说出工具、资源、提示词分别由谁决定使用

本课资料

适用环境

  • macOS
  • Windows
  • Linux
  • 网页
  • iOS

官方文档

Claude Code ↗