饭团追书免费版
67.97MB · 2025-11-15
假设你要做一个聊天界面,用户可以问Claude:"我的GitHub上有哪些开着的PR?"
没有MCP时你需要做什么:
GitHub的功能极其庞大——仓库、PR、Issue、Projects...你得写几十上百个工具定义。更要命的是,GitHub API一更新,你的代码就得跟着改。
MCP把工具定义和执行逻辑都搬到了专门的MCP Server上。 你的应用只需要连接这个Server,就能获得所有工具,不用自己写一行集成代码。
传统方式:你的应用 → 写100个工具 → 调用GitHub API
MCP方式:你的应用 → MCP Client → MCP Server(已经实现好的100个工具) → GitHub API
关键优势:
用户提问
↓
你的应用(MCP Client)
↓
MCP Server(工具集合)
↓
外部服务(GitHub/Notion/数据库...)
MCP Client 是你的应用代码,负责和MCP Server通信。
MCP Server 是独立的服务进程,封装了某个领域的工具(比如GitHub专属Server、AWS专属Server)。
通信协议 支持多种传输方式:标准输入输出(stdio)、HTTP、WebSocket等。最常见的是本地运行时用stdio。
以"我有哪些仓库?"这个问题为例:
ListToolsRequestget_repos、list_issues等get_repos工具"CallToolRequest给ServerCallToolResult,应用再发给Claude这个流程看起来步骤很多,但每个组件职责清晰。MCP Client帮你抽象掉了和Server通信的复杂性,你只需要调用简单的list_tools()和call_tool()方法。
理论讲完,现在写代码。我们要实现一个内存文档管理系统,支持读取和编辑文档。
使用官方Python SDK,创建Server只需一行:
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",
"outlook.pdf": "This document presents the projected future performance",
"plan.md": "The plan outlines the steps for implementation.",
"spec.txt": "Technical requirements for the equipment"
}
用装饰器定义工具,SDK会自动生成Claude能理解的JSON Schema:
from pydantic import Field
@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装饰器自动注册工具Field提供参数描述,帮助Claude理解每个参数的用途str)提供自动校验实现简单的查找替换功能:
@mcp.tool(
name="edit_document",
description="Edit a document by replacing a string with a new string."
)
def edit_document(
doc_id: str = Field(description="Id of the document to edit"),
old_str: str = Field(description="Text to replace. Must match exactly."),
new_str: str = Field(description="New text to insert.")
):
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)
return f"Successfully replaced '{old_str}' with '{new_str}' in {doc_id}"
对比传统方式: 如果不用MCP,你需要手写这样的JSON Schema:
{
"name": "edit_document",
"description": "Edit a document...",
"input_schema": {
"type": "object",
"properties": {
"doc_id": {"type": "string", "description": "..."},
"old_str": {"type": "string", "description": "..."},
"new_str": {"type": "string", "description": "..."}
},
"required": ["doc_id", "old_str", "new_str"]
}
}
用SDK后,这些全部自动生成。
写完代码后怎么测试?MCP SDK内置了一个浏览器调试工具。
mcp dev mcp_server.py
访问http://127.0.0.1:6274,你会看到一个Web界面。
read_doc_contents工具doc_id为report.pdf"The report details the state of a 20m condenser tower."可以连续测试多个工具:
edit_document修改文档read_doc_contents验证修改是否生效Inspector会保持Server状态,所以修改会持久化(在内存中)。
开发建议: 在写复杂工具时,先在Inspector里测试边界条件(比如传入不存在的doc_id),确保错误处理正确。
Server写完了,现在需要一个Client来使用这些工具。
Client包含两部分:
为什么要封装?因为Session需要手动管理资源(连接的打开和关闭),封装后可以用Python的async with自动处理。
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.tools
这个方法返回Server提供的所有工具定义,你会把这些工具发给Claude。
async def call_tool(
self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
return await self.session().call_tool(tool_name, tool_input)
当Claude决定调用某个工具时,你用这个方法转发请求给Server。
Client文件通常包含一个测试入口:
if __name__ == "__main__":
import asyncio
async def test():
async with MCPClient() as client:
tools = await client.list_tools()
print(f"Found {len(tools)} tools:")
for tool in tools:
print(f" - {tool.name}: {tool.description}")
asyncio.run(test())
运行:
uv run mcp_client.py
你应该看到:
Found 2 tools:
- read_doc_contents: Read the contents of a document...
- edit_document: Edit a document by replacing a string...
Tools是给Claude用的,Resources是给你的应用代码用的。典型场景是UI自动补全或注入上下文。
假设你要实现一个@文档名的提及功能:
@时,自动补全显示所有可用文档这需要两个操作:
@mcp.resource(
"docs://documents",
mime_type="application/json"
)
def list_docs() -> list[str]:
return list(docs.keys())
URI是固定的docs://documents,返回文档ID列表。
@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]
URI包含参数{doc_id},SDK会自动解析并传给函数。
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
工作流程:
client.read_resource("docs://documents")ReadResourceRequest给Serverlist_docs()函数["deposition.md", "report.pdf", ...]Prompts是给用户用的高质量模板。用户触发后,直接发送精心设计的指令给Claude。
用户当然可以自己输入"把report.pdf转成markdown格式",但效果可能不理想。作为MCP Server作者,你可以提供一个经过反复测试的优化Prompt,处理各种边缘情况。
这样用户只需要点一个按钮或输入/format report.pdf,就能获得最佳结果。
from mcp.server.fastmcp import base
@mcp.prompt(
name="format",
description="Rewrites the contents of a 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 headers, bullet points, tables, etc as necessary. Use the 'edit_document' tool to modify the document. After reformatting, return a summary of changes made.
"""
return [base.UserMessage(prompt)]
关键设计:
async def list_prompts(self) -> list[types.Prompt]:
result = await self.session().list_prompts()
return result.prompts
async def get_prompt(self, prompt_name: str, args: dict[str, str]):
result = await self.session().get_prompt(prompt_name, args)
return result.messages
用户输入/format plan.md时:
/format命令client.get_prompt("format", {"doc_id": "plan.md"})read_doc_contents和edit_document完成任务MCP提供了三种机制,分别服务于不同的控制主体:
谁决定? Claude自主决定何时调用
适用场景:
示例: 用户问"3的平方根是多少?",Claude自动调用execute_javascript工具计算。
谁决定? 你的应用代码决定何时获取
适用场景:
示例: 用户输入@,应用调用Resource获取文档列表,显示在下拉菜单。
谁决定? 用户通过按钮、命令触发
适用场景:
示例: 用户点击"格式化文档"按钮,应用获取format Prompt并发送给Claude。
现在把所有部分组合起来,看一个完整的交互流程。
用户输入: "report.pdf里写了什么?"
用户输入: @report.pdf "总结这个文档"
用户输入: /format plan.md
Tools和Resources中抛出的异常会被自动捕获并返回给Client:
@mcp.tool(name="read_doc")
def read_document(doc_id: str):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
Claude会收到错误信息,并可能重试或换个策略。
使用Pydantic的Field可以提供运行时校验:
from pydantic import Field, validator
@mcp.tool(name="edit_doc")
def edit_document(
doc_id: str = Field(description="...", min_length=1),
old_str: str = Field(description="...", min_length=1),
new_str: str = Field(description="...")
):
# min_length会自动校验,不满足条件会拒绝请求
...
所有Client方法都是异步的,需要在async函数中调用:
async def main():
async with MCPClient() as client:
tools = await client.list_tools()
result = await client.call_tool("read_doc", {"doc_id": "report.pdf"})
使用async with确保Session正确关闭:
class MCPClient:
async def __aenter__(self):
# 创建并启动Session
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 关闭Session,释放资源
pass
直接调用API: 你需要自己写工具定义、处理认证、管理请求。
使用MCP: 工具定义已经写好,认证由Server处理,你只需要连接并使用。
类比: 就像你不会自己实现HTTP协议去访问网站,而是用浏览器或requests库。MCP就是外部服务集成的"标准库"。
通常由服务提供商官方或社区开发者编写。比如AWS可能会发布官方MCP Server,包含所有AWS服务的工具。也有很多开源MCP Server可以直接用。
可以。你的Client可以同时连接GitHub Server、Notion Server、AWS Server等,Claude可以跨Server调用工具。
官方SDK支持Python和TypeScript。其他语言可以实现MCP协议(基于JSON-RPC 2.0)。
MCP基于本地进程通信(stdio)或网络协议,延迟很低。工具调用的耗时主要取决于实际API(比如GitHub API的响应时间)。
MCP的核心价值在于标准化和复用:
如果你要构建AI应用,并且需要让Claude访问外部服务,MCP是目前最优雅的解决方案。它不会限制你的灵活性,反而通过标准化让你的应用更易扩展。
相关资源: