8.1 函数调用机制(感觉很多都是从Authropic那搬过来的)

8.1.1 什么是函数调用(function calling)

让LLM能够调用外部函数的能力,通过生成结构化的函数调用请求,

8.1.2 函数调用的价值

获取数据;执行计算;操作系统;访问服务

8.1.3 工作原理

工具定义

name,description,parameters,required

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "name": "get_weather",
  "description": "获取指定城市的天气信息",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "温度单位"
      }
    },
    "required": ["city"]
  }
}

模型决策

是否需要调用和调用什么

结果处理

将tool result返回给模型

生成响应

8.1.4 模型支持情况

模型类型函数调用支持典型特点
商用闭源通用模型通常原生支持协议与参数更统一,工具生态成熟
商用多模态模型通常原生支持可能支持与多模态输入绑定的工具流程
开源模型视实现而定可能需要特定指令微调/框架适配,兼容性需压测

8.1.5 调用模式

自动模式

模型自主决定是否调用

强制模式

1
{"tool_choice": {"type": "function", "function": {"name": "get_weather"}}}

禁用模式 禁止工具调用:

1
{"tool_choice": "none"}

8.1.6 并行调用

字面意思,可以单次响应调用多个工具

8.1.7 函数调用与上下文工程

1.tool def 2. tool call 3. tool result 都是上下文的一部分

8.2 工具定义与设计

8.2.1 优秀工具定义的特征

清晰明确(name),参数完整(parameter),易于使用,文档充分(description)

8.2.2 工具定义结构

生产环境应优先使用严格 JSON Schema:关闭额外字段、明确所有属性的 required 语义,并用 null 或显式枚举表达可选值。不同供应商对 strict mode、默认值、流式部分 JSON 和复杂 schema 关键字的支持不完全一致,工具层仍需使用成熟 JSON Schema 库做服务端校验,不能只依赖模型“按格式输出”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "name": "search_products",
  "description": "在产品数据库中搜索商品",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "搜索关键词"
      },
      "category": {
        "type": ["string", "null"],
        "enum": ["electronics", "clothing", "books", null],
        "description": "产品类别"
      },
      "price_max": {
        "type": ["number", "null"],
        "description": "最高价格限制"
      },
      "sort_by": {
        "type": ["string", "null"],
        "enum": ["price", "rating", "newest", null],
        "description": "排序方式;为空时由服务端使用默认排序"
      }
    },
    "required": ["query", "category", "price_max", "sort_by"],
    "additionalProperties": false
  }
}

8.2.3 设计原则

原则一:单一职责

原则二:参数设计清晰

原则三:提供足够的描述

原则四:合适适用枚举(首先的参数适用枚举限制)

8.2.4 常见工具类型

类型示例特点
查询类搜索、获取信息只读,无副作用
操作类创建、更新、删除有副作用,需确认
计算类数学运算、转换确定性输出
通信类发送邮件、消息外部影响

8.2.5 工具数量的权衡

太少:能力受限 太多:模型选择困难 建议: 核心工具;动态加载;按场景分组管理

8.2.6 工具分组策略

image-20260916165016343

根据对话场景选择

8.2.7 工具文档最佳实践

描述模板:

1
[用途]。当[触发条件]时调用。[返回内容概述]。

参数说明: 参数也可以有description

8.2.8 工具测试

工具定义需要测试:

  1. 理解测试:模型是否能正确理解何时调用
  2. 参数测试:参数提取是否准确
  3. 边界测试:模糊情况如何处理
  4. 拒绝测试:无关请求是否不会调用

8.2.9 上下文感知的工具设计

命名空间

当智能体需要访问数十个MCP服务器和数百个工具时,工具功能重叠或用例模糊会导致选择困难

命名空间策略:

  • 按服务分组:asana_search、jira_search、github_search
  • 按资源分组asana_projects_search、asana_users_search

相应格式优化

工具返回信息应该优先考虑上下文相关性,避免低级技术标识符 例如:

1
2
❌ 避免:uuid、256px_image_url、mime_type
✓ 推荐:name、image_url、file_type

灵活的响应格式: 通过添加response_format参数(规定返回格式),让智能体控制返回详细程度

工具使用示例

JSON Schema能够定义结构有效性,但是无法表达使用模式

  • 格式约定:日期用 YYYY-MM-DD,ID 用 USR-XXXXX
  • 可选参数相关性:关键任务填写所有字段,简单任务只需核心字段
  • 嵌套结构用法
1
2
3
4
5
6
7
8
9
{
  "name": "create_ticket",
  "input_schema": {...},
  "input_examples": [
    {"title": "紧急:登录页面 500 错误", "priority": "critical", "labels": ["bug"]},
    {"title": "添加暗色模式支持", "labels": ["feature-request"]},
    {"title": "更新文档"}
  ]
}

8.3 工具执行和结果处理

8.3.1 工具执行流程

image-20260916165916159

8.3.2 参数验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def validate_params(tool_name, params, schema):
    # 检查必需参数
    for required in schema.get("required", []):
        if required not in params:
            raise ValueError(f"缺少必需参数: {required}")

    # 检查参数类型
    for key, value in params.items():
        expected_type = schema["properties"][key]["type"]
        if not isinstance(value, TYPE_MAP[expected_type]):
            raise TypeError(f"参数 {key} 类型错误")

    # 检查枚举值
    for key, value in params.items():
        if "enum" in schema["properties"][key]:
            if value not in schema["properties"][key]["enum"]:
                raise ValueError(f"参数 {key} 值无效")

8.3.3 执行策略

同步执行:

简单场景

1
result = execute_tool(tool_name, params)

异步执行: IO密集型或耗时操作

1
2
3
4
async def execute_tools(tool_calls):
    tasks = [execute_tool_async(tc) for tc in tool_calls]
    results = await asyncio.gather(*tasks)
    return results

超时控制: 防止无限等待(在result处)

1
2
3
4
5
6
7
try:
    result = await asyncio.wait_for(
        execute_tool(tool_name, params),
        timeout=30.0
    )
except asyncio.TimeoutError:
    result = {"error": "执行超时"}

8.3.4 结果格式化

将tool result格式化为易于模型理解的形式 简洁输出

1
2
3
4
5
6
7
{
  "status": "success",
  "data": {
    "temperature": 25,
    "condition": "晴"
  }
}

包含上下文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "query": "北京天气",
  "result": {
    "city": "北京",
    "temperature": 25,
    "condition": "晴",
    "humidity": 45
  },
  "data_time": "2026-03-01 10:00"
}

8.3.5 错误处理

错误类型处理方式反馈给模型
参数错误不执行说明具体问题
执行失败记录日志返回错误信息
超时终止执行建议稍后重试
权限不足拒绝执行说明权限要求

8.3.6 结果注入上下文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "get_weather",
      "arguments": "{\"city\": \"北京\"}"
    }
  }]
},
{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "{\"temperature\": 25, \"condition\": \"晴\"}"
}

8.3.7 结果压缩

工具返回大量数据时困难需要压缩

8.3.8 多轮工具调用

每轮结果都成为上下文的一部分,印象后续决策

8.3.9 安全考虑

  1. 输入验证:防止注入攻击
  2. 权限控制:验证操作权限
  3. 操作审计:记录敏感操作
  4. 确认机制:危险操作需人工确认

8.3.10 高级工具执行技术

工具搜索工具

当连接多个MCP服务器时,工具定义可能消耗大量token 解决方法:不预加载所有工具,而是按需发现 通过defer_loading = true实现,使用mid-conversation中的tool_addition

1
2
3
4
5
6
7
8
9
{
  "tools": [
    {"type": "tool_search_tool", "name": "tool_search"},
    {
      "name": "github.createPullRequest",
      "defer_loading": true
    }
  ]
}

编程式工具调用

传统工具的瓶颈:

  • 中间结果污染上下文
  • 每次调用都需要一次推理,开销大

解决方案: 让模型生成受限的工作流计划,由服务端在沙箱和权限边界内执行;不要让模型生成的任意代码直接持有团队、费用、客户或生产系统凭据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 模型只生成声明式计划;服务端负责鉴权、执行、脱敏和审计
workflow = {
    "operation": "expense_budget_check",
    "team": "engineering",
    "period": "Q3",
    "output": "over_budget_summary",
}

result = await workflow_runner.run(
    workflow,
    caller=user_identity,
    policy_checks=["team_scope", "read_only", "pii_redaction"],
    network_scope="internal_tools_only",
)
return result.redacted_summary

效果(常见观察):

  • 上下文只接收最终结果而非原始数据,通常能显著降低 Token 消耗
  • 降低中间结果“污染上下文”的概率,并减少不必要的多轮推理

8.3.11 工程实战:端到端数据分析助手

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
async def run_analysis_agent(user_query, context):
    messages = context + [{"role": "user", "content": user_query}]

    # 1. 规划阶段
    plan = await llm.plan(messages)
    # output: 1. read_csv, 2. analyze_trend, 3. plot_chart

    results = {}

    for step in plan.steps:
        # 2. 执行阶段
        tool_name = step.tool
        tool_args = step.args

        try:
            # 3. 结果处理
            # 仅截取前5行数据作为上下文,避免 Token 爆炸
            raw_result = await tools.execute(tool_name, tool_args)
            processed_result = truncate_dataframe(raw_result)

            # 注入当前步骤结果到上下文
            messages.append({
                "role": "tool",
                "tool_call_id": step.tool_call_id,
                "content": processed_result
            })

        except Exception as e:
            # 错误恢复机制
            messages.append({"role": "system", "content": f"工具执行失败: {str(e)},请尝试其他方法"})
            continue

    # 4. 生成最终回复
    final_response = await llm.chat(messages)
    return final_response

8.3.12 核心指标与评估

指标目标值(示意)说明
执行成功率以基线提升为目标工具调用无异常(Crash/Timeout)的比例
参数准确率以基线提升为目标模型生成的参数符合 Schema 定义且逻辑正确
平均延迟取决于工具与 SLO工具本身的执行耗时(不含 LLM 推理)
幻觉率越低越好调用了不存在的工具或虚构了参数
Token 效率越高越好原始工具结果 vs 注入上下文的结果大小

优化建议:

  • 对于高频且低延迟要求的工具(如查天气),使用本地逻辑而非 LLM 规划。
  • 对于易错的复杂参数(如 SQL),在 System Prompt 中提供 Few-Shot 示例。

8.4 MCP和工具标准化

8.4.1 协议简介

MCP是一种开放协议规范,旨在标准化AI应用与外部工具,资源和提示模板的交互方式

MCP解决的核心问题:

  • 缺乏统一的工具交互标准
  • 不同模型/应用的工具集成方式各异
  • 工具能力难以复用

8.4.2 协议框架

image-20260916182615304

主要组件:

  • Host/AI应用:负责用户授权,上下文聚合和安全边界
  • MCP客户端:集成到 Host 中,与单个 Server 建立连接;在 2025-11-25 及更早修订版中这是一个有状态会话,2026-07-28 修订版移除了协议层会话(Streamable HTTP 的 Mcp-Session-Id 头一并移除),改为无状态交互
  • MCP服务端:提供工具,资源和提示模板接口
  • 传输层:支持stdio和Streamable HTTP

MCP传输与JSON-RPC 2.0 详解

MCP基于JSON-RPC 2.0 作为消息传输标准,在客户端和服务端之间进行结构化的远程过程调用.JSON-RPC 2.0定义了请求,响应和通知三种消息类型,每个消息包含必要的版本,方法名,参数和ID信息

传输层演进 最新的MCP支持两类传输 stdio:标准的输入输出流,适合本地进程通信和容器化部署 Streamable HTTP:新一代的HTTP传输方案,替代了早期的HTTP+SSE组合,支持更高效的双向流式通信,特别是对长连接和大型响应流有更好的支持

OAuth 2.1和安全认证 在生产环境中,MCP服务端需要身份认证和授权. 当前MCP HTTP传输的授权规范按照OAuth 2.1,PKCE,资源指示符和受保护资源元数据等机制设计;stdio本地进程传输依赖宿主应用的进程穷的那个,环境变量,本机权限模型,不能照搬HTTP授权流程

  • Authorization Endpoint:用户授权端点,获取授权码
  • Token Exchange:通过授权码交换访问令牌
  • Token Validation:服务端验证令牌有效性和权限范围
  • Scope Control:细粒度权限控制,限制客户端的访问范围

这些机制只能在正确配置 HTTPS、令牌受众校验、短期令牌、刷新令牌轮换和最小权限 scope 时降低风险。它们不能让不安全网络变安全,也不能替代服务端对每个工具调用的授权校验。

Origin验证和本地绑定

  • 为防止恶意来源的请求,MCP 协议引入了:

    • Origin Validation:验证请求来自合法的客户端源
    • Localhost Binding:对本地部署的服务端,可以限制只接受本机(127.0.0.1)的连接,防止网络攻击

    这些机制构成了 MCP 协议在分布式系统中的安全基础。

一次典型请求的时序:

image-20260916190744456

8.4.3 核心概念

最常见的基础构建:Tools,Resources和Prompts

工具Tools

可执行函数,类似于函数调用.格式包含name,description和inputSchema(遵循JSON SChema格式)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "name": "read_file",
  "description": "读取指定路径的文件内容",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {"type": "string"}
    },
    "required": ["path"]
  }
}

较新的规范还允许工具声明可选的 outputSchema。当工具返回结构化结果时,结果应放在 structuredContent 中并符合该 schema;为兼容旧客户端,也可同时在 content 中返回序列化文本。工具执行失败应通过 isError: true 表达可供模型自我修正的业务错误,协议级错误则仍使用 JSON-RPC error

资源Resources

可读取的数据源,如文件、数据库、API 端点等。格式包含 uri(资源唯一标识符)、name(显示名称),以及可选的 mimeType(内容类型)和 description:

1
2
3
4
5
{
  "uri": "file://config.json",
  "name": "配置文件",
  "mimeType": "application/json"
}

提示模板Prompts

预定义的提示词模板,便于复用常见的交互模式。格式包含 name(模板名称)、description(描述),以及可选的 arguments(参数列表):

1
2
3
4
5
6
7
{
  "name": "code_review",
  "description": "代码审查提示模板",
  "arguments": [
    {"name": "code", "required": true}
  ]
}

任务

用于表示可能持续较长时间的工作单元,例如批量索引、长时爬取或异步分析。相比“一次请求立即返回”的工具调用,任务更强调状态跟踪、进度查询和结果延迟获取:

1
2
3
4
5
6
7
8
{
  "taskId": "task-123",
  "status": "working",
  "createdAt": "2026-05-20T10:30:00Z",
  "lastUpdatedAt": "2026-05-20T10:40:00Z",
  "ttl": 60000,
  "pollInterval": 5000
}

客户端能力

MCP 不只规定服务端如何暴露工具、资源和提示模板,也规定客户端可以向服务端提供哪些能力。2025-11-25 规范中的典型客户端能力包括(其中 Sampling 和 Roots 已在 2026-07-28 修订版中标记为弃用,至少保留 12 个月弃用期,仍可使用但新实现不建议采用;该修订版列出的客户端能力只有 Elicitation):

  • Sampling:服务端请求客户端代为调用模型。客户端必须保留用户可见的审批、模型选择和敏感数据过滤边界,不能让远端服务端绕过用户授权直接驱动模型。
  • Roots:客户端声明服务端可访问的文件或工作区边界。服务端只能把 roots 当作授权范围提示,真实文件访问仍应由客户端权限和工具实现控制。
  • Elicitation:服务端请求客户端向用户补充结构化输入。适合缺少必要参数、需要人工确认或高风险操作前的二次确认。

从上下文工程角度看,客户端能力会把“谁能看见什么、谁能请求模型、谁能要求用户确认”纳入协议层。生产系统应把这些能力与用户审批、状态持久化、审计日志和工具 allowlist 一起设计,而不是只把 MCP 当成远程工具注册表。

8.8.4 协议优势

  1. 标准化:统一的协议减少集成成本
  2. 可组合:不同 MCP 服务可以组合使用
  3. 跨平台:同一服务可被不同客户端使用
  4. 生态系统:日益丰富的预构建服务

8.4.6 实现服务

简单的 MCP 服务实现框架(Python SDK v2):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from mcp.server import MCPServer

server = MCPServer("my-service")

@server.tool()
async def my_tool(param: str) -> str:
    """工具描述"""
    result = process(param)
    return result

@server.resource("config://main")
async def get_config() -> str:
    """获取配置"""
    return load_config()

if __name__ == "__main__":
    server.run()

8.4.7 MCP与上下文工程的深度融合

MCP 不仅仅是工具调用的标准化协议,更是上下文工程的关键基础设施。理解 MCP 如何影响上下文工程的各个环节至关重要。

传统方法的限制:

复制

1
用户查询 → 检索系统 → 向量库 → 上下文

问题:

  • 向量库中的信息可能陈旧
  • 对高频实时数据的支持通常依赖增量索引、缓存刷新或直连数据源,工程复杂度更高
  • 多个数据源的集成复杂

基于 MCP 的上下文获取:

1
2
3
4
5
6
用户查询 → 查询规划 → MCP 服务发现 → 多个Resources
                        ├─ 文件系统 (file://)
                        ├─ 数据库 (db://)
                        ├─ API (http://)
                        └─ 实时数据源 (stream://)
                        → 动态上下文组装

实现示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 示意性伪代码:不同 MCP 客户端的具体方法名会不同
class MCPContextAggregator:
    """使用 MCP 聚合来自多个源的上下文"""

    def __init__(self, mcp_servers: dict):
        """
        mcp_servers: {
            'filesystem': MCPClient(...),
            'database': MCPClient(...),
            'api': MCPClient(...)
        }
        """
        self.servers = mcp_servers

    async def gather_context(self, query: str, context_type: str) -> str:
        """
        根据查询类型从不同的MCP服务获取上下文
        """
        context_parts = []

        # 优先级1:从数据库获取结构化数据
        if 'database' in self.servers:
            db_context = await self.servers['database'].read_resource(
                f"db://query/{context_type}"
            )
            if db_context:
                context_parts.append(f"【结构化数据】\n{db_context}")

        # 优先级2:从文件系统获取文档
        if 'filesystem' in self.servers:
            file_context = await self.servers['filesystem'].read_resource(
                f"file://docs/{context_type}/"
            )
            if file_context:
                context_parts.append(f"【文档内容】\n{file_context}")

        # 优先级3:从API获取实时数据
        if 'api' in self.servers:
            api_context = await self.servers['api'].read_resource(
                f"http://api/context?type={context_type}"
            )
            if api_context:
                context_parts.append(f"【实时数据】\n{api_context}")

        return "\n---\n".join(context_parts)

关键优势:

  • 动态性:资源可以是实时数据,无需预构建向量库
  • 多源融合:天然支持来自不同系统的数据整合
  • 可扩展性:新增数据源仅需接入新的 MCP 服务器
  • 信息溯源:资源 URI 明确指示数据来源

MCP Tools扩展上下文操作

上下文不仅是输入,也可能需要在推理过程中动态变换。MCP Tools 提供了这种能力:

复制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 示意性伪代码:用于说明如何把 MCP Tools 作为上下文处理算子
class MCPContextManipulator:
    """使用MCP工具在推理过程中变换上下文"""

    def __init__(self, mcp_tools: dict):
        """
        mcp_tools: {
            'compress': Tool,
            'filter': Tool,
            'enrich': Tool,
            'translate': Tool
        }
        """
        self.tools = mcp_tools

    async def compress_context(self, context: str, ratio: float = 0.5) -> str:
        """使用MCP工具压缩上下文"""
        result = await self.tools['compress'].execute(
            context=context,
            compression_ratio=ratio
        )
        return result['compressed']

    async def filter_context(self, context: str, keywords: list) -> str:
        """过滤上下文,仅保留相关部分"""
        result = await self.tools['filter'].execute(
            context=context,
            keep_keywords=keywords
        )
        return result['filtered']

    async def enrich_context(self, context: str, knowledge_bases: list) -> str:
        """使用知识库丰富上下文"""
        result = await self.tools['enrich'].execute(
            context=context,
            sources=knowledge_bases
        )
        return result['enriched']

    async def translate_context(self, context: str, target_language: str) -> str:
        """将上下文翻译为目标语言"""
        result = await self.tools['translate'].execute(
            text=context,
            target_lang=target_language
        )
        return result['translated']

使用场景:

  1. 即时压缩:当上下文超过限制时,动态调用压缩工具
  2. 关键词过滤:提取与查询相关的上下文片段
  3. 跨语言支持:自动翻译外语上下文
  4. 知识增强:在推理中动态调用知识库

MCP Prompts标准化提示词模式

MCP Prompts 规范化了上下文的呈现方式,使得提示词设计可复用和版本管理。协议中的 prompts/get 返回的是包含 messages 的结构化结果,而不是单个可直接 .format() 的字符串模板;客户端需要把返回的消息数组合并进自己的模型请求。

复制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 示意性伪代码:模板获取与渲染接口因客户端而异
class MCPPromptManager:
    """使用MCP Prompts标准化上下文呈现"""

    def __init__(self, mcp_prompt_server):
        """
        mcp_prompt_server: 提供预定义提示词模板的MCP服务器
        """
        self.prompt_server = mcp_prompt_server

    async def build_rag_prompt(self,
                              query: str,
                              retrieved_docs: list,
                              system_context: str = None) -> list:
        """
        使用标准化的RAG提示词模板构建提示

        MCP服务器可提供以下模板:
        - rag/basic: 基础RAG模板
        - rag/cite: 带引文的RAG
        - rag/reasoning: 带推理步骤的RAG
        """

        # 获取结构化 prompt 结果
        prompt_result = await self.prompt_server.get_prompt(
            name="rag/reasoning",
            arguments={
                "system_context": system_context or "你是一个有帮助的AI助手",
                "documents": "\n".join([f"【{doc['id']}】{doc['content']}"
                                       for doc in retrieved_docs]),
                "query": query,
            }
        )

        return prompt_result.messages

    async def build_agent_prompt(self,
                                task: str,
                                tools: list,
                                state: dict) -> list:
        """
        为智能体构建标准化的提示词,包含:
        - 任务描述
        - 可用工具列表
        - 当前状态
        - 推理指导
        """

        prompt_result = await self.prompt_server.get_prompt(
            name="agent/planning",
            arguments={
                "task": task,
                "tools": "\n".join([f"- {tool['name']}: {tool['description']}"
                                    for tool in tools]),
                "current_state": str(state),
                "reasoning_guide": "分步骤思考,先规划再执行",
            }
        )

        return prompt_result.messages

标准化的好处:

  • 版本控制:提示词可以版本管理,方便回滚和 A/B 测试
  • 可复用性:团队可共享优化过的提示词模板
  • 一致性:相同类型的任务使用相同的提示结构
  • 可维护性:改进某个模板自动应用到所有相关系统

这个架构使得上下文工程从静态的向量检索演进到 动态、可组合、可扩展 的系统。

8.4.8 未来展望

MCP 协议正在快速发展。不同客户端、IDE 与模型平台对 MCP 的支持程度可能不同,且会随版本迭代变化;在做技术选型时建议以对应项目与产品的最新文档为准。

8.4.9 实战案例:构建 GitHub 协议服务

以下是一个 GitHub MCP Server 的 示意性 实现,用于展示如何同时提供 Tools、Resources 和 Prompts。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from mcp.server import MCPServer
import hashlib
import os
import re
import httpx
from typing import Optional

# 初始化 MCP Server
mcp = MCPServer("github-server")
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]

SENSITIVE_PATTERNS = [
    re.compile(r"(?i)(api[_-]?key|token|secret|password)\s*[:=]\s*[^\s]+"),
    re.compile(r"ghp_[A-Za-z0-9_]{20,}"),
]

def redact_sensitive(text: str) -> str:
    """只用于确认预览;真实提交内容仍按原文发送。"""
    redacted = text
    for pattern in SENSITIVE_PATTERNS:
        redacted = pattern.sub("<REDACTED>", redacted)
    return redacted

# ============ Tools:可执行的操作 ============

@mcp.tool()
async def search_repos(query: str, language: str = None) -> str:
    """搜索 GitHub 仓库

    Args:
        query: 搜索关键词
        language: 可选,编程语言过滤
    """
    search_query = f"{query} language:{language}" if language else query
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.github.com/search/repositories",
            params={"q": search_query, "per_page": 5},
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"}
        )
        repos = resp.json().get("items", [])
        return "\n".join([
            f"- {r['full_name']} ⭐{r['stargazers_count']}: {r['description']}"
            for r in repos
        ])

@mcp.tool()
async def create_issue(
    repo: str,
    title: str,
    body: str,
    dry_run: bool = True,
    confirmed_operation_id: Optional[str] = None,
) -> str:
    """在指定仓库创建 Issue

    Args:
        repo: 仓库全名,如 owner/repo
        title: Issue 标题
        body: Issue 内容
        dry_run: 默认只返回待确认预览,不执行写操作
        confirmed_operation_id: 人工确认后的操作编号
    """
    allowed_repos = set(os.environ.get("GITHUB_ALLOWED_REPOS", "").split(","))
    if repo not in allowed_repos:
        return "创建失败:仓库不在 allowlist 中"
    if len(body) > 4000:
        return "创建失败:Issue 内容过长,请先人工复核"

    operation_id = hashlib.sha256(f"{repo}\0{title}\0{body}".encode()).hexdigest()[:12]
    if dry_run or confirmed_operation_id != operation_id:
        body_preview = redact_sensitive(body)[:500]
        return (
            "待确认的 Issue 创建请求:\n"
            f"- repo: {repo}\n"
            f"- title: {title}\n"
            f"- body_preview: {body_preview}\n"
            f"- operation_id: {operation_id}\n"
            "确认后再以 dry_run=False 且 confirmed_operation_id 等于该编号重试。"
        )

    async with httpx.AsyncClient() as client:
        resp = await client.post(
            f"https://api.github.com/repos/{repo}/issues",
            json={"title": title, "body": body},
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"}
        )
        if resp.status_code == 201:
            return f"Issue 创建成功: {resp.json()['html_url']}"
        return f"创建失败:GitHub API 返回状态码 {resp.status_code}"

# ============ Resources:可读取的数据 ============

@mcp.resource("github://repos/{owner}/{repo}/readme")
async def get_readme(owner: str, repo: str) -> str:
    """获取仓库的 README 内容"""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://api.github.com/repos/{owner}/{repo}/readme",
            headers={
                "Authorization": f"Bearer {GITHUB_TOKEN}",
                "Accept": "application/vnd.github.raw"
            }
        )
        return resp.text if resp.status_code == 200 else "README 未找到"

@mcp.resource("github://user/starred")
async def get_starred() -> str:
    """获取用户 Star 的仓库列表"""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.github.com/user/starred",
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"}
        )
        repos = resp.json()[:10]
        return "\n".join([f"- {r['full_name']}" for r in repos])

# ============ Prompts:预定义模板 ============

@mcp.prompt()
def code_review_prompt(repo: str, pr_number: int) -> str:
    """生成代码审查的提示词模板"""
    return f"""请审查 {repo} 仓库的 PR #{pr_number}。

审查要点:
1. 代码逻辑是否正确
2. 是否有潜在的性能问题
3. 代码风格是否符合规范
4. 是否有足够的测试覆盖

请先获取 PR 详情,然后逐一分析。"""

@mcp.prompt()
def issue_template(title: str) -> str:
    """生成 Issue 创建模板"""
    return f"""请创建一个规范的 GitHub Issue。

标题: {title}

请按以下格式填写内容:
## 问题描述
[简述问题]

## 复现步骤
1.
2.

## 期望行为
[描述期望的正确行为]

## 环境信息
- 操作系统:
- 版本号:"""

# 运行服务
if __name__ == "__main__":
    mcp.run()

配置和使用

1. 安装依赖:

1
pip install mcp httpx

2. 在 Claude Desktop 中配置(claude_desktop_config.json):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "mcpServers": {
    "github": {
      "command": "python",
      "args": ["/path/to/github_server.py"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

3. 使用示例:

1
2
3
4
5
6
用户: 帮我搜索 Python 的 RAG 相关项目
助手: [调用 search_repos(query="RAG", language="python")]
       找到以下项目:
       - langchain-ai/langchain ⭐75000: LLM 应用开发框架
       - run-llama/llama_index ⭐30000: 数据框架连接 LLM
       ...

踩坑经验:

  • Token 权限要最小化,只授予必需的 scope
  • 写操作要加仓库 allowlist、用户确认、内容脱敏和审计记录
  • 客户端配置只引用环境变量或密钥管理器,不直接保存真实 token
  • 异步 HTTP 客户端比同步更适合 MCP Server
  • 复杂操作应拆分为多个小工具,而非一个大工具

本章小结

关键概念清单

概念定义
函数调用模型生成结构化函数调用请求的能力
工具定义描述工具名称、参数和用途的结构化描述
工具执行应用程序实际运行工具并获取结果
MCPModel Context Protocol,统一暴露工具、资源和提示模板的标准协议
并行调用模型在单次响应中请求多个工具调用

核心观点

  1. 函数调用扩展了模型边界:
    • 访问实时数据:天气、股价、新闻
    • 执行计算和操作:精确数学运算、文件操作
    • 与外部系统交互:API、数据库、网络服务
  2. 工具设计直接影响效果:
    • 单一职责:每个工具只做一件事
    • 清晰的描述:让模型准确理解何时使用
    • 合理的参数设计:类型明确、有默认值
  3. 执行与结果处理需要精心设计:
    • 参数验证:执行前验证参数有效性
    • 格式化输出:将结果转换为模型易理解的形式
    • 错误处理:优雅处理各种失败情况
    • 结果压缩:大量数据返回时进行压缩
  4. MCP 推动标准化:
    • 统一的协议:减少集成成本
    • 可复用的服务:工具能力可跨应用共享
    • 日益丰富的生态:预构建服务持续增加

常见误区

  • 误区一:提供越多工具越好 正解:工具过多会增加选择复杂度和 Token 开销,应按需动态加载
  • 误区二:模型会准确调用工具 正解:需要充分测试和优化工具定义,模型可能误用工具
  • 误区三:直接返回原始工具结果 正解:应该格式化和适当压缩,便于模型理解和使用
  • 误区四:工具定义写一次就不用改 正解:需要根据实际调用情况持续优化描述和参数

实践建议

  1. 精心设计工具定义:投入时间优化描述和参数,明确说明何时使用
  2. 完善错误处理:考虑各种失败情况,提供清晰的错误信息
  3. 控制工具数量:动态加载相关工具,核心工具优先
  4. 关注 MCP 发展:跟进标准化进展,利用生态优势
  5. 安全优先:验证输入防止注入,审计敏感操作
  6. 测试覆盖:测试正常调用、边界情况和错误恢复