MCP Python SDK
模型上下文协议(Model Context Protocol,MCP)Python SDK 是用于构建 MCP 服务器和客户端的官方 Python 实现。本文将详细介绍如何使用 MCP Python SDK 来创建高效、可扩展的 AI 应用程序。
什么是 MCP?
模型上下文协议(MCP)允许应用程序以标准化方式为大语言模型(LLM)提供上下文,将提供上下文与实际 LLM 交互的关注点分离。MCP 服务器可以:
- 暴露数据:通过资源(Resources)提供数据,类似于 GET 端点
- 提供功能:通过工具(Tools)执行代码或产生副作用,类似于 POST 端点
- 定义交互模式:通过提示(Prompts)提供可重用的 LLM 交互模板
安装
使用 UV(推荐)
# 创建新项目
uv init mcp-server-demo
cd mcp-server-demo
# 添加 MCP 依赖
uv add "mcp[cli]"运行 MCP 开发工具
uv run mcp快速开始
让我们创建一个简单的 MCP 服务器,展示计算器工具和动态数据:
"""
FastMCP 快速开始示例
"""
from mcp.server.fastmcp import FastMCP
# 创建 MCP 服务器
mcp = FastMCP("Demo")
# 添加加法工具
@mcp.tool()
def add(a: int, b: int) -> int:
"""两数相加"""
return a + b
# 添加动态问候资源
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""获取个性化问候语"""
return f"Hello, {name}!"
# 添加提示模板
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""生成问候提示"""
styles = {
"friendly": "请写一个热情友好的问候语",
"formal": "请写一个正式专业的问候语",
"casual": "请写一个随意轻松的问候语",
}
return f"{styles.get(style, styles['friendly'])}给一个名叫 {name} 的人。"
if __name__ == "__main__":
mcp.run()核心概念
服务器生命周期管理
FastMCP 服务器支持生命周期管理,允许在启动时初始化资源:
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from mcp.server.fastmcp import Context, FastMCP
# 模拟数据库类
class Database:
@classmethod
async def connect(cls) -> "Database":
return cls()
async def disconnect(self) -> None:
pass
def query(self) -> str:
return "查询结果"
@dataclass
class AppContext:
db: Database
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
# 启动时初始化
db = await Database.connect()
try:
yield AppContext(db=db)
finally:
# 关闭时清理
await db.disconnect()
# 传递生命周期到服务器
mcp = FastMCP("My App", lifespan=app_lifespan)
# 在工具中访问生命周期上下文
@mcp.tool()
def query_db(ctx: Context) -> str:
"""使用初始化的资源的工具"""
db = ctx.request_context.lifespan_context.db
return db.query()资源(Resources)
资源用于向 LLM 暴露数据,类似于 REST API 中的 GET 端点:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(name="Resource Example")
@mcp.resource("file://documents/{name}")
def read_document(name: str) -> str:
"""根据名称读取文档"""
return f"{name} 的内容"
@mcp.resource("config://settings")
def get_settings() -> str:
"""获取应用设置"""
return """{
"theme": "dark",
"language": "zh-CN",
"debug": false
}"""工具(Tools)
工具让 LLM 能够通过服务器执行操作:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(name="Tool Example")
@mcp.tool()
def sum_numbers(a: int, b: int) -> int:
"""计算两数之和"""
return a + b
@mcp.tool()
def get_weather(city: str, unit: str = "celsius") -> str:
"""获取城市天气"""
return f"{city} 的天气:22°{unit[0].upper()}"结构化输出
工具支持结构化输出,使用类型注解自动验证:
from typing import TypedDict
from pydantic import BaseModel, Field
# 使用 Pydantic 模型
class WeatherData(BaseModel):
temperature: float = Field(description="摄氏度温度")
humidity: float = Field(description="湿度百分比")
condition: str
wind_speed: float
@mcp.tool()
def get_weather_structured(city: str) -> WeatherData:
"""获取结构化天气数据"""
return WeatherData(
temperature=22.5,
humidity=45.0,
condition="晴朗",
wind_speed=5.2,
)
# 使用 TypedDict
class LocationInfo(TypedDict):
latitude: float
longitude: float
name: str
@mcp.tool()
def get_location(address: str) -> LocationInfo:
"""获取位置坐标"""
return LocationInfo(
latitude=39.9042,
longitude=116.4074,
name="北京, 中国"
)上下文(Context)
工具和资源函数可以接收上下文对象,提供 MCP 功能访问:
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession
mcp = FastMCP(name="Context Example")
@mcp.tool()
async def long_running_task(
task_name: str,
ctx: Context[ServerSession, None],
steps: int = 5
) -> str:
"""执行带进度更新的任务"""
await ctx.info(f"开始: {task_name}")
for i in range(steps):
progress = (i + 1) / steps
await ctx.report_progress(
progress=progress,
total=1.0,
message=f"步骤 {i + 1}/{steps}",
)
await ctx.debug(f"完成步骤 {i + 1}")
return f"任务 '{task_name}' 完成"提示(Prompts)
提示是可重用的模板,帮助 LLM 有效与服务器交互:
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
mcp = FastMCP(name="Prompt Example")
@mcp.prompt(title="代码审查")
def review_code(code: str) -> str:
return f"请审查这段代码:\n\n{code}"
@mcp.prompt(title="调试助手")
def debug_error(error: str) -> list[base.Message]:
return [
base.UserMessage("我遇到了这个错误:"),
base.UserMessage(error),
base.AssistantMessage("我来帮你调试。你已经尝试了什么?"),
]图像处理
FastMCP 提供 Image 类自动处理图像数据:
from PIL import Image as PILImage
from mcp.server.fastmcp import FastMCP, Image
mcp = FastMCP("Image Example")
@mcp.tool()
def create_thumbnail(image_path: str) -> Image:
"""从图像创建缩略图"""
img = PILImage.open(image_path)
img.thumbnail((100, 100))
return Image(data=img.tobytes(), format="png")运行服务器
开发模式
使用 MCP Inspector 快速测试和调试:
uv run mcp dev server.py
# 添加依赖
uv run mcp dev server.py --with pandas --with numpy
# 挂载本地代码
uv run mcp dev server.py --with-editable .Claude Desktop 集成
安装到 Claude Desktop:
uv run mcp install server.py
# 自定义名称
uv run mcp install server.py --name "我的分析服务器"
# 环境变量
uv run mcp install server.py -v API_KEY=abc123 -v DB_URL=postgres://...直接执行
用于自定义部署场景:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.tool()
def hello(name: str = "World") -> str:
"""向某人问好"""
return f"Hello, {name}!"
def main():
mcp.run()
if __name__ == "__main__":
main()Streamable HTTP 传输
适用于生产部署:
from mcp.server.fastmcp import FastMCP
# 有状态服务器(维护会话状态)
mcp = FastMCP("StatefulServer")
# 无状态服务器(无会话持久化)
# mcp = FastMCP("StatelessServer", stateless_http=True)
@mcp.tool()
def greet(name: str = "World") -> str:
"""按名称问候某人"""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="streamable-http")高级用法
用户交互(Elicitation)
请求用户提供额外信息:
from pydantic import BaseModel, Field
from mcp.server.fastmcp import Context, FastMCP
class BookingPreferences(BaseModel):
checkAlternative: bool = Field(description="是否想检查其他日期?")
alternativeDate: str = Field(
default="2024-12-26",
description="替代日期 (YYYY-MM-DD)",
)
@mcp.tool()
async def book_table(
date: str,
time: str,
party_size: int,
ctx: Context
) -> str:
"""预订餐桌并检查日期可用性"""
if date == "2024-12-25":
result = await ctx.elicit(
message=f"{date} 没有 {party_size} 人的空桌。想试试其他日期吗?",
schema=BookingPreferences,
)
if result.action == "accept" and result.data:
if result.data.checkAlternative:
return f"[成功] 已预订 {result.data.alternativeDate}"
return "[取消] 未进行预订"
return "[取消] 预订已取消"
return f"[成功] 已预订 {date} {time}"采样(与 LLM 交互)
工具可以通过采样与 LLM 交互:
from mcp.types import SamplingMessage, TextContent
@mcp.tool()
async def generate_poem(topic: str, ctx: Context) -> str:
"""使用 LLM 采样生成诗歌"""
prompt = f"写一首关于 {topic} 的短诗"
result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(type="text", text=prompt),
)
],
max_tokens=100,
)
if result.content.type == "text":
return result.content.text
return str(result.content)OAuth 认证
对于需要访问受保护资源的服务器:
from pydantic import AnyHttpUrl
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.fastmcp import FastMCP
class SimpleTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
# 在此实现实际的令牌验证
pass
# 创建具有认证的 FastMCP 实例
mcp = FastMCP(
"Weather Service",
token_verifier=SimpleTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"),
resource_server_url=AnyHttpUrl("http://localhost:3001"),
required_scopes=["user"],
),
)
@mcp.tool()
async def get_weather(city: str = "北京") -> dict[str, str]:
"""获取城市天气数据"""
return {
"city": city,
"temperature": "22",
"condition": "多云转晴",
"humidity": "65%",
}MCP 客户端
SDK 还提供了用于连接 MCP 服务器的高级客户端接口:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# 创建服务器参数
server_params = StdioServerParameters(
command="uv",
args=["run", "server", "fastmcp_quickstart", "stdio"],
)
async def run_client():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化连接
await session.initialize()
# 列出可用工具
tools = await session.list_tools()
print(f"可用工具: {[t.name for t in tools.tools]}")
# 调用工具
result = await session.call_tool("add", arguments={"a": 5, "b": 3})
print(f"工具结果: {result.content[0].text}")
# 读取资源
resource = await session.read_resource("greeting://World")
print(f"资源内容: {resource.contents[0].text}")
if __name__ == "__main__":
asyncio.run(run_client())最佳实践
- 使用结构化输出:为工具定义明确的返回类型,提供更好的类型安全
- 实现生命周期管理:在服务器启动时初始化资源,在关闭时清理
- 提供丰富的上下文:使用 Context 对象进行日志记录、进度报告和用户交互
- 合理组织资源和工具:将相关功能分组,使用清晰的命名约定
- 错误处理:实现适当的错误处理和用户友好的错误消息
- 性能优化:对于长时间运行的操作,使用进度报告和异步处理