Model Context Protocol (MCP) 的核心概念和能力
类别: MCP LLM 标签: MCP LLM Resource Prompt Tool Transport Sampling Root目录
- Introduction简介
- Core architecture(核心架构)
- Resources(资源)
- Prompts(提示词)
- Tools(工具)
- Sampling(采样)
- Roots(根)
- Transports(传输)
Introduction简介
Model Context Protocol (MCP) 入门
MCP 是一个开放协议,用于标准化应用程序向 LLM 提供上下文的方式。可以将 MCP 视为 AI 应用程序的 USB-C 端口。正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。
为什么选择 MCP?
MCP 帮助您在 LLM 之上构建代理和复杂的工作流程。LLM 经常需要与数据和工具集成,而 MCP 提供了:
- 越来越多的预构建集成,您的 LLM 可以直接插入
- 在 LLM 提供商和供应商之间切换的灵活性
- 在您的基础设施中保护数据的最佳实践
一般架构
MCP 的核心遵循客户端-服务器架构,其中主机应用程序可以连接到多个服务器:
flowchart LR
subgraph "您的计算机"
Host["带有 MCP 客户端的主机<br>(Claude、IDE、工具)"]
S1["MCP 服务器 A"]
S2["MCP 服务器 B"]
S3["MCP 服务器 C"]
Host <-->|"MCP 协议"| S1
Host <-->|"MCP 协议"| S2
Host <-->|"MCP 协议"| S3
S1 <--> D1[("本地<br>数据源 A")]
S2 <--> D2[("本地<br>数据源 B")]
end
subgraph "互联网"
S3 <-->|"Web APIs"| D3[("远程<br>服务 C")]
end
- MCP 主机:希望通过 MCP 访问数据的程序,如 Claude Desktop、IDE 或 AI 工具
- MCP 客户端:维护与服务器 1:1 连接的协议客户端
- MCP 服务器:轻量级程序,每个程序通过标准化的模型上下文协议公开特定的功能
- 本地数据源:您的计算机的文件、数据库和服务,MCP 服务器可以安全地访问
- 远程服务:通过互联网(例如,通过 API)可用的外部系统,MCP 服务器可以连接到
Core architecture(核心架构)
了解 MCP 如何连接客户端、服务器和 LLM
模型上下文协议 (MCP) 构建于灵活、可扩展的架构之上,从而实现 LLM 应用程序和集成之间的无缝通信。本文档涵盖了核心架构组件和概念。
概述
MCP 遵循客户端-服务器架构,其中:
- 主机 是启动连接的 LLM 应用程序(如 Claude Desktop 或 IDE)
- 客户端 在主机应用程序内与服务器保持 1:1 连接
- 服务器 向客户端提供上下文、工具和提示
flowchart LR
subgraph "主机"
client1[MCP 客户端]
client2[MCP 客户端]
end
subgraph "服务器进程"
server1[MCP 服务器]
end
subgraph "服务器进程"
server2[MCP 服务器]
end
client1 <-->|传输层| server1
client2 <-->|传输层| server2
核心组件
协议层
协议层处理消息成帧、请求/响应链接和高级通信模式。
TypeScript
class Protocol<Request, Notification, Result> {
// 处理传入的请求
setRequestHandler<T>(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise<Result>): void
// 处理传入的通知
setNotificationHandler<T>(schema: T, handler: (notification: T) => Promise<void>): void
// 发送请求并等待响应
request<T>(request: Request, schema: T, options?: RequestOptions): Promise<T>
// 发送单向通知
notification(notification: Notification): Promise<void>
Python
class Session(BaseSession[RequestT, NotificationT, ResultT]):
async def send_request(
self,
request: RequestT,
result_type: type[Result]
) -> Result:
"""
发送请求并等待响应。如果响应包含错误,则引发 McpError。
"""
# 请求处理实现
async def send_notification(
self,
notification: NotificationT
) -> None:
"""发送不需要响应的单向通知。"""
# 通知处理实现
async def _received_request(
self,
responder: RequestResponder[ReceiveRequestT, ResultT]
) -> None:
"""处理来自另一端的传入请求。"""
# 请求处理实现
async def _received_notification(
self,
notification: ReceiveNotificationT
) -> None:
"""处理来自另一端的传入通知。"""
# 通知处理实现
关键类包括:
Protocol
Client
Server
传输层
传输层处理客户端和服务器之间的实际通信。MCP 支持多种传输机制:
- Stdio 传输
- 使用标准输入/输出进行通信
- 适用于本地进程
- 带有 SSE 的 HTTP 传输
- 使用服务器发送事件 (Server-Sent Events) 进行服务器到客户端的消息传递
- 使用 HTTP POST 进行客户端到服务器的消息传递
所有传输都使用 JSON-RPC 2.0 交换消息。有关模型上下文协议消息格式的详细信息,请参阅规范。
消息类型
MCP 具有以下主要消息类型:
- 请求 需要来自另一端的响应:
interface Request { method: string; params?: { ... }; }
- 结果 是对请求的成功响应:
interface Result { [key: string]: unknown; }
- 错误 表示请求失败:
interface Error { code: number; message: string; data?: unknown; }
- 通知 是不需要响应的单向消息:
interface Notification { method: string; params?: { ... }; }
连接生命周期
1. 初始化
sequenceDiagram
participant Client
participant Server
Client->>Server: initialize request
Server->>Client: initialize response
Client->>Server: initialized notification
Note over Client,Server: Connection ready for use
- 客户端发送带有协议版本和功能的
initialize
请求 - 服务器响应其协议版本和功能
- 客户端发送
initialized
通知作为确认 - 开始正常的消息交换
2. 消息交换
初始化后,支持以下模式:
- 请求-响应:客户端或服务器发送请求,另一方响应
- 通知:任何一方发送单向消息
3. 终止
任何一方都可以终止连接:
- 通过
close()
清理关闭 - 传输断开
- 错误情况
错误处理
MCP 定义了以下标准错误代码:
enum ErrorCode {
// 标准 JSON-RPC 错误代码
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603
}
SDK 和应用程序可以在 -32000 以上定义自己的错误代码。
错误通过以下方式传播:
- 对请求的错误响应
- 传输上的错误事件
- 协议级别的错误处理程序
实现示例
以下是实现 MCP 服务器的基本示例:
TypeScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
resources: {}
}
});
// 处理请求
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "example://resource",
name: "Example Resource"
}
]
};
});
// 连接传输
const transport = new StdioServerTransport();
await server.connect(transport);
Python
import asyncio
import mcp.types as types
from mcp.server import Server
from mcp.server.stdio import stdio_server
app = Server("example-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="example://resource",
name="Example Resource"
)
]
async def main():
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main)
最佳实践
传输选择
- 本地通信
- 使用 stdio 传输进行本地进程通信
- 对于同一机器通信非常有效
- 简单的进程管理
- 远程通信
- 对于需要 HTTP 兼容性的场景,使用 SSE
- 考虑包括身份验证和授权在内的安全影响
消息处理
- 请求处理
- 彻底验证输入
- 使用类型安全的模式
- 优雅地处理错误
- 实施超时
- 进度报告
- 对长时间运行的操作使用进度令牌
- 增量报告进度
- 在已知时包括总进度
- 错误管理
- 使用适当的错误代码
- 包括有用的错误消息
- 清理错误时的资源
安全注意事项
- 传输安全
- 对远程连接使用 TLS
- 验证连接来源
- 在需要时实施身份验证
- 消息验证
- 验证所有传入消息
- 消毒输入
- 检查消息大小限制
- 验证 JSON-RPC 格式
- 资源保护
- 实施访问控制
- 验证资源路径
- 监控资源使用情况
- 限制请求速率
- 错误处理
- 不要泄漏敏感信息
- 记录与安全相关的错误
- 实施适当的清理
- 处理 DoS 场景
调试和监控
- 日志记录
- 记录协议事件
- 跟踪消息流
- 监控性能
- 记录错误
- 诊断
- 实施运行状况检查
- 监控连接状态
- 跟踪资源使用情况
- 分析性能
- 测试
- 测试不同的传输
- 验证错误处理
- 检查边缘情况
- 负载测试服务器
Resources(资源)
将来自服务器的数据和内容暴露给 LLM
资源是模型上下文协议 (MCP) 中的一个核心原语,它允许服务器暴露数据和内容,这些数据和内容可以被客户端读取,并用作 LLM 交互的上下文。
Note
资源被设计为应用程序控制的,这意味着客户端应用程序可以决定如何以及何时使用它们。 不同的 MCP 客户端可能会以不同的方式处理资源。例如:
- Claude Desktop 目前要求用户显式选择资源才能使用它们
- 其他客户端可能会根据启发式方法自动选择资源
- 某些实现甚至可能允许 AI 模型本身决定使用哪些资源
服务器作者应该准备好在实现资源支持时处理任何这些交互模式。为了自动将数据暴露给模型,服务器作者应该使用模型控制的原语,例如 工具。
概述
资源表示 MCP 服务器想要提供给客户端的任何类型的数据。这可以包括:
- 文件内容
- 数据库记录
- API 响应
- 实时系统数据
- 屏幕截图和图像
- 日志文件
- 还有更多
每个资源都由一个唯一的 URI 标识,并且可以包含文本或二进制数据。
资源 URI
资源使用以下格式的 URI 标识:
[协议]://[主机]/[路径]
例如:
file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://localhost/display1
协议和路径结构由 MCP 服务器实现定义。服务器可以定义它们自己的自定义 URI 方案。
资源类型
资源可以包含两种类型的内容:
文本资源
文本资源包含 UTF-8 编码的文本数据。这些适用于:
- 源代码
- 配置文件
- 日志文件
- JSON/XML 数据
- 纯文本
二进制资源
二进制资源包含以 base64 编码的原始二进制数据。这些适用于:
- 图像
- 音频文件
- 视频文件
- 其他非文本格式
资源发现
客户端可以通过两种主要方法发现可用的资源:
直接资源
服务器通过 resources/list
端点暴露一个具体的资源列表。每个资源包括:
{
uri: string; // 资源的唯一标识符
name: string; // 人类可读的名称
description?: string; // 可选的描述
mimeType?: string; // 可选的 MIME 类型
}
资源模板
对于动态资源,服务器可以暴露 URI 模板,客户端可以使用这些模板来构造有效的资源 URI:
{
uriTemplate: string; // 遵循 RFC 6570 的 URI 模板
name: string; // 此类型的人类可读名称
description?: string; // 所有匹配资源的可选描述
mimeType?: string; // 所有匹配资源的可选 MIME 类型
}
读取资源
要读取资源,客户端使用资源 URI 发出 resources/read
请求。
服务器返回一个资源内容列表:
{
contents: [
{
uri: string; // 资源的 URI
mimeType?: string; // 可选的 MIME 类型
// 以下之一:
text?: string; // 对于文本资源
blob?: string; // 对于二进制资源(base64 编码)
}
]
}
Tip
服务器可能会返回多个资源以响应一个 resources/read
请求。例如,当读取目录时,这可以用于返回目录中的文件列表。
资源更新
MCP 通过两种机制支持资源的实时更新:
列表更改
当可用资源列表更改时,服务器可以通过 notifications/resources/list_changed
通知客户端。
内容更改
客户端可以订阅特定资源的更新:
- 客户端发送带有资源 URI 的
resources/subscribe
- 当资源更改时,服务器发送
notifications/resources/updated
- 客户端可以使用
resources/read
获取最新内容 - 客户端可以使用
resources/unsubscribe
取消订阅
示例实现
这是一个在 MCP 服务器中实现资源支持的简单示例:
TypeScript
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
resources: {}
}
});
// 列出可用的资源
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "file:///logs/app.log",
name: "应用程序日志",
mimeType: "text/plain"
}
]
};
});
// 读取资源内容
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === "file:///logs/app.log") {
const logContents = await readLogFile();
return {
contents: [
{
uri,
mimeType: "text/plain",
text: logContents
}
]
};
}
throw new Error("Resource not found");
});
Python
app = Server("example-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="file:///logs/app.log",
name="应用程序日志",
mimeType="text/plain"
)
]
@app.read_resource()
async def read_resource(uri: AnyUrl) -> str:
if str(uri) == "file:///logs/app.log":
log_contents = await read_log_file()
return log_contents
raise ValueError("Resource not found")
# 启动服务器
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)
最佳实践
在实现资源支持时:
- 使用清晰、描述性的资源名称和 URI
- 包含有用的描述以指导 LLM 理解
- 在已知时设置适当的 MIME 类型
- 为动态内容实现资源模板
- 使用订阅来获取频繁更改的资源
- 使用清晰的错误消息来优雅地处理错误
- 考虑对大型资源列表进行分页
- 在适当的时候缓存资源内容
- 在处理之前验证 URI
- 记录您的自定义 URI 方案
安全注意事项
在暴露资源时:
- 验证所有资源 URI
- 实施适当的访问控制
- 净化文件路径以防止目录遍历
- 处理二进制数据时要小心
- 考虑对资源读取进行速率限制
- 审计资源访问
- 加密传输中的敏感数据
- 验证 MIME 类型
- 为长时间运行的读取实现超时
- 适当处理资源清理
Prompts(提示词)
创建可重用的提示词模板和工作流
提示词使服务器能够定义可重用的提示词模板和工作流,客户端可以轻松地将其呈现给用户和 LLM。它们提供了一种强大的方式来标准化和共享常见的 LLM 交互。
Note
提示词被设计为用户控制,这意味着它们从服务器暴露给客户端,目的是让用户能够明确地选择它们以供使用。
概述
MCP 中的提示词是预定义的模板,可以:
- 接受动态参数
- 包含来自资源的上下文
- 链接多个交互
- 指导特定的工作流
- 作为 UI 元素呈现(如斜杠命令)
提示词结构
每个提示词都定义为:
{
name: string; // 提示词的唯一标识符
description?: string; // 人类可读的描述
arguments?: [ // 可选的参数列表
{
name: string; // 参数标识符
description?: string; // 参数描述
required?: boolean; // 参数是否是必需的
}
]
}
发现提示词
客户端可以通过 prompts/list
端点发现可用的提示词:
// 请求
{
method: "prompts/list"
}
// 响应
{
prompts: [
{
name: "analyze-code",
description: "分析代码以获得潜在的改进",
arguments: [
{
name: "language",
description: "编程语言",
required: true
}
]
}
]
}
使用提示词
要使用提示词,客户端发出 prompts/get
请求:
// 请求
{
method: "prompts/get",
params: {
name: "analyze-code",
arguments: {
language: "python"
}
}
}
// 响应
{
description: "分析 Python 代码以获得潜在的改进",
messages: [
{
role: "user",
content: {
type: "text",
text: "请分析以下 Python 代码以获得潜在的改进:\n\n```python\ndef calculate_sum(numbers):\n total = 0\n for num in numbers:\n total = total + num\n return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n```"
}
}
]
}
动态提示词
提示词可以是动态的,包括:
嵌入式资源上下文
{
"name": "analyze-project",
"description": "分析项目日志和代码",
"arguments": [
{
"name": "timeframe",
"description": "分析日志的时间段",
"required": true
},
{
"name": "fileUri",
"description": "要审查的代码文件的 URI",
"required": true
}
]
}
当处理 prompts/get
请求时:
{
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "分析这些系统日志和代码文件中的任何问题:"
}
},
{
"role": "user",
"content": {
"type": "resource",
"resource": {
"uri": "logs://recent?timeframe=1h",
"text": "[2024-03-14 15:32:11] ERROR: Connection timeout in network.py:127\n[2024-03-14 15:32:15] WARN: Retrying connection (attempt 2/3)\n[2024-03-14 15:32:20] ERROR: Max retries exceeded",
"mimeType": "text/plain"
}
}
},
{
"role": "user",
"content": {
"type": "resource",
"resource": {
"uri": "file:///path/to/code.py",
"text": "def connect_to_service(timeout=30):\n retries = 3\n for attempt in range(retries):\n try:\n return establish_connection(timeout)\n except TimeoutError:\n if attempt == retries - 1:\n raise\n time.sleep(5)\n\ndef establish_connection(timeout):\n # Connection implementation\n pass",
"mimeType": "text/x-python"
}
}
}
]
}
多步骤工作流
const debugWorkflow = {
name: "debug-error",
async getMessages(error: string) {
return [
{
role: "user",
content: {
type: "text",
text: `这是我看到的一个错误:${error}`
}
},
{
role: "assistant",
content: {
type: "text",
text: "我将帮助分析这个错误。到目前为止你尝试了什么?"
}
},
{
role: "user",
content: {
type: "text",
text: "我尝试重新启动服务,但错误仍然存在。"
}
}
];
}
};
示例实现
这是一个在 MCP 服务器中实现提示词的完整示例:
TypeScript
import { Server } from "@modelcontextprotocol/sdk/server";
import {
ListPromptsRequestSchema,
GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types";
const PROMPTS = {
"git-commit": {
name: "git-commit",
description: "生成 Git 提交消息",
arguments: [
{
name: "changes",
description: "Git diff 或更改描述",
required: true
}
]
},
"explain-code": {
name: "explain-code",
description: "解释代码如何工作",
arguments: [
{
name: "code",
description: "要解释的代码",
required: true
},
{
name: "language",
description: "编程语言",
required: false
}
]
}
};
const server = new Server({
name: "example-prompts-server",
version: "1.0.0"
}, {
capabilities: {
prompts: {}
}
});
// 列出可用的提示词
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: Object.values(PROMPTS)
};
});
// 获取特定的提示词
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const prompt = PROMPTS[request.params.name];
if (!prompt) {
throw new Error(`未找到提示词:${request.params.name}`);
}
if (request.params.name === "git-commit") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `为这些更改生成简洁但描述性的提交消息:\n\n${request.params.arguments?.changes}`
}
}
]
};
}
if (request.params.name === "explain-code") {
const language = request.params.arguments?.language || "Unknown";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `解释这段 ${language} 代码是如何工作的:\n\n${request.params.arguments?.code}`
}
}
]
};
}
throw new Error("未找到提示词实现");
});
Python
from mcp.server import Server
import mcp.types as types
# 定义可用的提示词
PROMPTS = {
"git-commit": types.Prompt(
name="git-commit",
description="生成 Git 提交消息",
arguments=[
types.PromptArgument(
name="changes",
description="Git diff 或更改描述",
required=True
)
],
),
"explain-code": types.Prompt(
name="explain-code",
description="解释代码如何工作",
arguments=[
types.PromptArgument(
name="code",
description="要解释的代码",
required=True
),
types.PromptArgument(
name="language",
description="编程语言",
required=False
)
],
)
}
# 初始化服务器
app = Server("example-prompts-server")
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return list(PROMPTS.values())
@app.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None = None
) -> types.GetPromptResult:
if name not in PROMPTS:
raise ValueError(f"未找到提示词:{name}")
if name == "git-commit":
changes = arguments.get("changes") if arguments else ""
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"生成简洁但描述性的提交消息 "
f"为了这些更改:\n\n{changes}"
)
)
]
)
if name == "explain-code":
code = arguments.get("code") if arguments else ""
language = arguments.get("language", "Unknown") if arguments else "Unknown"
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"解释这段 {language} 代码是如何工作的:\n\n{code}"
)
)
]
)
raise ValueError("未找到提示词实现")
最佳实践
在实现提示词时:
- 使用清晰、描述性的提示词名称
- 为提示词和参数提供详细的描述
- 验证所有必需的参数
- 优雅地处理缺失的参数
- 考虑提示词模板的版本控制
- 适当时缓存动态内容
- 实施错误处理
- 记录预期的参数格式
- 考虑提示词的可组合性
- 使用各种输入测试提示词
UI 集成
提示词可以作为以下 UI 元素呈现:
- 斜杠命令
- 快速操作
- 上下文菜单项
- 命令面板条目
- 引导式工作流
- 交互式表单
更新和更改
服务器可以通知客户端有关提示词的更改:
- 服务器功能:
prompts.listChanged
- 通知:
notifications/prompts/list_changed
- 客户端重新获取提示词列表
安全注意事项
在实现提示词时:
- 验证所有参数
- 过滤用户输入
- 考虑速率限制
- 实施访问控制
- 审计提示词使用情况
- 适当处理敏感数据
- 验证生成的内容
- 实施超时
- 考虑提示词注入风险
- 记录安全要求
Tools(工具)
使 LLM 能够通过您的服务器执行操作
工具是模型上下文协议 (MCP) 中一个强大的原语,使服务器能够向客户端公开可执行的功能。通过工具,LLM 可以与外部系统交互、执行计算并在现实世界中执行操作。
Note
工具被设计为模型控制,这意味着工具从服务器公开给客户端,目的是 AI 模型能够自动调用它们(在人工参与批准的情况下)。
概述
MCP 中的工具允许服务器公开可由客户端调用并由 LLM 用于执行操作的可执行函数。工具的关键方面包括:
- 发现:客户端可以通过
tools/list
端点列出可用的工具 - 调用:工具使用
tools/call
端点调用,服务器执行请求的操作并返回结果 - 灵活性:工具的范围可以从简单的计算到复杂的 API 交互
与资源类似,工具由唯一的名称标识,并且可以包含描述以指导其使用。但是,与资源不同,工具表示可以修改状态或与外部系统交互的动态操作。
工具定义结构
每个工具都使用以下结构定义:
{
name: string; // 工具的唯一标识符
description?: string; // 人工可读的描述
inputSchema: { // 工具参数的 JSON Schema
type: "object",
properties: { ... } // 工具特定的参数
}
}
实现工具
这是一个在 MCP 服务器中实现基本工具的示例:
TypeScript
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
// 定义可用的工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [{
name: "calculate_sum",
description: "将两个数字相加",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
}]
};
});
// 处理工具执行
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "calculate_sum") {
const { a, b } = request.params.arguments;
return {
content: [
{
type: "text",
text: String(a + b)
}
]
};
}
throw new Error("Tool not found");
});
Python
app = Server("example-server")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="calculate_sum",
description="将两个数字相加",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
}
)
]
@app.call_tool()
async def call_tool(
name: str,
arguments: dict
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
if name == "calculate_sum":
a = arguments["a"]
b = arguments["b"]
result = a + b
return [types.TextContent(type="text", text=str(result))]
raise ValueError(f"Tool not found: {name}")
示例工具模式
以下是服务器可以提供的一些工具类型示例:
系统操作
与本地系统交互的工具:
{
name: "execute_command",
description: "运行 shell 命令",
inputSchema: {
type: "object",
properties: {
command: { type: "string" },
args: { type: "array", items: { type: "string" } }
}
}
}
API 集成
包装外部 API 的工具:
{
name: "github_create_issue",
description: "创建一个 GitHub issue",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
labels: { type: "array", items: { type: "string" } }
}
}
}
数据处理
转换或分析数据的工具:
{
name: "analyze_csv",
description: "分析 CSV 文件",
inputSchema: {
type: "object",
properties: {
filepath: { type: "string" },
operations: {
type: "array",
items: {
enum: ["sum", "average", "count"]
}
}
}
}
}
最佳实践
在实现工具时:
- 提供清晰、描述性的名称和描述
- 使用详细的 JSON Schema 定义参数
- 在工具描述中包含示例,以演示模型应如何使用它们
- 实施适当的错误处理和验证
- 对长时间运行的操作使用进度报告
- 保持工具操作的重点和原子性
- 记录预期的返回值结构
- 实施适当的超时
- 考虑对资源密集型操作进行速率限制
- 记录工具使用情况以进行调试和监控
安全注意事项
在公开工具时:
输入验证
- 根据 schema 验证所有参数
- 清理文件路径和系统命令
- 验证 URL 和外部标识符
- 检查参数大小和范围
- 防止命令注入
访问控制
- 在需要时实施身份验证
- 使用适当的授权检查
- 审计工具使用情况
- 限制请求速率
- 监控滥用
错误处理
- 不要向客户端公开内部错误
- 记录与安全相关的错误
- 适当处理超时
- 错误后清理资源
- 验证返回值
工具发现和更新
MCP 支持动态工具发现:
- 客户端可以随时列出可用的工具
- 当工具更改时,服务器可以使用
notifications/tools/list_changed
通知客户端 - 可以在运行时添加或删除工具
- 可以更新工具定义(尽管应该小心执行此操作)
错误处理
工具错误应在结果对象中报告,而不是作为 MCP 协议级别的错误。这允许 LLM 查看并可能处理该错误。当工具遇到错误时:
- 在结果中将
isError
设置为true
- 在
content
数组中包含错误详细信息
这是一个用于工具的正确错误处理示例:
TypeScript
try {
// 工具操作
const result = performOperation();
return {
content: [
{
type: "text",
text: `Operation successful: ${result}`
}
]
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `Error: ${error.message}`
}
]
};
}
Python
try:
# 工具操作
result = perform_operation()
return types.CallToolResult(
content=[
types.TextContent(
type="text",
text=f"Operation successful: {result}"
)
]
)
except Exception as error:
return types.CallToolResult(
isError=True,
content=[
types.TextContent(
type="text",
text=f"Error: {str(error)}"
)
]
)
这种方法允许 LLM 看到发生了错误,并可能采取纠正措施或请求人工干预。
测试工具
MCP 工具的全面测试策略应包括:
- 功能测试:验证工具是否使用有效输入正确执行,并适当处理无效输入
- 集成测试:使用真实和模拟的依赖项测试工具与外部系统的交互
- 安全测试:验证身份验证、授权、输入清理和速率限制
- 性能测试:检查负载下的行为、超时处理和资源清理
- 错误处理:确保工具通过 MCP 协议正确报告错误并清理资源
Sampling(采样)
让你的服务器从 LLM 请求补全
采样是一个强大的 MCP 功能,允许服务器通过客户端请求 LLM 补全,从而实现复杂的代理行为,同时保持安全性和隐私。
采样如何工作
采样流程遵循以下步骤:
- 服务器向客户端发送一个
sampling/createMessage
请求 - 客户端审查该请求,并且可以修改它
- 客户端从 LLM 采样
- 客户端审查补全结果
- 客户端将结果返回给服务器
这种人机协作设计确保用户保持对 LLM 所见和所生成内容的控制。
消息格式
采样请求使用标准化的消息格式:
{
messages: [
{
role: "user" | "assistant",
content: {
type: "text" | "image",
// 对于文本:
text?: string,
// 对于图像:
data?: string, // base64 编码
mimeType?: string
}
}
],
modelPreferences?: {
hints?: [{
name?: string // 建议的模型名称/系列
}],
costPriority?: number, // 0-1,最小化成本的重要性
speedPriority?: number, // 0-1,低延迟的重要性
intelligencePriority?: number // 0-1,模型能力的重要性
},
systemPrompt?: string,
includeContext?: "none" | "thisServer" | "allServers",
temperature?: number,
maxTokens: number,
stopSequences?: string[],
metadata?: Record<string, unknown>
}
请求参数
消息
messages
数组包含要发送到 LLM 的对话历史记录。每个消息都有:
role
: “user” 或 “assistant”content
: 消息内容,可以是:- 带有
text
字段的文本内容 - 带有
data
(base64) 和mimeType
字段的图像内容
- 带有
模型偏好
modelPreferences
对象允许服务器指定其模型选择偏好:
hints
: 客户端可用于选择合适模型的模型名称建议数组:name
: 可以匹配完整或部分模型名称的字符串(例如 “claude-3”, “sonnet”)- 客户端可以将提示映射到来自不同提供商的等效模型
- 多个提示按偏好顺序评估
- 优先级值(0-1 归一化):
costPriority
: 最小化成本的重要性speedPriority
: 低延迟响应的重要性intelligencePriority
: 高级模型能力的重要性
客户端根据这些偏好及其可用模型做出最终模型选择。
系统提示
可选的 systemPrompt
字段允许服务器请求特定的系统提示。客户端可以修改或忽略此提示。
上下文包含
includeContext
参数指定要包含的 MCP 上下文:
"none"
: 没有额外的上下文"thisServer"
: 包含来自请求服务器的上下文"allServers"
: 包含来自所有连接的 MCP 服务器的上下文
客户端控制实际包含的上下文。
采样参数
使用以下参数微调 LLM 采样:
temperature
: 控制随机性(0.0 到 1.0)maxTokens
: 要生成的最大 token 数stopSequences
: 停止生成的序列数组metadata
: 其他提供商特定的参数
响应格式
客户端返回一个补全结果:
{
model: string, // 使用的模型的名称
stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string,
role: "user" | "assistant",
content: {
type: "text" | "image",
text?: string,
data?: string,
mimeType?: string
}
}
示例请求
以下是请求客户端采样的示例:
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "当前目录下有哪些文件?"
}
}
],
"systemPrompt": "你是一个有用的文件系统助手。",
"includeContext": "thisServer",
"maxTokens": 100
}
}
最佳实践
实施采样时:
- 始终提供清晰、结构良好的提示
- 适当处理文本和图像内容
- 设置合理的 token 限制
- 通过
includeContext
包含相关上下文 - 在使用响应之前验证它们
- 优雅地处理错误
- 考虑限制采样请求的速率
- 记录预期的采样行为
- 使用各种模型参数进行测试
- 监控采样成本
人工参与控制
采样在设计时考虑了人工监督:
对于提示
- 客户端应向用户显示建议的提示
- 用户应该能够修改或拒绝提示
- 系统提示可以被过滤或修改
- 上下文包含由客户端控制
对于补全
- 客户端应向用户显示补全结果
- 用户应该能够修改或拒绝补全结果
- 客户端可以过滤或修改补全结果
- 用户控制使用哪个模型
安全注意事项
实施采样时:
- 验证所有消息内容
- 过滤敏感信息
- 实施适当的速率限制
- 监控采样使用情况
- 加密传输中的数据
- 处理用户数据隐私
- 审计采样请求
- 控制成本风险
- 实施超时
- 优雅地处理模型错误
常见模式
代理工作流
采样支持代理模式,例如:
- 读取和分析资源
- 根据上下文做出决策
- 生成结构化数据
- 处理多步骤任务
- 提供交互式帮助
上下文管理
上下文的最佳实践:
- 请求最少的必要上下文
- 清晰地构建上下文
- 处理上下文大小限制
- 根据需要更新上下文
- 清理过时的上下文
错误处理
强大的错误处理应:
- 捕获采样失败
- 处理超时错误
- 管理速率限制
- 验证响应
- 提供回退行为
- 适当地记录错误
局限性
请注意以下限制:
- 采样取决于客户端能力
- 用户控制采样行为
- 上下文大小有限制
- 速率限制可能适用
- 应考虑成本
- 模型可用性各不相同
- 响应时间各不相同
- 并非支持所有内容类型
Roots(根)
理解 MCP 中的根
根是 MCP 中的一个概念,它定义了服务器可以操作的边界。它们为客户端提供了一种告知服务器相关资源及其位置的方式。
什么是根?
根是客户端建议服务器应关注的 URI。当客户端连接到服务器时,它会声明服务器应使用的根。虽然主要用于文件系统路径,但根可以是任何有效的 URI,包括 HTTP URL。
例如,根可以是:
file:///home/user/projects/myapp
https://api.example.com/v1
为什么使用根?
根有几个重要的用途:
- 指导:它们告知服务器相关资源和位置
- 清晰:根清楚地表明哪些资源是工作区的一部分
- 组织:多个根允许您同时处理不同的资源
根如何工作
当客户端支持根时,它:
- 在连接期间声明
roots
功能 - 向服务器提供建议的根列表
- 在根更改时通知服务器(如果支持)
虽然根是信息性的,并非强制性的,但服务器应:
- 尊重提供的根
- 使用根 URI 来定位和访问资源
- 优先处理根边界内的操作
常见用例
根通常用于定义:
- 项目目录
- 仓库位置
- API 端点
- 配置位置
- 资源边界
最佳实践
使用根时:
- 仅建议必要的资源
- 对根使用清晰、描述性的名称
- 监控根的可访问性
- 优雅地处理根更改
示例
以下是一个典型的 MCP 客户端如何公开根的示例:
{
"roots": [
{
"uri": "file:///home/user/projects/frontend",
"name": "前端仓库"
},
{
"uri": "https://api.example.com/v1",
"name": "API 端点"
}
]
}
此配置建议服务器关注本地仓库和 API 端点,同时保持它们在逻辑上分离。
Transports(传输)
了解 MCP 的通信机制
模型上下文协议 (MCP) 中的传输为客户端和服务器之间的通信提供了基础。传输处理消息发送和接收的底层机制。
消息格式
MCP 使用 JSON-RPC 2.0 作为其线路格式。传输层负责将 MCP 协议消息转换为 JSON-RPC 格式以进行传输,并将接收到的 JSON-RPC 消息转换回 MCP 协议消息。
使用三种类型的 JSON-RPC 消息:
请求
{
jsonrpc: "2.0",
id: number | string,
method: string,
params?: object
}
响应
{
jsonrpc: "2.0",
id: number | string,
result?: object,
error?: {
code: number,
message: string,
data?: unknown
}
}
通知
{
jsonrpc: "2.0",
method: string,
params?: object
}
内置传输类型
MCP 包括两种标准传输实现:
标准输入/输出 (stdio)
stdio 传输支持通过标准输入和输出流进行通信。这对于本地集成和命令行工具特别有用。
在以下情况下使用 stdio:
- 构建命令行工具
- 实施本地集成
- 需要简单的进程通信
- 使用 shell 脚本
TypeScript (服务器)
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {}
});
const transport = new StdioServerTransport();
await server.connect(transport);
TypeScript (客户端)
const client = new Client({
name: "example-client",
version: "1.0.0"
}, {
capabilities: {}
});
const transport = new StdioClientTransport({
command: "./server",
args: ["--option", "value"]
});
await client.connect(transport);
Python (服务器)
app = Server("example-server")
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)
Python (客户端)
params = StdioServerParameters(
command="./server",
args=["--option", "value"]
)
async with stdio_client(params) as streams:
async with ClientSession(streams[0], streams[1]) as session:
await session.initialize()
服务器发送事件 (SSE)
SSE 传输支持使用 HTTP POST 请求进行服务器到客户端的流式传输,以进行客户端到服务器的通信。
在以下情况下使用 SSE:
- 只需要服务器到客户端的流式传输
- 使用受限网络
- 实施简单的更新
TypeScript (服务器)
import express from "express";
const app = express();
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {}
});
let transport: SSEServerTransport | null = null;
app.get("/sse", (req, res) => {
transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
app.post("/messages", (req, res) => {
if (transport) {
transport.handlePostMessage(req, res);
}
});
app.listen(3000);
TypeScript (客户端)
const client = new Client({
name: "example-client",
version: "1.0.0"
}, {
capabilities: {}
});
const transport = new SSEClientTransport(
new URL("http://localhost:3000/sse")
);
await client.connect(transport);
Python (服务器)
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route
app = Server("example-server")
sse = SseServerTransport("/messages")
async def handle_sse(scope, receive, send):
async with sse.connect_sse(scope, receive, send) as streams:
await app.run(streams[0], streams[1], app.create_initialization_options())
async def handle_messages(scope, receive, send):
await sse.handle_post_message(scope, receive, send)
starlette_app = Starlette(
routes=[
Route("/sse", endpoint=handle_sse),
Route("/messages", endpoint=handle_messages, methods=["POST"]),
]
)
Python (客户端)
async with sse_client("http://localhost:8000/sse") as streams:
async with ClientSession(streams[0], streams[1]) as session:
await session.initialize()
自定义传输
MCP 使为特定需求实施自定义传输变得容易。任何传输实现只需要符合 Transport 接口:
您可以为以下情况实施自定义传输:
- 自定义网络协议
- 专用通信通道
- 与现有系统集成
- 性能优化
TypeScript
interface Transport {
// 开始处理消息
start(): Promise<void>;
// 发送 JSON-RPC 消息
send(message: JSONRPCMessage): Promise<void>;
// 关闭连接
close(): Promise<void>;
// 回调
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
}
Python
请注意,虽然 MCP 服务器通常使用 asyncio 实现,但我们建议使用 anyio
实现低级接口(如传输),以获得更广泛的兼容性。
@contextmanager
async def create_transport(
read_stream: MemoryObjectReceiveStream[JSONRPCMessage | Exception],
write_stream: MemoryObjectSendStream[JSONRPCMessage]
):
"""
MCP 的传输接口。
Args:
read_stream: 用于读取传入消息的流
write_stream: 用于写入传出消息的流
"""
async with anyio.create_task_group() as tg:
try:
# 开始处理消息
tg.start_soon(lambda: process_messages(read_stream))
# 发送消息
async with write_stream:
yield write_stream
except Exception as exc:
# 处理错误
raise exc
finally:
# 清理
tg.cancel_scope.cancel()
await write_stream.aclose()
await read_stream.aclose()
错误处理
传输实现应处理各种错误情况:
- 连接错误
- 消息解析错误
- 协议错误
- 网络超时
- 资源清理
错误处理示例:
TypeScript
class ExampleTransport implements Transport {
async start() {
try {
// 连接逻辑
} catch (error) {
this.onerror?.(new Error(`Failed to connect: ${error}`));
throw error;
}
}
async send(message: JSONRPCMessage) {
try {
// 发送逻辑
} catch (error) {
this.onerror?.(new Error(`Failed to send message: ${error}`));
throw error;
}
}
}
Python
请注意,虽然 MCP 服务器通常使用 asyncio 实现,但我们建议使用 anyio
实现低级接口(如传输),以获得更广泛的兼容性。
@contextmanager
async def example_transport(scope: Scope, receive: Receive, send: Send):
try:
# 创建用于双向通信的流
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
async def message_handler():
try:
async with read_stream_writer:
# 消息处理逻辑
pass
except Exception as exc:
logger.error(f"Failed to handle message: {exc}")
raise exc
async with anyio.create_task_group() as tg:
tg.start_soon(message_handler)
try:
# 生成用于通信的流
yield read_stream, write_stream
except Exception as exc:
logger.error(f"Transport error: {exc}")
raise exc
finally:
tg.cancel_scope.cancel()
await write_stream.aclose()
await read_stream.aclose()
except Exception as exc:
logger.error(f"Failed to initialize transport: {exc}")
raise exc
最佳实践
在实施或使用 MCP 传输时:
- 正确处理连接生命周期
- 实施适当的错误处理
- 在连接关闭时清理资源
- 使用适当的超时
- 在发送之前验证消息
- 记录传输事件以进行调试
- 在适当的时候实施重新连接逻辑
- 处理消息队列中的反压
- 监控连接健康状况
- 实施适当的安全措施
安全注意事项
在实施传输时:
身份验证和授权
- 实施适当的身份验证机制
- 验证客户端凭据
- 使用安全令牌处理
- 实施授权检查
数据安全
- 使用 TLS 进行网络传输
- 加密敏感数据
- 验证消息完整性
- 实施消息大小限制
- 消毒输入数据
网络安全
- 实施速率限制
- 使用适当的超时
- 处理拒绝服务场景
- 监控异常模式
- 实施适当的防火墙规则
调试传输
调试传输问题的提示:
- 启用调试日志记录
- 监控消息流
- 检查连接状态
- 验证消息格式
- 测试错误场景
- 使用网络分析工具
- 实施健康检查
- 监控资源使用情况
- 测试边缘情况
- 使用适当的错误跟踪