MCP协议与工具调用
MCP协议与工具调用

MCP协议与工具调用

一、MCP 概述

1.1 什么是 MCP

MCP(Model Context Protocol)是 Anthropic 于 2024 年 11 月提出的开放性协议标准,旨在定义 LLM 应用与外部工具/数据源之间的标准化通信方式。截至 2025 年,MCP 已被广泛采用为 AI Agent 生态的基础设施标准。

1.2 背景:工具调用的演进

在 MCP 出现之前,LLM 工具调用经历了三个混乱阶段:

阶段一(2022前):Prompt Engineering 方式
  在 prompt 中描述工具,用正则解析 LLM 输出

阶段二(2023):OpenAI Function Calling
  OpenAI 定义了 function calling API 格式,但各家实现互不兼容

阶段三(2024+):MCP 标准化
  统一的 JSON-RPC 协议,模型无关、厂商无关

核心矛盾:每个 LLM 平台(OpenAI, Anthropic, Google)都有各自的工具调用格式,每个工具/数据源需要为每个平台单独编写适配器。这导致了组合爆炸——M 个模型 × N 个工具 = M×N 个适配器。

1.3 M×N 问题的数学解释

传统集成: 每个(模型, 工具)对都需要独立适配
  OpenAI × GitHub API = 适配器1
  OpenAI × Slack API  = 适配器2
  Claude × GitHub API = 适配器3
  Claude × Slack API  = 适配器4
  ... (M×N 组合)

MCP 方案: 统一中间层
  OpenAI ─┐          ┌─ GitHub
          ├─ MCP ────┤
  Claude ─┘          └─ Slack
  (M + N 个连接)

1.4 MCP 架构

MCP 采用经典的 Client-Server 架构,以 JSON-RPC 2.0 作为通信协议:

┌──────────────────┐                        ┌──────────────────┐
│   MCP Client     │     JSON-RPC 2.0       │   MCP Server      │
│  (LLM 应用/Host)  │ ◄────────────────────► │  (工具/数据服务)  │
└──────────────────┘                        └──────────────────┘
       │                                              │
  ┌────┴────┐                                  ┌─────┴─────┐
  │ Claude  │                                  │  Database │
  │ GPT     │                                  │  API      │
  │ Gemini  │                                  │  Files    │
  └─────────┘                                  └───────────┘

1.5 MCP 三要素

概念 说明 与 LLM 的交互方式
Tools 可由 LLM 调用执行的函数 LLM 决定调用 → Server 执行 → 返回结果
Resources 可供 LLM 读取的结构化数据 LLM 请求读取 → Server 返回数据
Prompts 预定义的提示模板 Server 提供 → Client 注入到 LLM 对话中

三要素的设计哲学:借鉴了编程语言中的”函数-数据结构-模板”三元组。


二、JSON-RPC 2.0 协议基础

MCP 选择 JSON-RPC 2.0 作为通信协议,这是理解 MCP 底层机制的基础。

2.1 协议核心

JSON-RPC 2.0 是无状态的轻量级远程过程调用协议,定义了四种消息类型:

请求(Request):

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "add",
        "arguments": {"a": 5, "b": 3}
    }
}

响应(Response):

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "content": [{"type": "text", "text": "8"}]
    }
}

错误(Error):

{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": -32601,
        "message": "Method not found",
        "data": "Tool 'divide_by_zero' does not exist"
    }
}

通知(Notification):无 id 字段,不需要响应:

{
    "jsonrpc": "2.0",
    "method": "notifications/initialized"
}

2.2 MCP 核心方法

方法 方向 作用
initialize Client→Server 握手,协商协议版本和能力
tools/list Client→Server 列出可用工具
tools/call Client→Server 调用指定工具
resources/list Client→Server 列出可用资源
resources/read Client→Server 读取指定资源
resources/templates/list Client→Server 列出资源 URI 模板
prompts/list Client→Server 列出可用提示模板
prompts/get Client→Server 获取指定提示
notifications/initialized Client→Server 通知初始化完成
sampling/createMessage Server→Client 反向请求 LLM 生成

2.3 通信流程的生命周期

1. 连接建立(Transport 层: stdio/HTTP/SSE)
2. Client 发送 initialize 请求 → Server 返回能力声明
3. Client 发送 initialized 通知(握手完成)
4. 正常操作阶段(循环)
   - Client 查询 tools/resources/prompts
   - Client 调用 tools/call 或读取 resources/read
   - Server 可能发起 sampling/createMessage 反向请求
5. 连接关闭

三、MCP Server 开发

3.1 FastMCP 快速启动

FastMCP 是简化版的 MCP Server 框架,通过 Python 装饰器声明式定义能力:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DemoServer", json_response=True)

# 定义工具
@mcp.tool()
def add(a: int, b: int) -> int:
    """将两个数相加"""
    return a + b

# 定义资源(动态 URI 模板)
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """获取个性化问候"""
    return f"你好,{name}!"

# 定义提示模板
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
    """生成问候提示"""
    styles = {
        "friendly": "请写一段温暖友好的问候",
        "formal": "请写一段正式专业的问候",
        "casual": "请写一段轻松随意的问候",
    }
    return f"{styles.get(style, styles['friendly'])},对象是{name}。"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

3.2 使用标准 MCPServer

对于需要更细粒度控制的场景:

from mcp.server.mcpserver import MCPServer

mcp = MCPServer("MyServer", json_response=True)

@mcp.tool()
def search_database(query: str, limit: int = 10) -> str:
    """搜索数据库"""
    results = db.search(query, limit=limit)
    return json.dumps(results, ensure_ascii=False)

@mcp.resource("file://{path}")
def read_file(path: str) -> str:
    """读取文件内容"""
    with open(path, "r") as f:
        return f.read()

@mcp.prompt()
def code_review(code: str, language: str) -> str:
    """代码审查提示模板"""
    return f"""请审查以下 {language} 代码:
1. 检查安全漏洞
2. 评估性能
3. 建议改进方案

```{language}
{code}
```"""

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

3.3 传输方式的深入对比

传输方式 底层协议 连接模式 优势 劣势 适用场景
stdio 标准 I/O 进程管道 零网络开销,最简单 仅限本地进程,无并发 IDE 插件、CLI 工具
Streamable HTTP HTTP/1.1 Request-Response + SSE 无需长连接,穿透防火墙 无服务端推送 Web 服务、微服务
SSE HTTP/1.1 服务端→客户端单向流 实时推送,简单 单向,需要额外通道发请求 通知、日志流

为什么 MCP 支持多种传输:不同部署场景的约束不同。IDE 插件场景中,本地进程间通信用 stdio 最自然;云端微服务场景中,HTTP 是唯一可选方案;需要服务器主动推送通知时,SSE 是最简单的选择。


四、MCP Client 开发

4.1 基础 Client 连接

import asyncio
from mcp.client.session import ClientSession
from mcp.client.stdio import stdio_client
from mcp.types import AnyUrl

server_params = {"command": "python", "args": ["my_mcp_server.py"]}

async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # 1. 初始化连接
            await session.initialize()

            # 2. 列出可用工具
            tools = await session.list_tools()
            print(f"可用工具: {[t.name for t in tools.tools]}")

            # 3. 调用工具
            result = await session.call_tool("add", arguments={"a": 5, "b": 3})
            print(f"工具结果: {result.structuredContent}")

            # 4. 列出资源模板
            templates = await session.list_resource_templates()
            for t in templates.resourceTemplates:
                print(f"  - {t.uriTemplate}")

            # 5. 读取资源
            content = await session.read_resource(AnyUrl("greeting://World"))
            print(f"资源内容: {content.contents[0].text}")

            # 6. 获取提示
            prompt = await session.get_prompt(
                "greet_user",
                arguments={"name": "张三", "style": "formal"}
            )
            print(f"提示内容: {prompt.messages[0].content}")

asyncio.run(run())

4.2 采样(Sampling)机制

采样是 MCP 中 Server 反向请求 Client 侧 LLM 生成能力的机制。这是 MCP 的独特设计——传统 Client-Server 模型中 Server 不请求 Client 的资源,但 MCP 允许这种反向模式。

使用场景:

  • Server 需要 LLM 来总结检索结果
  • Server 需要 LLM 对工具输出进行后处理
  • Server 需要 LLM 生成自然语言提示
from mcp.types import CreateMessageRequestParams, CreateMessageResult

async def handle_sampling(context, params: CreateMessageRequestParams) -> CreateMessageResult:
    llm_response = await my_llm.generate(params.messages)
    return CreateMessageResult(
        role="assistant",
        content=TextContent(type="text", text=llm_response),
        model="gpt-4o",
        stopReason="endTurn",
    )

async with ClientSession(read, write, sampling_callback=handle_sampling) as session:
    await session.initialize()

4.3 Task 模式(异步长任务)

对于执行时间可能很长的操作(如大文件处理、批量数据分析),MCP 提供了 Task 机制——将同步调用转为异步任务,支持状态轮询。

# 创建异步任务
result = await session.experimental.call_tool_as_task(
    "long_running_operation", {"data": "..."}
)
task_id = result.task.taskId

# 轮询任务状态
async for status in session.experimental.poll_task(task_id):
    print(f"任务状态: {status.status}")
    if status.status == "input_required":
        # 处理需要用户输入的场景
        final = await session.experimental.get_task_result(task_id)
        break
    elif status.status == "completed":
        final = await session.experimental.get_task_result(task_id)
        break

Task 状态机:

created → running → (input_required → running)* → completed
                   ↘ failed

五、MCP 安全模型

5.1 OAuth 2.1 认证

MCP Server 可以作为 OAuth 2.1 的 Resource Server(RS),由独立的 Authorization Server(AS)管理认证:

from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.fastmcp import FastMCP
from pydantic import AnyHttpUrl

class SimpleTokenVerifier(TokenVerifier):
    async def verify_token(self, token: str) -> AccessToken | None:
        # 在此实现实际的 token 验证逻辑
        # 验证 JWT 签名、过期时间、scope 等
        pass

mcp = FastMCP(
    "Weather Service",
    json_response=True,
    token_verifier=SimpleTokenVerifier(),
    auth=AuthSettings(
        issuer_url=AnyHttpUrl("https://auth.example.com"),
        resource_server_url=AnyHttpUrl("http://localhost:3001"),
        required_scopes=["user"],
    ),
)

5.2 Client 侧 OAuth 流程

from mcp.client.auth import OAuthClientProvider, TokenStorage

class InMemoryTokenStorage(TokenStorage):
    def __init__(self):
        self.tokens = None
    async def get_tokens(self):
        return self.tokens
    async def set_tokens(self, tokens):
        self.tokens = tokens

oauth_auth = OAuthClientProvider(
    server_url="http://localhost:8001",
    client_metadata=OAuthClientMetadata(...),
    storage=InMemoryTokenStorage(),
    redirect_handler=handle_redirect,
    callback_handler=handle_callback,
)

async with httpx.AsyncClient(auth=oauth_auth) as client:
    async with streamable_http_client(url, http_client=client) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

5.3 安全架构层级

┌──────────────────────────┐
│     身份认证 (OAuth)      │  ← 你是谁?
├──────────────────────────┤
│     权限控制 (Scopes)     │  ← 你能做什么?
├──────────────────────────┤
│     传输加密 (TLS/HTTPS)  │  ← 防止窃听
├──────────────────────────┤
│     输入验证 (Pydantic)   │  ← 防止注入
├──────────────────────────┤
│     资源隔离 (Sandbox)    │  ← 限制影响范围
└──────────────────────────┘

六、工具调用进阶

6.1 工具注册表模式

class ToolRegistry:
    """中心化工具注册与发现"""

    def __init__(self):
        self._tools: dict[str, callable] = {}
        self._schemas: dict[str, dict] = {}

    def register(self, func: callable, schema: dict = None):
        """注册工具,自动推断 JSON Schema"""
        self._tools[func.__name__] = func
        self._schemas[func.__name__] = schema or self._infer_schema(func)

    def list_tools(self) -> list[dict]:
        return [{"name": name, "schema": schema}
                for name, schema in self._schemas.items()]

    def invoke(self, name: str, args: dict):
        if name not in self._tools:
            raise ValueError(f"未知工具: {name}")
        return self._tools[name](**args)

registry = ToolRegistry()
registry.register(search_web)
registry.register(get_weather)

6.2 安全的工具执行器

class SafeToolExecutor:
    def __init__(self, allowed_tools: set[str], max_timeout: int = 30):
        self.allowed_tools = allowed_tools
        self.max_timeout = max_timeout

    def execute(self, tool_name: str, args: dict):
        # 1. 白名单校验
        if tool_name not in self.allowed_tools:
            raise PermissionError(f"工具 {tool_name} 不在允许列表中")

        # 2. 参数校验
        self._validate_args(tool_name, args)

        # 3. 超时控制
        with timeout(self.max_timeout):
            return self._invoke(tool_name, args)

    def _validate_args(self, tool_name: str, args: dict):
        for key, value in args.items():
            if isinstance(value, str) and len(value) > 10000:
                raise ValueError(f"参数 {key} 过长")

6.3 并行工具调用

当 LLM 在一次响应中返回多个 tool_calls 时,这些工具调用通常是相互独立的(无数据依赖),可以并行执行:

import asyncio

async def parallel_tool_execution(tool_calls: list):
    """并行执行多个独立工具调用,显著降低延迟"""
    async def execute_one(call):
        tool = tools_by_name[call["name"]]
        return await tool.ainvoke(call["args"])

    results = await asyncio.gather(*[execute_one(c) for c in tool_calls])
    return results

何时并行:工具间无数据依赖时 何时串行:工具 A 的输出是工具 B 的输入时


七、Skill 概念与实现

7.1 Skill 的定位:能力封装单元

Skill 是比 Tool 更高层次的抽象——它将完成特定任务所需的所有资源打包为一个可组合的单元:

维度 Tool Skill
粒度 原子操作 组合能力
组成 单个函数 Tools + Prompts + Instructions + 逻辑
复用单位 函数级 模块级
认知负载 LLM 需理解每个 Tool LLM 只需理解 Skill 的”是什么”

7.2 Skill 架构实现

class Skill:
    """Skill 基类——能力的模块化封装"""

    def __init__(self, name: str, description: str):
        self.name = name
        self.description = description

    def get_tools(self) -> list:
        raise NotImplementedError

    def get_prompts(self) -> list:
        raise NotImplementedError

    def get_instructions(self) -> str:
        raise NotImplementedError


class CodeReviewSkill(Skill):
    """代码审查 Skill——将代码审查能力封装为独立模块"""

    def __init__(self):
        super().__init__("code-review", "执行多维度代码审查")

    def get_tools(self):
        return [
            static_analysis_tool,
            security_scan_tool,
            dependency_check_tool,
        ]

    def get_prompts(self):
        return [code_review_prompt, security_audit_prompt]

    def get_instructions(self):
        return """
审查流程:
1. 静态分析(语法树检查、复杂度计算)
2. 安全扫描(注入漏洞、硬编码密钥)
3. 依赖审查(版本过期、已知漏洞)
4. 风格检查(命名规范、代码格式)
5. 生成结构化的审查报告
        """

7.3 Skill 的加载与组合

Agent 可以动态加载不同的 Skill 组合,根据任务类型激活对应的能力集。Skill 之间的组合遵循”高内聚、低耦合”原则——每个 Skill 独立工作,通过共享的工具注册表或 MCP 协议进行交互。


八、MCP 在企业架构中的定位

8.1 分层架构

┌─────────────────────────────────────────────────┐
│              AI 应用层(Agent 编排)               │
│     ┌─────────┐  ┌──────────┐  ┌──────────┐    │
│     │Agent A  │  │ Agent B  │  │ Agent C  │    │
│     └────┬────┘  └────┬─────┘  └────┬─────┘    │
│          │             │              │          │
├──────────┼─────────────┼──────────────┼─────────┤
│          │       MCP Client Layer               │
│          │       (标准 JSON-RPC)                 │
├──────────┼─────────────┼──────────────┼─────────┤
│          │             │              │          │
│   ┌──────┴───┐  ┌──────┴───┐  ┌──────┴───┐    │
│   │MCP Server│  │MCP Server│  │MCP Server│    │
│   │ (搜索)   │  │ (数据库) │  │ (文件)   │    │
│   └──────────┘  └──────────┘  └──────────┘    │
│                                                 │
│          MCP Server 层(工具/数据源)             │
└─────────────────────────────────────────────────┘

8.2 MCP 带来的架构优势

特性 传统方式 MCP 方式
集成成本 O(M×N) 自定义适配 O(M+N) 标准接口
服务发现 硬编码工具列表 动态 list_tools()
版本管理 无标准化方案 initialize 协商版本
安全模型 各自实现 统一的 OAuth 2.1 + Scopes
传输方式 厂商自定义 stdio/HTTP/SSE 标准切换
工具复用 紧耦合 松耦合,独立部署

8.3 何时为工具创建 MCP Server

应该创建 MCP Server:

  • 工具被多个 Agent 或应用共享
  • 工具需要独立部署和扩缩容
  • 工具有独立的认证授权需求
  • 工具由不同团队开发维护

暂时不需要创建 MCP Server:

  • 工具仅被单个 Agent 使用
  • 工具逻辑简单(几行代码的计算函数)
  • 处于原型验证阶段

相关条目

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注