第11章 MCP-工具的标准化接口
来源:https://ai-agent-guide.xiaofuge.cn/chapters/ch07-mcp.html 所属:第三篇-Agent的手脚
Model Context Protocol — Agent 的 USB-C 接口
11.1 为什么需要 MCP?
在 MCP 出现前,每个 Agent 框架有自己的工具定义方式。LangChain 用 @tool 装饰器,AutoGen 用 register_function,CrewAI 用 BaseTool 类……工具开发者要为每个框架写一遍适配代码。
**问题:**N 个 Agent 框架 × M 个工具 = N×M 个适配代码。重复劳动、维护噩梦。
**MCP(Model Context Protocol)**是 Anthropic 在 2024 年提出的开放标准,目标是统一 Agent 与外部工具/数据的连接方式。
MCP 就像 Agent 世界的 USB-C 接口——一个标准协议,所有工具都按这个标准提供,所有 Agent 都按这个标准调用。N×M 变成 N+M。
**解决:**工具开发者只需实现一次 MCP Server,所有支持 MCP 的 Agent 框架都能用。N×M 变成 N+M。
11.2 MCP 架构
📐 MCP 的三个核心概念
Tools(工具)
可被 LLM 调用的函数。如搜索、查询数据库、发邮件。类似 Function Calling,但标准化了。
Resources(资源)
可被 LLM 读取的数据。如文件内容、数据库记录、API 返回。是只读的上下文。
Prompts(提示模板)
预定义的 Prompt 模板。Server 提供专业提示,Client 直接使用。标准化知识传递。
11.3 MCP 的通信机制
MCP 基于 JSON-RPC 2.0 协议,支持三种传输方式:
stdio(标准输入输出)
MCP Server 作为子进程运行,通过标准输入输出通信。
适合:本地工具、同机部署
优点:简单、零网络开销
缺点:不能跨机器
SSE(Server-Sent Events)— 即将废弃
通过 HTTP + SSE 双向通信。Client 发 POST,Server 通过 SSE 控道推送响应。
适合:远程工具(旧方案)
优点:跨机器
缺点:需维持长连接、断线不可恢复
⚠ 已被 Streamable HTTP 替代
Streamable HTTP✨ 推荐
2025-03-26 新增。以普通 HTTP 为基础,可选升级 SSE 流式响应。
适合:远程工具、云服务、Serverless
优点:无需长连接、断线可恢复、兼容基础设施
缺点:无(取代 SSE 的所有优势)
MCP 消息格式
MCP JSON-RPC 消息示例
// ===== Client → Server: 调用工具 =====
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_web",
"arguments": {
"query": "LangGraph tutorial"
}
}
}
// ===== Server → Client: 返回结果 =====
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "找到 10 条相关结果..."
}
]
}
}
// ===== Server → Client: 推送通知 =====
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progress": 50,
"message": "已搜索 50%"
}
}
Streamable HTTP:为什么 SSE 被取代?
2025 年 3 月 26 日,MCP 协议引入了 Streamable HTTP 传输方式,替代原有的 HTTP+SSE。这不是一个全新的协议——它是 SSE 的渐进式升级,在保留流式响应优势的同时,解决了 SSE 的三个硬伤: ❌ SSE 的三个硬伤
① 断线不可恢复:SSE 连接中断后,无法从断点继续,只能重建连接,之前的上下文丢失。
② 服务端必须维持长连接:服务器必须一直保持稳定的 SSE 长连接,否则通信就中断。这对 Serverless 和弹性扩缩容场景是灾难。
③ 服务器只能通过 SSE 通道发送:服务端无法在已有请求之外主动推送消息,通信是"单向被动响应"。
Streamable HTTP 的核心设计理念:
- 以普通 HTTP 为基础:客户端用 POST/GET 发请求,和调用普通 REST API 一样
- 可选升级为 SSE 流:当需要流式响应时(如进度通知、长耗时工具),服务器可选地将响应升级为 SSE 流——但不强制
- 统一端点:不再需要单独的
/sse端点,一切通过/mcp(或/message) 端点处理 - 会话 ID 可选:服务器可以选择分配
Mcp-Session-Id来维护状态,也可以完全无状态运行 | 维度 | stdio | SSE(旧) | Streamable HTTP(新 ✨) | | --- | --- | --- | --- | | 连接模型 | 子进程长连接 | HTTP+SSE 双通道长连接 | 普通 HTTP + 可选 SSE 流 | | 端点 | stdin/stdout | /sse + /message 两个端点 | 单一 /mcp 端点 | | 断线恢复 | N/A(本地) | ❌ 不支持 | ✅ 通过 Session-Id 恢复 | | 服务端状态 | 有状态 | 必须有状态(长连接) | 可选:无状态 or 有状态 | | Serverless 友好 | ❌ | ❌(长连接不适合) | ✅ 无状态模式天然适配 | | 中间件兼容 | ❌ | 部分 | ✅ 纯 HTTP,CDN/WAF/API Gateway 全兼容 | | 向后兼容 | N/A | — | ✅ 是 SSE 的渐进升级 | ### Streamable HTTP 的三种运行模式
Streamable HTTP 不是"要么流式要么非流式"的二元选择,它支持三种运行模式,覆盖从最简单到最复杂的所有场景:
① 无状态
不分配 Session-Id。每个请求独立处理。
适合:简单工具服务(如查天气)
运行:Client POST → Server 直接返回 JSON 结果
部署:完美适配 Serverless / 容器弹性扩缩
② 无状态 + 流式
不分配 Session-Id,但响应升级为 SSE 流。
适合:需要进度反馈的工具
运行:Client POST → Server 返回 SSE 流 → 发送进度通知 → 发送最终结果 → 关闭流
部署:仍然适配 Serverless
③ 有状态
分配 Session-Id,维护会话上下文。
适合:需要多轮交互的复杂工具
运行:Client POST → Server 返回 Session-Id → 后续请求携带该 Id
部署:通过 Redis 等中间件实现会话路由,支持水平扩展
Streamable HTTP 交互示例(无状态 + 流式模式)
// ===== 1. Client 发起工具调用 =====
POST /mcp
Content-Type: application/json
Accept: text/event-stream // 声明接受 SSE 流式响应
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "analyze_code",
"arguments": { "file": "main.py" }
}
}
// ===== 2. Server 返回 SSE 流(响应头声明) =====
HTTP/1.1 200 OK
Content-Type: text/event-stream // 响应升级为 SSE
// ===== 3. SSE 流中逐步推送 =====
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":30,"message":"正在分析代码结构..."}}
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":70,"message":"正在检查安全漏洞..."}}
// ===== 4. 最终结果 =====
data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"分析完成:发现3个潜在问题"}]}}
// ===== 5. Server 关闭 SSE 流 =====
(流结束,HTTP 连接关闭)
💡 为什么不用 WebSocket?
MCP 官方曾认真评估过 WebSocket,但最终选择了 Streamable HTTP,原因有三:
- 无状态 RPC 场景:如果每次调用都依赖 WebSocket 长连接,无状态服务就失去了意义——Streamable HTTP 让简单工具只需一次 HTTP POST 就搞定
- 浏览器限制:WebSocket 无法像普通 HTTP 一样附加 Authorization 等请求头,且只有 GET 请求可升级为 WebSocket,POST 需要额外两步
- 兼容性简洁:避免在 MCP 规范中引入 WebSocket 可选支持,减少客户端与服务器间的兼容性组合爆炸
官方态度:如果将来实践中发现 SSE 确实不够理想,会重新评估 WebSocket。但目前 Streamable HTTP 已经足够好用。
11.4 编写一个 MCP Server
from mcp import Server, Tool
from mcp.types import TextContent
server = Server("my-tools-server")
# 注册工具
@server.tool()
def search_web(query: str) -> list[TextContent]:
"""搜索互联网获取信息
Args:
query: 搜索关键词
"""
results = web_search(query)
return [TextContent(type="text", text=str(results))]
@server.tool()
def get_weather(city: str, unit: str = "celsius") -> list[TextContent]:
"""获取城市天气
Args:
city: 城市名
unit: 温度单位 (celsius/fahrenheit)
"""
temp = weather_api(city, unit)
return [TextContent(type="text", text=f"{city}: {temp}°")]
# 注册资源
@server.resource("file://{path}")
def read_file(path: str) -> str:
"""读取本地文件"""
with open(path) as f:
return f.read()
# 启动 Server
if __name__ == "__main__":
server.run(transport="stdio") # stdio 模式
import { Server, Tool } from '@modelcontextprotocol/sdk/server';
import { TextContent } from '@modelcontextprotocol/sdk/types';
const server = new Server('my-tools-server');
// 注册工具
server.tool('search_web', { query: 'string' }, async (args) => {
/** 搜索互联网获取信息 */
const results = webSearch(args.query);
return [{ type: 'text', text: String(results) } as TextContent];
});
server.tool('get_weather', { city: 'string', unit: 'string' }, async (args) => {
/** 获取城市天气 */
const temp = weatherApi(args.city, args.unit || 'celsius');
return [{ type: 'text', text: `${args.city}: ${temp}°` } as TextContent];
});
// 注册资源
server.resource('file://{path}', async (uri) => {
/** 读取本地文件 */
const fs = await import('fs/promises');
return await fs.readFile(uri.replace('file://', ''), 'utf-8');
});
// 启动 Server
server.run({ transport: 'stdio' }); // stdio 模式
package main
import (
"context"
"fmt"
"os"
"os/exec"
)
// MCP Server: 最简MCP Server (Go示意)
// Go MCP SDK: github.com/mark3labs/mcp-go
func main() {
// 注册工具
registerTool("search_web", searchWeb)
registerTool("get_weather", getWeather)
// 注册资源
registerResource("file://{path}", readFile)
// 启动Server (stdio模式)
runStdio()
}
func searchWeb(ctx context.Context, args map[string]interface{}) (string, error) {
query := args["query"].(string)
results := webSearch(query)
return fmt.Sprintf("%v", results), nil
}
func getWeather(ctx context.Context, args map[string]interface{}) (string, error) {
city := args["city"].(string)
unit := "celsius"
if u, ok := args["unit"]; ok { unit = u.(string) }
temp := weatherApi(city, unit)
return fmt.Sprintf("%s: %d°", city, temp), nil
}
func readFile(ctx context.Context, path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil { return "", err }
return string(data), nil
}
func runStdio() {
// stdio传输:通过os.Stdin/os.Stdout通信
_ = exec.Command("echo", "MCP Server running on stdio")
}
func webSearch(query string) []string { return []string{"result1", "result2"} }
func weatherApi(city, unit string) int { return 25 }
func registerTool(name string, handler interface{}) {}
func registerResource(pattern string, handler interface{}) {}
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.McpSchema.*;
import java.util.*;
public class MyToolsServer {
public static void main(String[] args) {
// 注册工具
var toolSpec1 = new Tool("search_web", "搜索互联网获取信息",
Map.of("type", "object",
"properties", Map.of("query", Map.of("type", "string")),
"required", List.of("query")));
var toolSpec2 = new Tool("get_weather", "获取城市天气",
Map.of("type", "object",
"properties", Map.of(
"city", Map.of("type", "string"),
"unit", Map.of("type", "string")),
"required", List.of("city")));
var server = McpServer.createServer()
.withTool(toolSpec1, (exchange, req) -> {
String query = (String) req.get("query");
String results = webSearch(query);
return new CallToolResult(List.of(
new Content("text", results)));
})
.withTool(toolSpec2, (exchange, req) -> {
String city = (String) req.get("city");
String unit = (String) req.getOrDefault("unit", "celsius");
int temp = weatherApi(city, unit);
return new CallToolResult(List.of(
new Content("text", city + ": " + temp + "°")));
})
.build();
// 启动 Server (stdio模式)
server.run(new StdioServerTransport());
}
static String webSearch(String query) { return "搜索结果..."; }
static int weatherApi(String city, String unit) { return 25; }
}
关键点:这个 MCP Server 可以被任何支持 MCP 的 Agent 使用——Claude Desktop、Cursor、LangChain、OpenClaw 等。写一次,到处用。
11.5 MCP 生态现状
🌐 谁在支持 MCP? | 角色 | 支持者 | 状态 | | --- | --- | --- | | Client(消费方) | Claude Desktop, Cursor, Windsurf, Cline | ✅ 已支持 | | Server(提供方) | GitHub, Slack, Google Drive, PostgreSQL... | ✅ 100+ 官方/社区 Server | | 框架集成 | LangChain, Spring AI, ADK, OpenClaw | ✅ 原生支持 | | 标准化 | Anthropic 主导,开放规范 | 📜 开放标准 | ## 11.6 MCP 客户端完整实现
上一节我们写了一个最简版的 MCP Server,但要让 Agent 真正使用 MCP Server 的能力,还需要一个完整的 MCP 客户端来发起连接、发现能力、调用工具。本节展示如何使用官方 mcp Python 库编写一个功能完整的 MCP 客户端,覆盖工具调用、资源读取和提示词获取三大场景。
客户端核心流程:创建连接 → 初始化会话 → 列出能力 → 调用工具/读取资源/获取提示词 → 处理结果。每一步都是异步操作,需要使用 async/await 语法。
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# 1. 配置 Server 启动参数
server_params = StdioServerParameters(
command="python",
args=["mcp_server.py"],
env=None
)
# 2. 建立 stdio 连接并创建会话
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 3. 初始化握手(必须)
await session.initialize()
# 4. 列出可用工具
tools = await session.list_tools()
print("可用工具:", [tool.name for tool in tools.tools])
# 5. 列出可用资源
resources = await session.list_resources()
print("可用资源:", [res.uri for res in resources.resources])
# 6. 调用工具
result = await session.call_tool(
"search_web",
arguments={"query": "AI Agent MCP protocol", "max_results": 5}
)
print("搜索结果:", result.content)
# 7. 读取资源
content = await session.read_resource("file:///docs/api.md")
print("资源内容:", content.contents[0].text[:200])
# 8. 获取提示词模板
prompt = await session.get_prompt(
"code_review",
arguments={"language": "python", "code": "def add(a,b): return a+b"}
)
print("提示词:", prompt.messages[0].content.text)
asyncio.run(main())
import { Client } from '@modelcontextprotocol/sdk/client';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio';
async function main() {
// 1. 配置Server启动参数
const transport = new StdioClientTransport({
command: 'python',
args: ['mcp_server.py'],
});
// 2. 建立连接并创建会话
const client = new Client(
{ name: 'my-agent', version: '1.0.0' },
{ capabilities: {} }
);
await client.connect(transport);
// 3. 初始化握手(自动完成)
// 4. 列出可用工具
const tools = await client.listTools();
console.log('可用工具:', tools.tools.map(t => t.name));
// 5. 列出可用资源
const resources = await client.listResources();
console.log('可用资源:', resources.resources.map(r => r.uri));
// 6. 调用工具
const result = await client.callTool({
name: 'search_web',
arguments: { query: 'AI Agent MCP protocol', max_results: 5 },
});
console.log('搜索结果:', result.content);
// 7. 读取资源
const content = await client.readResource('file:///docs/api.md');
console.log('资源内容:', content.contents[0].text.substring(0, 200));
// 8. 获取提示词模板
const prompt = await client.getPrompt('code_review', {
language: 'python', code: 'def add(a,b): return a+b',
});
console.log('提示词:', prompt.messages[0].content.text);
await client.close();
}
main();
package main
import (
"context"
"fmt"
"os/exec"
)
// MCP Client: 完整MCP客户端 (Go示意)
// Go MCP SDK: github.com/mark3labs/mcp-go
func main() {
ctx := context.Background()
// 1. 配置Server启动参数
cmd := exec.Command("python", "mcp_server.py")
// 2. 建立stdio连接
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
cmd.Start()
// 3. 初始化握手
client := NewMCPClient(stdin, stdout)
client.Initialize(ctx)
// 4. 列出可用工具
tools, _ := client.ListTools(ctx)
fmt.Print("可用工具: ")
for _, t := range tools {
fmt.Printf("%s ", t.Name)
}
fmt.Println()
// 5. 列出可用资源
resources, _ := client.ListResources(ctx)
fmt.Print("可用资源: ")
for _, r := range resources {
fmt.Printf("%s ", r.URI)
}
fmt.Println()
// 6. 调用工具
result, _ := client.CallTool(ctx, "search_web",
map[string]interface{}{"query": "AI Agent MCP protocol", "max_results": 5})
fmt.Println("搜索结果:", result)
// 7. 读取资源
content, _ := client.ReadResource(ctx, "file:///docs/api.md")
fmt.Println("资源内容:", content[:200])
// 8. 获取提示词模板
prompt, _ := client.GetPrompt(ctx, "code_review",
map[string]string{"language": "python", "code": "def add(a,b): return a+b"})
fmt.Println("提示词:", prompt)
cmd.Wait()
}
// 简化的MCP Client结构
type MCPClient struct { stdin, stdout interface{} }
func NewMCPClient(stdin, stdout interface{}) *MCPClient { return &MCPClient{stdin, stdout} }
func (c *MCPClient) Initialize(ctx context.Context) {}
func (c *MCPClient) ListTools(ctx context.Context) ([]Tool, error) { return []Tool{}, nil }
func (c *MCPClient) ListResources(ctx context.Context) ([]Resource, error) { return []Resource{}, nil }
func (c *MCPClient) CallTool(ctx context.Context, name string, args map[string]interface{}) (string, error) { return "", nil }
func (c *MCPClient) ReadResource(ctx context.Context, uri string) (string, error) { return "", nil }
func (c *MCPClient) GetPrompt(ctx context.Context, name string, args map[string]string) (string, error) { return "", nil }
type Tool struct{ Name string }
type Resource struct{ URI string }
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.*;
import java.util.*;
public class McpClientExample {
public static void main(String[] args) {
// 1. 配置Server启动参数
var transport = new StdioClientTransport.Builder()
.command("python")
.args("mcp_server.py")
.build();
// 2. 建立连接并创建会话
var client = McpClient.create(transport)
.clientInfo(new ClientInfo("my-agent", "1.0.0"))
.build();
// 3. 初始化握手
client.initialize();
// 4. 列出可用工具
ListToolsResult tools = client.listTools();
System.out.println("可用工具: " +
tools.tools().stream().map(t -> t.name()).toList());
// 5. 列出可用资源
ListResourcesResult resources = client.listResources();
System.out.println("可用资源: " +
resources.resources().stream().map(r -> r.uri()).toList());
// 6. 调用工具
CallToolResult result = client.callTool(
new CallToolRequest("search_web",
Map.of("query", "AI Agent MCP protocol", "max_results", 5)));
System.out.println("搜索结果: " + result.content());
// 7. 读取资源
ReadResourceResult content = client.readResource("file:///docs/api.md");
System.out.println("资源内容: " +
content.contents().get(0).text().substring(0, 200));
// 8. 获取提示词模板
GetPromptResult prompt = client.getPrompt(
new GetPromptRequest("code_review",
Map.of("language", "python", "code", "def add(a,b): return a+b")));
System.out.println("提示词: " +
prompt.messages().get(0).content().text());
client.close();
}
}
这段代码完整展示了 MCP 客户端的生命周期:通过 StdioServerParameters 指定 Server 的启动命令和参数,stdio_client 建立 stdio 传输通道,ClientSession 管理协议层会话。session.initialize() 完成能力协商握手,之后即可自由调用 list_tools、call_tool、read_resource、get_prompt 等方法。
注意:session.initialize() 是必须的第一步,它会完成 JSON-RPC 握手和能力协商。跳过这一步直接调用工具会报错。客户端和服务器端必须使用相同的传输方式(stdio、SSE 或 Streamable HTTP)。
11.7 MCP Server 完整实现
6.4 节展示了一个最简版的 Server,但生产级的 MCP Server 需要同时实现资源(Resources)、工具(Tools)和提示词(Prompts)三类能力,并正确处理生命周期。本节展示一个完整的天气数据 MCP Server,包含资源定义、工具注册和错误处理。
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
app = Server("weather-data-server")
# ===== 资源:只读数据 =====
@app.list_resources()
async def handle_list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="weather://forecast/beijing",
name="北京天气预报",
description="北京未来7天天气",
mimeType="application/json"
),
types.Resource(
uri="weather://forecast/shanghai",
name="上海天气预报",
description="上海未来7天天气",
mimeType="application/json"
)
]
@app.read_resource()
async def handle_read_resource(uri: str) -> str:
if uri == "weather://forecast/beijing":
return '{"city":"北京","temp":25,"weather":"晴"}'
elif uri == "weather://forecast/shanghai":
return '{"city":"上海","temp":28,"weather":"多云"}'
raise ValueError(f"未知资源: {uri}")
# ===== 工具:可调用的函数 =====
@app.list_tools()
async def handle_list_tools() -> list[types.Tool]:
return [
types.Tool(
name="get_weather",
description="获取指定城市的实时天气",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
)
]
@app.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "get_weather":
city = arguments.get("city", "北京")
return [types.TextContent(
type="text",
text=f"{{'city':'{city}','temp':25,'humidity':60}}"
)]
raise ValueError(f"未知工具: {name}")
# ===== 启动 Server =====
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="weather-data-server",
server_version="1.0.0",
capabilities=app.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={}
)
)
)
asyncio.run(main())
import { Server } from '@modelcontextprotocol/sdk/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
import { Resource, Tool, TextContent } from '@modelcontextprotocol/sdk/types';
const app = new Server('weather-data-server', '1.0.0');
// ===== 资源:只读数据 =====
app.listResources(async () => [
{
uri: 'weather://forecast/beijing',
name: '北京天气预报',
description: '北京未来7天天气',
mimeType: 'application/json',
},
{
uri: 'weather://forecast/shanghai',
name: '上海天气预报',
description: '上海未来7天天气',
mimeType: 'application/json',
},
]);
app.readResource(async (uri) => {
if (uri === 'weather://forecast/beijing') {
return '{"city":"北京","temp":25,"weather":"晴"}';
} else if (uri === 'weather://forecast/shanghai') {
return '{"city":"上海","temp":28,"weather":"多云"}';
}
throw new Error(`未知资源: ${uri}`);
});
// ===== 工具:可调用的函数 =====
app.listTools(async () => [
{
name: 'get_weather',
description: '获取指定城市的实时天气',
inputSchema: {
type: 'object',
properties: { city: { type: 'string', description: '城市名称' } },
required: ['city'],
},
},
]);
app.callTool(async (name, args) => {
if (name === 'get_weather') {
const city = (args.city as string) || '北京';
return [{
type: 'text',
text: `{"city":"${city}","temp":25,"humidity":60}`,
} as TextContent];
}
throw new Error(`未知工具: ${name}`);
});
// ===== 启动Server =====
const transport = new StdioServerTransport();
await app.connect(transport);
package main
import (
"context"
"fmt"
"os"
)
// MCP Server: 完整MCP Server (Go示意)
// Go MCP SDK: github.com/mark3labs/mcp-go
type Resource struct {
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description"`
MimeType string `json:"mimeType"`
}
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]interface{} `json:"inputSchema"`
}
func main() {
ctx := context.Background()
// ===== 资源:只读数据 =====
resources := []Resource{
{URI: "weather://forecast/beijing", Name: "北京天气预报",
Description: "北京未来7天天气", MimeType: "application/json"},
{URI: "weather://forecast/shanghai", Name: "上海天气预报",
Description: "上海未来7天天气", MimeType: "application/json"},
}
// ===== 工具:可调用的函数 =====
tools := []Tool{
{Name: "get_weather", Description: "获取指定城市的实时天气",
InputSchema: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]interface{}{"type": "string", "description": "城市名称"},
},
"required": []string{"city"},
}},
}
// 注册处理器
registerHandlers(resources, tools, handleReadResource, handleCallTool)
// ===== 启动Server (stdio) =====
runStdio(ctx)
}
func handleReadResource(ctx context.Context, uri string) (string, error) {
switch uri {
case "weather://forecast/beijing":
return `{"city":"北京","temp":25,"weather":"晴"}`, nil
case "weather://forecast/shanghai":
return `{"city":"上海","temp":28,"weather":"多云"}`, nil
default:
return "", fmt.Errorf("未知资源: %s", uri)
}
}
func handleCallTool(ctx context.Context, name string, args map[string]interface{}) (string, error) {
if name == "get_weather" {
city, _ := args["city"].(string)
if city == "" { city = "北京" }
return fmt.Sprintf(`{"city":"%s","temp":25,"humidity":60}`, city), nil
}
return "", fmt.Errorf("未知工具: %s", name)
}
func registerHandlers(res []Resource, tools []Tool, rrf func(context.Context, string) (string, error),
ctf func(context.Context, string, map[string]interface{}) (string, error)) {
_ = res; _ = tools; _ = rrf; _ = ctf
}
func runStdio(ctx context.Context) {
// stdio传输:通过os.Stdin/os.Stdout通信
_ = os.Stdin; _ = os.Stdout
}
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures.*;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.McpSchema.*;
import java.util.*;
public class WeatherDataServer {
public static void main(String[] args) {
// ===== 资源:只读数据 =====
var resourceSpec1 = new ResourceSpec(
"weather://forecast/beijing", "北京天气预报",
"北京未来7天天气", "application/json");
var resourceSpec2 = new ResourceSpec(
"weather://forecast/shanghai", "上海天气预报",
"上海未来7天天气", "application/json");
// ===== 工具:可调用的函数 =====
var toolSpec = new ToolSpec("get_weather", "获取指定城市的实时天气",
Map.of("type", "object",
"properties", Map.of("city",
Map.of("type", "string", "description", "城市名称")),
"required", List.of("city")));
var server = McpServer.createServer()
.withResource(resourceSpec1, (exchange, uri) -> {
if ("weather://forecast/beijing".equals(uri)) {
return new ReadResourceResult(
List.of(new ResourceContent(uri, "{\"city\":\"北京\",\"temp\":25,\"weather\":\"晴\"}", "application/json")));
} else if ("weather://forecast/shanghai".equals(uri)) {
return new ReadResourceResult(
List.of(new ResourceContent(uri, "{\"city\":\"上海\",\"temp\":28,\"weather\":\"多云\"}", "application/json")));
}
throw new IllegalArgumentException("未知资源: " + uri);
})
.withResource(resourceSpec2, (exchange, uri) -> {
// Same handler logic
return new ReadResourceResult(List.of());
})
.withTool(toolSpec, (exchange, request) -> {
String city = (String) request.getOrDefault("city", "北京");
return new CallToolResult(List.of(
new Content("text",
"{\"city\":\"" + city + "\",\"temp\":25,\"humidity\":60}")));
})
.build();
// ===== 启动Server =====
server.run(new StdioServerTransport());
}
}
这个 Server 实现了 MCP 的三大核心能力:Resources 通过 @app.list_resources() 和 @app.read_resource() 暴露天气数据;Tools 通过 @app.list_tools() 和 @app.call_tool() 提供天气查询函数;InitializationOptions 声明服务器能力和版本信息,供客户端在握手阶段读取。
架构要点:Server 使用装饰器模式注册处理器,每个处理器对应一个 JSON-RPC 方法。app.run() 启动事件循环后,所有通信通过 stdio 流自动完成。Client 不需要知道 Server 内部实现,只需通过标准协议调用即可。
11.8 MCP 协议深度解析
理解 MCP 不能只停留在 API 使用层面,还需要深入协议底层。本节从 JSON-RPC 2.0 报文格式、能力协商流程、协议栈分层架构三个维度,带你彻底理解 MCP 的通信本质。
JSON-RPC 2.0 报文结构
MCP 选择 JSON-RPC 2.0 作为底层协议,因为它轻量、无状态、支持双向通信。每条消息都是一个 JSON 对象,包含 jsonrpc 版本号、id(请求标识)、method(方法名)和 params(参数)。服务端可以通过 notifications/* 方法主动推送通知,不需要客户端轮询。
JSON-RPC 2.0: 能力协商完整握手流程
// ===== 1. Client → Server: 初始化请求 =====
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": { "name": "my-agent", "version": "1.0.0" }
}
}
// ===== 2. Server → Client: 返回能力 =====
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true }
},
"serverInfo": { "name": "weather-server", "version": "1.0.0" }
}
}
// ===== 3. Client → Server: 确认初始化完成 =====
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
// ===== 4. Client → Server: 列出工具 =====
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
// ===== 5. Server → Client: 返回工具列表 =====
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [{
"name": "get_weather",
"description": "获取城市天气",
"inputSchema": { "type": "object", "properties": { "city": { "type": "string" } } }
}]
}
}
能力协商(Capabilities Negotiation)
能力协商是 MCP 握手的核心环节。Client 和 Server 在 initialize 阶段各自声明自己支持的能力集合,双方取交集后确定可用功能。这意味着 Client 可以声明支持 roots(文件系统根目录)和 sampling(让 Server 请求 LLM 推理),Server 可以声明支持 tools、resources、prompts 中的一项或多项。协商完成后,双方只使用对方支持的能力,避免调用不存在的功能导致错误。
📊 MCP vs Function Calling vs OpenAPI 对比 | 维度 | MCP | Function Calling | OpenAPI | | --- | --- | --- | --- | | 设计目标 | Agent 与工具/数据连接 | LLM 调用外部函数 | REST API 描述规范 | | 通信协议 | JSON-RPC 2.0 | LLM 厂商自定义 | HTTP/HTTPS | | 运行方式 | 独立 Server 进程 | 框架内嵌函数 | 独立 HTTP 服务 | | 能力发现 | 运行时动态发现 | 编译时静态注册 | 静态文档/Swagger UI | | 支持能力类型 | Tools + Resources + Prompts | 仅函数调用 | HTTP 端点 | | 双向通信 | ✅ 支持(通知/采样) | ❌ 单向调用 | ❌ 请求-响应 | | 跨框架复用 | ✅ 写一次到处用 | ❌ 每框架重写 | ⚠️ 需手动适配 | ### MCP 协议栈分层架构
MCP 协议采用分层设计,每一层职责清晰:传输层负责字节流的收发,协议层负责 JSON-RPC 消息的序列化与路由,能力层负责 Tools/Resources/Prompts 的具体语义处理。这种分层设计使得 MCP 可以在不改变上层逻辑的前提下,灵活替换传输方式(stdio → SSE → Streamable HTTP)。
MCP 协议栈分层架构
应用层 (Application Layer) Agent / LLM — 决策调用、结果处理
能力层 (Capability Layer) Tools · Resources · Prompts — 三大能力的语义处理
协议层 (Protocol Layer) JSON-RPC 2.0 — 消息序列化、请求路由、能力协商
传输层 (Transport Layer) stdio · SSE · Streamable HTTP — 字节流收发
Client 双方 双方 Client/Server Host 共享 共享 两端
每层可独立替换 · 传输层从 stdio 换成 Streamable HTTP 不影响上层逻辑
MCP 协议握手流程
下图动态展示了 MCP 客户端与服务器之间从建立连接到调用工具的完整握手流程。整个过程严格遵循 JSON-RPC 2.0 规范,每个步骤都有明确的请求和响应格式。
MCP 协议握手流程
MCP Client
MCP Server
① initialize (协议版本 + 客户端能力)
② result (服务器能力 + 版本信息)
③ notifications/initialized (确认)
④ tools/list → ⑤ tools/call (实际调用)
握手阶段
确认
调用阶段
握手三阶段:初始化阶段(① ②)—— Client 发送 initialize 请求,携带自己支持的协议版本和能力;Server 返回自己支持的能力集合。确认阶段(③)—— Client 发送 notifications/initialized 通知,表示握手完成。调用阶段(④ ⑤)—— 双方开始正常的方法调用,如 tools/list、tools/call、resources/read 等。
11.9 MCP Gateway 与路由
当 Agent 需要同时连接多个 MCP Server 时,直接管理每个连接会变得复杂:不同 Server 的健康状态、版本兼容、负载分布都需要逐一处理。MCP Gateway 正是为了解决这个问题而诞生的——它作为统一入口,代理所有 MCP Server 的连接和调度。
MCP Gateway 就像 Agent 世界中的 API Gateway——一个入口管所有后端,路由、鉴权、限流、负载均衡一站搞定。
Gateway 的核心职责
统一接入
Agent 只需连接 Gateway 一个端点,Gateway 代理转发到所有后端 MCP Server。连接数从 N 降到 1。
智能路由
根据工具类型、服务优先级、负载情况选择最优 Server。避免单点过载,提升响应速度。
安全管控
集中鉴权、限流、审计日志。Agent 不直连后端 Server,安全边界统一收口到 Gateway。
Gateway 架构流程图
三种路由策略 | 策略 | 原理 | 适用场景 | 示例 | | --- | --- | --- | --- | | 按工具类型路由 | 根据工具名称/类别,将请求分发到对应专长的 Server | 工具职责明确、分类清晰 | search_* → 搜索Server;db_* → 数据库Server | | 按服务优先级路由 | 配置 Server 优先级列表,优先使用高优先级 Server,降级到低优先级 | 有主备关系的 Server 集群 | 优先官方Server → 降级到社区Server | | 按负载均衡路由 | 根据各 Server 的当前负载、响应时间动态选择最空闲的 Server | 同质 Server 多实例部署 | 3个相同DB Server,轮询/最少连接分配 | ### 配置 MCP Gateway
YAML: MCP Gateway 配置示例
# mcp-gateway-config.yaml
gateway:
name: "my-agent-gateway"
listen: "0.0.0.0:8080" # Gateway 监听地址
# 路由策略配置
routing:
strategy: "tool-type" # tool-type | priority | load-balance
# 按工具类型路由规则
tool_type_rules:
- pattern: "search_*"
servers: ["search-server-primary", "search-server-backup"]
- pattern: "db_*"
servers: ["postgres-server", "mysql-server"]
- pattern: "file_*"
servers: ["filesystem-server"]
- pattern: "*"
servers: ["general-server"] # 默认兜底
# 按优先级路由(tool-type 匹配多Server时)
priority_rules:
search_server_primary: 1 # 最高优先级
search_server_backup: 2 # 降级备选
postgres_server: 1
mysql_server: 2
# 负载均衡配置(相同Server多实例时)
load_balance:
algorithm: "least-connections" # round-robin | least-connections | weighted
health_check_interval: 30 # 健康检查间隔(秒)
retry_count: 2 # 失败重试次数
# 后端 MCP Server 列表
servers:
- name: "search-server-primary"
transport: "sse"
url: "http://search-server:3001/mcp"
timeout: 5000
- name: "search-server-backup"
transport: "sse"
url: "http://search-server-backup:3001/mcp"
timeout: 5000
- name: "postgres-server"
transport: "stdio"
command: "python"
args: ["postgres_mcp_server.py"]
- name: "filesystem-server"
transport: "stdio"
command: "npx"
args: ["-y", "@anthropic/mcp-server-filesystem", "/home/user/docs"]
- name: "general-server"
transport: "sse"
url: "http://general-server:3002/mcp"
timeout: 10000
关键设计:Gateway 配置支持三种路由策略可组合使用——先按工具类型分流到对应 Server 组,再在组内按优先级或负载均衡选择具体实例。健康检查确保自动剔除不可用 Server,重试机制保障请求成功率。
11.10 MCP 市场与 Skills 生态
MCP 不仅仅是技术协议,它催生了一个完整的工具生态系统。截至 2025-2026 年,MCP Server 的月下载量已突破 9700 万至 1.1 亿次,Skills(技能包)生态超过 8.5 万个。这意味着 MCP 正在从技术标准走向产业生态——就像 USB-C 不只是接口规格,更催生了整个配件市场。
数据亮点:MCP 月下载量 9700 万-1.1 亿 | Skills 生态超 8.5 万个 | 主要平台 SkillsMP(40万+ skills)、Skills.sh、ClawHub、LobeHub Skills。
三大架构角色
MCP 生态中,Agent、Skills、MCP Server 三者形成清晰的分工体系: 🏗️ Agent × Skills × MCP 三大架构角色
Agent 决策中枢
Agent 是大脑——负责理解用户意图、规划任务步骤、选择合适的 Skills 和 MCP Server 执行。类似公司的 CEO,决策但不亲手执行。
Skills 专业技能库
Skills 是专业能力包——封装了特定领域的知识、流程和工具调用组合。如「PDF处理技能」「邮件技能」「数据分析技能」。类似公司的专业团队。
MCP 标准化接口
MCP Server 是标准化工具接口——提供具体的原子能力(搜索、读写、计算)。Skills 组合多个 MCP Server 的原子能力完成复杂任务。类似公司的基础设施部门。
层级关系:Agent 调用 Skills → Skills 组合 MCP Server → MCP Server 执行原子操作。三层分工清晰:决策层、编排层、执行层。这与传统软件架构中的 Controller → Service → DAO 分层逻辑一致。
主要 Skills 市场平台 | 平台 | Skills 数量 | 特色 | 定位 | | --- | --- | --- | --- | | SkillsMP | 40 万+ | 品类最全,覆盖办公、开发、数据分析等全场景 | 综合型 Skills 超市 | | Skills.sh | 数万+ | 开发者友好,CLI 一键安装,强调可复用性 | 开发者工具集市 | | ClawHub | 数万+ | OpenClaw 官方市场,与 OpenClaw Agent 深度集成 | Agent 原生 Skills 市场 | | LobeHub Skills | 数千+ | 开源社区驱动,与 LobeChat 深度整合 | 开源 Skills 社区 | ### 市场生态流程图
MCP Skills 市场的运作遵循一个清晰的三方流转模型:开发者上架 → 平台审核 → Agent 安装使用。这个流程与传统 App Store 模式有相似之处,但本质区别在于 Skills 是能力包而非独立应用。
与传统 App Store 模式的对比 | 维度 | App Store 模式 | Skills 市场模式 | | --- | --- | --- | | 交付物 | 独立应用(完整UI+逻辑+数据) | 能力包(知识+流程+工具组合) | | 运行方式 | 独立进程,用户直接操作 | Agent 内嵌调用,用户间接使用 | | 组合性 | 应用间组合困难(沙箱隔离) | Skills 可自由组合编排(共享Agent上下文) | | 接口标准 | 平台SDK(iOS SDK/Android SDK) | MCP 协议(跨平台统一标准) | | 审核重点 | 隐私合规、内容安全、用户体验 | 权限边界、数据安全、MCP 兼容性 | | 商业模式 | 付费下载/订阅/广告 | 开源免费/企业定制/API调用计费 | 本质区别:App Store 卖的是产品——用户直接使用的完整应用;Skills 市场卖的是能力——Agent 组合使用的专业技能包。前者面向终端用户,后者面向 Agent(代理用户)。这意味着 Skills 市场更注重可组合性和标准化接口,而非独立的用户体验。
11.11 MCP 安全与权限管理
MCP Server 拥有强大的能力——读取文件、访问数据库、调用 API、发送邮件。这些能力如果不受管控,可能成为安全漏洞的入口。MCP 安全与权限管理的核心目标是:在赋予 Agent 能力的同时,划定明确的安全边界,确保每一次工具调用都在可控范围内。
风险警示:一个没有权限管控的 MCP Server 就像一扇没有锁的门。恶意 Prompt 可以通过 Agent 间接调用 MCP Server 删除文件、泄露数据、执行危险操作。权限管理不是可选的,是必须的。
MCP Server 的权限边界
每个 MCP Server 应声明自己的权限边界——它能做什么、不能做什么、在什么条件下可以做。这通过 Server 的 capabilities 声明和 permissions 配置来实现。
🔒 四类权限边界 | 权限类型 | 含义 | 示例 | | --- | --- | --- | | 作用域限制(Scope) | 限定工具可操作的数据范围 | 文件Server只能访问 /home/user/docs 目录 | | 操作类型限制(Action) | 限定工具可执行的操作类型 | 数据库Server只允许SELECT,禁止DROP/DELETE | | 调用频率限制(Rate Limit) | 限定工具的调用频率上限 | 邮件Server每分钟最多发送5封 | | 敏感操作标记(Sensitive) | 标记需要二次确认的危险操作 | 删除文件、转账、发布内容需人工确认 | ### OAuth 2.0 认证集成
当 MCP Server 需要访问第三方服务(如 GitHub、Google Drive、Slack)时,OAuth 2.0 是标准的认证授权机制。MCP 协议规范在 2025 年的更新中正式加入了 OAuth 2.0 支持——Client 与 Server 之间的握手可以包含 OAuth 认证流程。
MCP Server: OAuth 2.0 认证配置
# OAuth 2.0 配置示例(MCP Server 端)
oauth:
enabled: true
provider: "github" # 认证提供方
# GitHub OAuth 配置
github:
client_id: "Ov23li1234567890"
client_secret: "${GITHUB_CLIENT_SECRET}" # 从环境变量读取
authorization_url: "https://github.com/login/oauth/authorize"
token_url: "https://github.com/login/oauth/access_token"
scopes:
- "repo" # 仓库读写权限
- "read:org" # 组织信息读取
- "user:email" # 用户邮箱读取
# Token 生命周期管理
token:
refresh_enabled: true # 支持刷新Token
expiry: 3600 # Token有效期(秒)
refresh_expiry: 86400 # 刷新Token有效期
# 权限映射:OAuth Scope → MCP 工具权限
scope_mapping:
"repo":
- "tools/repo_read"
- "tools/repo_write"
"read:org":
- "tools/org_list"
- "tools/org_member_read"
"user:email":
- "tools/user_info"
工具调用审计日志
审计日志是 MCP 安全的重要保障——每一次工具调用都应被完整记录,包括调用者、时间、工具名、参数、返回结果。这不仅用于事后追溯,还能发现异常调用模式(如频繁调用敏感工具、参数包含可疑内容)。
MCP Gateway: 审计日志配置与格式
# 审计日志配置
audit:
enabled: true
storage: "postgresql" # postgresql | elasticsearch | file
retention_days: 90 # 日志保留天数
# 记录粒度
log_level: "full" # minimal | standard | full
# minimal: 仅记录工具名和调用时间
# standard: 记录工具名、参数摘要、返回状态
# full: 记录完整参数、完整返回、耗时、调用链
# 敏感字段脱敏
sanitization:
fields: ["password", "token", "secret", "api_key", "credit_card"]
strategy: "mask" # mask | hash | remove
mask_pattern: "***REDACTED***"
# ===== 审计日志条目格式 =====
{
"audit_id": "aud-20250701-001",
"timestamp": "2025-07-01T10:30:00.000Z",
"client_info": {
"agent_name": "my-assistant",
"session_id": "sess-abc123",
"user_id": "user-001"
},
"tool_call": {
"server_name": "github-mcp-server",
"tool_name": "create_issue",
"arguments": {
"repo": "org/main-project",
"title": "Bug: login page crashes",
"body": "When clicking login button..."
},
"result_status": "success",
"result_summary": "Created issue #1234"
},
"security": {
"oauth_scope_used": ["repo"],
"sensitive_flag": false,
"human_confirmed": false,
"rate_limit_remaining": 48
},
"performance": {
"latency_ms": 234,
"retry_count": 0
}
}
敏感操作的二次确认机制
某些 MCP 工具的操作后果是不可逆的——删除文件、转账、发布内容、修改数据库。这些操作需要二次确认机制:Agent 调用工具时,Gateway 或 Server 拦截请求,暂停执行,向用户发送确认请求,只有用户明确同意后才继续执行。
YAML: 敏感操作二次确认配置
# 敏感操作二次确认配置
confirmation:
enabled: true
# 需要二次确认的工具列表
sensitive_tools:
- name: "file_delete"
level: "critical" # critical | high | medium
message: "即将删除文件,此操作不可恢复。是否确认?"
timeout: 300 # 确认超时(秒),超时自动取消
require_explicit: true # 需要用户明确输入"确认"而非默认同意
- name: "db_drop_table"
level: "critical"
message: "即将删除数据库表,所有数据将丢失。是否确认?"
timeout: 300
require_explicit: true
- name: "send_email"
level: "high"
message: "即将发送邮件到 {{recipients}},是否确认?"
timeout: 60
- name: "git_push"
level: "medium"
message: "即将推送到 {{remote}}/{{branch}},是否确认?"
timeout: 30
- name: "payment_transfer"
level: "critical"
message: "即将转账 {{amount}} 元至 {{account}},是否确认?"
timeout: 300
require_explicit: true
# 通知渠道配置
notification:
channels: ["web_push", "email", "sms"]
# critical级别: web_push + email + sms 全渠道通知
# high级别: web_push + email
# medium级别: web_push
critical_channels: ["web_push", "email", "sms"]
high_channels: ["web_push", "email"]
medium_channels: ["web_push"]
安全三原则:① 最小权限原则——每个 MCP Server 只申请它必需的权限,不多给一个。② 默认拒绝原则——未经明确授权的操作默认拒绝,而非默认允许。③ 可审计原则——每一次调用都有日志,每一次异常都可追溯。这三条原则构成了 MCP 安全的基石。
🔗 关于 CLI 工具与 MCP 的关系
除了 Function Calling 和 MCP,Agent 还有第三种手脚——CLI 工具(git、docker、kubectl 等)。CLI vs MCP vs Function Calling 的对比、注册系统、懒加载策略等详细内容,请参见第13章「CLI 能力:Agent 操作本地工具」,那里有更完整的讲解。 📋 八股总结 — 面试高频考点
Q1: MCP 解决了什么问题?
问题:N 个 Agent 框架 × M 个工具 = N×M 个适配代码。每个框架有自己的工具定义方式,工具开发者要为每个框架写适配。
解决:MCP 提供统一标准协议。工具开发者只需实现一次 MCP Server,所有支持 MCP 的 Agent 都能用。N×M 变成 N+M。
类比:USB-C 接口——一个标准,所有设备通用。
Q2: MCP 的三个核心概念是什么?
① Tools(工具):可被 LLM 调用的函数。如搜索、查询数据库。类似标准化的 Function Calling。
② Resources(资源):可被 LLM 读取的只读数据。如文件内容、数据库记录。
③ Prompts(提示模板):预定义的 Prompt 模板。Server 提供专业提示,Client 直接使用。
Q3: MCP 的三种传输方式是什么?各有什么特点?
① stdio(标准输入输出):Server 作为子进程运行。
- 优点:简单、零网络开销
- 缺点:不能跨机器
- 适合:本地工具
② SSE(Server-Sent Events)⚠ 即将废弃:HTTP + SSE 双通道长连接。
- 优点:跨机器
- 缺点:必须维持长连接、断线不可恢复
- 适合:远程工具(旧方案)
③ Streamable HTTP ✨ 推荐:2025-03-26 新增,替代 SSE。
- 优点:无需长连接、断线可恢复、Serverless 友好、纯 HTTP 兼容基础设施
- 缺点:无
- 适合:远程工具、云服务、Serverless 部署
Q4: MCP 和 Function Calling 有什么区别?
Function Calling:LLM 厂商定义的调用格式。每个框架要自己实现工具注册和执行。工具与框架耦合。
MCP:跨框架的标准协议。工具作为独立 Server 运行,通过 JSON-RPC 通信。工具与框架解耦。
关系:MCP 不是替代 Function Calling,而是标准化了工具的暴露方式。Agent 内部仍然可以用 Function Calling,但工具来源可以是任何 MCP Server。
Q5: 谁在支持 MCP?它的发展前景如何?
Client 端:Claude Desktop、Cursor、Windsurf、Cline 等已支持。
Server 端:100+ 官方/社区 Server,覆盖 GitHub、Slack、Google Drive、PostgreSQL 等常用服务。
框架集成:LangChain、Spring AI、Google ADK、OpenClaw 等原生支持。
MCP 正在成为 Agent 工具生态的事实标准。Anthropic 主导但开放,类似 HTTP 之于 Web。
Q6: MCP 协议的核心设计理念是什么?
MCP 的核心设计理念是**「M×N 问题转化为 M+N」**。在 MCP 出现前,M 个 Agent 框架和 N 个工具之间需要写 M×N 份适配代码。MCP 通过定义一个标准协议层,让工具开发者只写一次 Server,Agent 框架只实现一次 Client,就能互相通信。这借鉴了 USB-C 的设计哲学——统一接口、即插即用、解耦生产者和消费者。
Q7: MCP 客户端如何发现服务器的可用能力?
通过**能力协商(Capabilities Negotiation)**机制。在 initialize 握手阶段,Client 发送自己的能力声明(如 roots、sampling),Server 返回自己的能力声明(如 tools、resources、prompts)。握手完成后,Client 通过 tools/list、resources/list、prompts/list 方法动态发现具体可用的工具、资源和提示词。整个过程是运行时动态发现的,不需要提前硬编码。
Q8: MCP Server 提供哪三类能力?它们有什么区别?
① Tools(工具):可被 LLM 主动调用的函数,有输入参数和返回值。如搜索、发邮件、查询数据库。是「动词」,执行操作。
② Resources(资源):可被 LLM 读取的只读数据,通过 URI 标识。如文件内容、数据库记录、配置信息。是「名词」,提供上下文。
③ Prompts(提示模板):预定义的 Prompt 模板,接受参数生成完整提示。如代码审查模板、SQL 优化模板。是「模板」,标准化知识传递。
简单记忆:Tools 做事,Resources 读数据,Prompts 给模板。
Q9: MCP 和 Function Calling 的本质区别是什么?
Function Calling 是 LLM 厂商定义的函数调用格式,工具与 Agent 框架紧耦合——每个框架要自己实现工具注册、Schema 定义和执行逻辑,工具不能跨框架复用。
MCP 是跨框架的开放标准协议,工具作为独立进程运行,通过 JSON-RPC 通信,与 Agent 框架完全解耦。MCP 还支持双向通信(Server 可主动推送通知)和运行时能力发现,这些是 Function Calling 不具备的。
关系:MCP 不替代 Function Calling,而是标准化了工具的暴露方式。Agent 内部仍可用 Function Calling 调度,但工具来源可以是任何 MCP Server。
Q10: MCP 的传输层有哪些实现?各自适用什么场景?
① stdio(标准输入输出):Server 作为 Client 的子进程运行,通过 stdin/stdout 通信。零网络开销、最简单,适合本地工具(文件系统、本地数据库)。
② SSE(Server-Sent Events):基于 HTTP + SSE 的双向通信。Client 发 HTTP POST 请求,Server 通过 SSE 流推送响应。支持跨机器,适合远程工具和云服务。
③ 可扩展传输:MCP 的分层设计允许未来增加 WebSocket、gRPC 等传输方式,不影响上层协议逻辑。传输层可插拔是 MCP 架构的重要优势。
Q11: MCP Gateway 解决了什么问题?它的三种路由策略是什么?
问题:当 Agent 同时连接多个 MCP Server 时,直接管理每个连接复杂度高——健康状态、版本兼容、负载分布都要逐一处理。
MCP Gateway:统一入口管理所有 MCP Server。Agent 只需连接 Gateway 一个端点,Gateway 代理转发、智能路由、安全管控。
三种路由策略:
① 按工具类型路由:根据工具名称/类别分发到对应专长的 Server。如 search_* → 搜索Server。
② 按服务优先级路由:优先使用高优先级 Server,故障时降级到低优先级。如官方Server → 社区Server。
③ 按负载均衡路由:根据各 Server 当前负载动态选择最空闲实例。如轮询、最少连接数分配。
Q12: MCP Skills 市场与传统 App Store 有什么本质区别?
App Store 卖的是产品——用户直接使用的完整应用,独立进程运行,应用间组合困难(沙箱隔离),审核关注隐私合规和用户体验。
Skills 市场卖的是能力——Agent 组合使用的专业技能包,内嵌于 Agent 调用,Skills 可自由组合编排(共享 Agent 上下文),审核关注权限边界和 MCP 兼容性。
本质区别:前者面向终端用户(直接交互),后者面向 Agent(代理用户间接使用)。Skills 市场更注重可组合性和标准化接口,而非独立的用户体验。截至 2025-2026 年,MCP 月下载量超 9700 万-1.1 亿,Skills 生态超 8.5 万个。
Q13: MCP 安全管理的三原则是什么?敏感操作为什么要二次确认?
安全三原则:
① 最小权限原则——每个 MCP Server 只申请必需权限,不多给一个。
② 默认拒绝原则——未经明确授权的操作默认拒绝,而非默认允许。
③ 可审计原则——每一次调用都有日志,每一次异常都可追溯。
敏感操作二次确认的原因:
- 某些操作后果不可逆(删除文件、转账、发布内容),一旦执行无法撤销。
- 恶意 Prompt 可能通过 Agent 间接调用 MCP Server 执行危险操作(Prompt Injection 风险)。
- 二次确认机制让用户在执行前有机会审查和拒绝,是 Agent 代理执行的安全护栏。
- 不同敏感级别(critical/high/medium)对应不同的通知渠道和确认要求。