11226 字
约 37 分钟
1
第11章 MCP-工具的标准化接口

第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_toolscall_toolread_resourceget_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 可以声明支持 toolsresourcesprompts 中的一项或多项。协商完成后,双方只使用对方支持的能力,避免调用不存在的功能导致错误。 📊 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/listtools/callresources/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 发送自己的能力声明(如 rootssampling),Server 返回自己的能力声明(如 toolsresourcesprompts)。握手完成后,Client 通过 tools/listresources/listprompts/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)对应不同的通知渠道和确认要求。
第11章 MCP-工具的标准化接口
http://www.clxhxhhr.top/posts/714/
作者
clxstart
发布于
2026-09-18
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。