EP01. "MCP 入门:用 Python SDK 打造你的第一个 MCP Server"
🔒 登录后可标记已读- 接 GitHub 这类外部服务,如果不用 MCP,光是 repos、PR、issues、projects 每一类都要自己写 tool schema——
Introduction to Model Context Protocol这门官方课(academy.claude.com,10 节课 + 测验,约 1 小时)就是教怎么用 MCP 省掉这一大堆重复劳动 - MCP(Model Context Protocol)是一层通信协议:Claude 要用工具、要读数据、要跑预建流程,都不用你手写整合代码,交给专门的 MCP Server 处理
- 课程用 Python 官方 SDK,带你从零搭一个「文件管理系统」的 MCP Server + Client,依序涵盖三大组件——Tools(模型自己决定要不要调用)、Resources(App 决定何时抓取)、Prompts(用户主动触发)
- 前置知识:基础 Python、了解
async/await、知道 API 是什么概念
重点内容
MCP 解决的问题
设想一个场景:做一个聊天界面,用户想问 Claude 关于 GitHub 仓库的问题。
- 不用 MCP 的做法:GitHub 功能太多(repos、pull requests、issues、projects……),每一项都要自己写 tool schema、写执行函数、测试、维护,工作量随功能数量线性爆炸
- MCP 的做法:把「定义 tool + 执行 tool」这件事的责任,从你的应用服务器转移给专门的 MCP Server。你不用自己写 GitHub 的 tools,直接用别人(通常是服务商自己)写好的 GitHub MCP Server,里面已经封装好
get_repos()这类标准化工具,直接连 GitHub API
📌 MCP Server 是谁写的?通常服务商会官方发布(例如 AWS 就发布了自己的官方 MCP Server)。它跟直接调用 API 的差别在于:MCP Server 把 tool schema 和执行函数都预先定义好了,你不用自己动手写。它跟「tool use」也不是一回事——MCP Server 负责提供已经定义好的工具,tool use 讲的是 Claude 具体怎么去调用这些工具,两者是互补关系。
架构总览:一次调用怎么在 Client 和 Server 之间跑
MCP Client(在你的应用里)和 MCP Server 之间靠传输层无关的方式通信——本地场景最常见的是标准输入输出(stdio),也支持 HTTP、WebSocket 等网络协议。
两种核心消息往返:
| 消息 | 作用 |
|---|---|
ListToolsRequest / ListToolsResult | Client 问 Server「你有哪些工具」,拿到工具清单 |
CallToolRequest / CallToolResult | Client 请 Server 执行某个工具(带参数),拿到执行结果 |
以「用户问 GitHub 仓库问题」为例,完整走一遍:
- 用户把问题发给你的服务器
- 服务器要先知道有哪些工具可用
- 服务器通过 MCP Client 去问
- MCP Client 发
ListToolsRequest给 MCP Server - Claude 收到用户问题 + 可用工具清单
- Claude 决定要用哪个工具
- 服务器请 MCP Client 执行这个工具
- MCP Client 发
CallToolRequest给 MCP Server(Server 内部去调 GitHub API) - GitHub 把数据回给 Server
- 结果包成
CallToolResult往回传 - Claude 根据结果组织最终答案
- 用户收到回复
📌 MCP Client 把这整条链路的复杂度都包起来了——写应用的人只需要专心处理业务逻辑,不用管服务器通信细节。
动手写 Tools:MCP Server 端
课程用 Python 官方 SDK 的 FastMCP 类,靠装饰器 + Python 类型提示定义工具,不用手写 JSON schema:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("DocumentMCP", log_level="ERROR")
示范用的文档存储(一个简单字典):
docs = {
"deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
"report.pdf": "The report details the state of a 20m condenser tower.",
"financials.docx": "These financials outline the project's budget and expenditures",
"outlook.pdf": "This document presents the projected future performance of the system",
"plan.md": "The plan outlines the steps for the project's implementation.",
"spec.txt": "These specifications define the technical requirements for the equipment"
}
读文档的工具:
@mcp.tool(
name="read_doc_contents",
description="Read the contents of a document and return it as a string."
)
def read_document(
doc_id: str = Field(description="Id of the document to read")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
编辑文档的工具:
@mcp.tool(
name="edit_document",
description="Edit a document by replacing a string in the documents content with a new string."
)
def edit_document(
doc_id: str = Field(description="Id of the document that will be edited"),
old_str: str = Field(description="The text to replace. Must match exactly, including whitespace."),
new_str: str = Field(description="The new text to insert in place of the old text.")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
docs[doc_id] = docs[doc_id].replace(old_str, new_str)
用这套 SDK 的好处:不用手写 JSON schema;类型提示自动做参数校验;Field(description=...) 里的说明文字帮 Claude 理解每个参数该怎么填;Python 的 raise/ValueError 直接就是错误处理;装饰器一贴,工具自动注册,不用另外维护清单。
用 Inspector 测试 Server(不用先接完整 App)
MCP Inspector 是 Python SDK 自带的浏览器端调试工具,不用搭好整个应用就能单独测 Server。
- 启动:
mcp dev mcp_server.py,本地跑在http://127.0.0.1:6274 - 点 Connect 按钮,状态从 disconnected 变成可用
- 切到 Tools 分页,点 List Tools 看到所有已注册工具
- 选一个工具,填测试参数(比如文档 id),点 Run Tool
- 面板直接显示执行结果和成功与否
💡 Inspector 会保留 Server 状态,可以连续测(比如先编辑文档、再读一次确认改动生效),适合边写边验证复杂的工具串联逻辑。
把 Server 接进 Client
Client 端由两部分组成:自己写的 MCP Client 类 + SDK 提供的 ClientSession(负责管理连接,要留意资源要正确释放)。
Client 要做两件事:把工具清单转给 Claude、Claude 要用工具时负责去执行。
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.tools
async def call_tool(self, tool_name: str, tool_input: dict) -> types.CallToolResult | None:
return await self.session().call_tool(tool_name, tool_input)
测试:uv run mcp_client.py 先确认连接和工具清单正常,再跑 uv run main.py 测完整流程(Client 接上应用逻辑,Claude 能实际调用工具)。
Resources:只读数据怎么定义
Resources 的作用类似 HTTP 的 GET 请求——暴露只读数据,不像 Tools 要 Claude 自己判断该不该调用。
典型场景:让用户打 @文档名 引用一份文档。这需要两个操作:列出可选文档(给自动补全用)、拿到某份文档的具体内容。用户一旦 @ 了某份文档,内容直接塞进发给 Claude 的 prompt 里,Claude 不用再另外调工具去拿。
请求流程:Client 发 ReadResourceRequest(带一个 URI 表示要哪份资源)→ Server 处理这个 URI、跑对应函数 → Server 用 ReadResourceResult 把结果传回来。
Direct Resources(静态 URI,不带参数):
@mcp.resource(
"docs://documents",
mime_type="application/json"
)
def list_docs() -> list[str]:
return list(docs.keys())
Templated Resources(URI 里带参数,SDK 自动解析成关键字参数传进函数):
@mcp.resource(
"docs://documents/{doc_id}",
mime_type="text/plain"
)
def fetch_doc(doc_id: str) -> str:
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
📌 mime_type 一定要设对(application/json、text/plain、application/pdf 等),告诉 Client 该怎么解析这份数据;SDK 会自动帮你序列化返回值,不用手动转 JSON。
测试同样用 uv run mcp dev mcp_server.py 打开 Inspector,这次看 Resources(静态资源)和 Resource Templates(带参数的资源)两个分区,Templated 的要先填参数才能测。
Client 端怎么读 Resource
import json
from pydantic import AnyUrl
async def read_resource(self, uri: str) -> Any:
result = await self.session().read_resource(AnyUrl(uri))
resource = result.contents[0]
if isinstance(resource, types.TextResourceContents):
if resource.mimeType == "application/json":
return json.loads(resource.text)
return resource.text
Server 回来的 contents 是个列表,第一项包含实际内容、MIME type、和一些元数据。逻辑很简单:MIME type 是 application/json 就解析成 Python 对象,其他情况直接当纯文字返回。
用户端体验:在 CLI 打 @,自动列出可用资源,方向键选、空格确认,内容直接塞进 prompt,一次性连同问题发给 Claude——中间不用再额外跑一次 tool call。
Prompts:预建的专家级指令
用户当然可以直接跟 Claude 描述想做什么,但如果 MCP Server 的作者已经把某个任务的 prompt 反复测试、打磨过,效果会比用户现场现写好很多——这是 Prompts 这个组件存在的理由。
范例场景:用户打 /format doc_id,触发一个预建好的「重新排版成 Markdown」流程:
@mcp.prompt(
name="format",
description="Rewrites the contents of the document in Markdown format."
)
def format_document(
doc_id: str = Field(description="Id of the document to format")
) -> list[base.Message]:
prompt = f"""
Your goal is to reformat a document to be written with markdown syntax.
The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>
Add in headers, bullet points, tables, etc as necessary. Feel free to add in structure.
Use the 'edit_document' tool to edit the document. After the document has been reformatted...
"""
return [
base.UserMessage(prompt)
]
函数返回的是一串 message 列表,会直接发给 Claude——可以塞多条 user/assistant 消息,做更复杂的多轮铺垫。同样可以先用 Inspector 测试,它会显示变量替换后完整的 prompt 内容,上线前就能确认没写错。
好处:结果稳定一致;把专业经验/边界情况处理编码进 prompt 里;多个地方都能重复用同一份;要改进只需要改 Server 这一处,所有用它的 Client 都同步受益。
Client 端怎么用 Prompts
async def list_prompts(self) -> list[types.Prompt]:
result = await self.session().list_prompts()
return result.prompts
async def get_prompt(self, prompt_name, args: dict[str, str]):
result = await self.session().get_prompt(prompt_name, args)
return result.messages
例如 format_document 这个 prompt 需要 doc_id 参数,Client 传 {"doc_id": "plan.md"} 进去,服务器把这个值插进模板里再回传。用户端打 / 就能看到所有可用 prompt 当作指令来选,选中后系统还会引导补上需要的参数(比如选哪份文档),组装完整后一次发给 Claude。
三大组件怎么选
| 组件 | 谁决定要不要用 | 适合的场景 |
|---|---|---|
| Tools | Claude 自己(模型判断这个任务需不需要调用) | 扩展 Claude 能做的事——比如帮它接一个能实际算平方根的 JS 执行器 |
| Resources | 你的应用代码(App 决定什么时候抓、怎么用) | 填充 UI 里的自动补全、给对话补充背景资料 |
| Prompts | 用户(点按钮、选菜单、打 slash command 主动触发) | 让用户一键跑一套已经调好、可重复用的高质量流程 |
学完你会
- ✅ 讲得清 MCP 为什么存在、它怎么省掉手写整合代码的功夫
- ✅ 用 Python SDK 的装饰器写出 Tools、Resources(含 Direct 和 Templated 两种)、Prompts 三种组件
- ✅ 用 MCP Inspector(
mcp dev)独立调试 Server,不用先搭好整个 App - ✅ 在 Client 端接好
list_tools/call_tool/read_resource/list_prompts/get_prompt,让 Claude 真正用上这些工具 - ✅ 遇到新需求,能判断该做成 Tool、Resource 还是 Prompt
常见错误
- ❌ 沿用旧习惯手写 JSON schema 定义工具——Python SDK 的装饰器 + 类型提示 +
Field(description=...)已经帮你自动生成,不用再手刻一遍 - ❌ 把该由 App 主动抓取的数据(比如自动补全清单)做成 Tool,指望 Claude 自己想到要调用——这种场景应该用 Resource,由你的代码决定什么时候取用,不是丢给模型猜
- ❌ 定义 Resource 时漏填或填错
mime_type——Client 靠这个字段判断内容该当 JSON 解析还是当纯文字处理,填错会导致 Client 端解析出错 - ❌ 把 Prompt 当成「省事的固定模板」随便写写——它的价值在于经过反复测试、覆盖边界情况,写得越粗糙,跟用户自己现场描述需求的差距就越小,等于白做
- 💡 还没搭好完整应用之前,先用
mcp dev mcp_server.py开 Inspector 单独测 Server 端的 Tools/Resources/Prompts,比每次都跑一整套 Client 快很多
Sources
官方文档:
- Introduction to Model Context Protocol(课程总览)— https://academy.claude.com/courses/introduction-to-model-context-protocol
- Introducing MCP — https://academy.claude.com/courses/introduction-to-model-context-protocol/introducing-mcp
- MCP clients — https://academy.claude.com/courses/introduction-to-model-context-protocol/mcp-clients
- Defining tools with MCP — https://academy.claude.com/courses/introduction-to-model-context-protocol/defining-tools-with-mcp
- The server inspector — https://academy.claude.com/courses/introduction-to-model-context-protocol/the-server-inspector
- Implementing a client — https://academy.claude.com/courses/introduction-to-model-context-protocol/implementing-a-client
- Defining resources — https://academy.claude.com/courses/introduction-to-model-context-protocol/defining-resources
- Accessing resources — https://academy.claude.com/courses/introduction-to-model-context-protocol/accessing-resources
- Defining prompts — https://academy.claude.com/courses/introduction-to-model-context-protocol/defining-prompts
- Prompts in the client — https://academy.claude.com/courses/introduction-to-model-context-protocol/prompts-in-the-client
- MCP review — https://academy.claude.com/courses/introduction-to-model-context-protocol/mcp-review