2026 前端 AI Agent 工程化实战营系列第 13 篇

本文整理自《2026 前端 AI Agent 工程化实战营》课程资料,作为系列文章第 13 篇。


theme: channing-cyan

本章demo地址feat/mcp

上一章解决的是“让模型读懂业务知识库”:通过 RAG,Agent 可以在回答前先检索文档,再基于资料生成结果。但读懂资料还不够,一个真正能落地的 Agent 还需要“动手”——查询项目管理系统、读取设计稿、查看代码仓库、搜索外部资料,甚至触发业务流程。

回到第九章的 Multi-Agent 系统,当时的工具仍然写在代码里:readFeatureSpecToolloadPerfBaselineTool 这些函数都固定在 expert-tools.ts 中,随着项目一起编译和部署。这种方式适合教学项目,也适合少量自有工具;但一旦进入真实业务,工具数量和外部系统会迅速增加:

每个系统都有自己的 API、认证方式、数据结构和错误模型。如果每接入一个系统,都要在业务代码里写一套专用 Tool,再重新发版,工程复杂度会很快失控。

本章的主角 MCP(Model Context Protocol,模型上下文协议),解决的正是这个问题。它不是一个新的大模型,也不是一个新的 Agent 框架,而是一套连接协议:让 AI 应用用统一方式发现、连接、调用外部工具和数据源。

可以把 MCP 理解成“AI 工具调用的 USB-C 接口”。没有 MCP 时,每个外部服务都像一根专用线;有了 MCP,只要服务端按协议暴露能力,客户端就可以在运行时发现并调用它。对 Agent 系统来说,这意味着工具不再只能硬编码在项目里,而可以变成独立部署、跨项目复用、按需连接的外部能力。

generated-image-1779534585624.png

本章内容信息密度较高,会同时涉及协议设计、工程实现、LangChain 桥接、多 Server 编排、安全模型与生产可观测性。为了避免专业名词堆叠,正文会像第十一章一样,先解释概念,再进入代码实现。

先给几个核心术语打底:

本章验收点


12.1 为什么 Function Calling 不够用

12.1.1 编译时绑定 vs 运行时发现

回顾第八章,我们用 Function Calling 让 LLM 调用工具:

// 第八章模式:编译时绑定
const tools = [
  { name: 'analyze_requirement', description: '...', parameters: {...} },
  { name: 'check_conflicts',     description: '...', parameters: {...} },
];
// 这些工具在代码里写死,要加新工具就要改代码、重新部署

这就是编译时绑定(compile-time binding)——工具清单在代码编写时就确定了。类比第十一章 11.1.2 讲的“没有 RAG 时会发生什么”:

维度 Function Calling(编译时绑定) MCP(运行时发现)
新增工具 改代码 → 编译 → 部署 Server 加工具 → Client 自动发现
跨项目复用 复制粘贴代码 同一个 MCP Server 被多个 Agent 连接
第三方服务 每个服务写一个适配器 服务自己提供 MCP Server
工具描述更新 改代码重新部署 Server 热更新,Client 下次 listTools 自动获取
类比 没有 USB,每个设备一根专用线 USB-C:一根线连一切

12.1.2 连接外部服务的五大痛点

  1. 认证碎片化:Jira 用 API Token,GitHub 用 OAuth App,飞书用 App Secret,每个都要写认证逻辑
  2. 数据格式不统一:REST 返回 JSON、GraphQL 返回嵌套结构、gRPC 返回 Protobuf
  3. 错误处理不一致:有的用 HTTP 状态码,有的在 body 里放 error 字段
  4. 发现机制缺失:Agent 不知道“外面有哪些工具可用”,只能硬编码在代码里
  5. 生命周期无管理:连接什么时候建立、什么时候释放、崩溃了怎么重连

12.1.3 MCP 的定位

如果用操作系统做类比,MCP 提供的是工具调用所需的几层基础能力:

graph TB
  subgraph "MCP 提供的五层能力"
    L1["1. 注册:Server 声明自己有哪些工具/资源"]
    L2["2. 发现:Client 动态获取可用工具列表"]
    L3["3. 调用:标准化的调用协议和返回格式"]
    L4["4. 权限:工具分级 + Human-in-the-Loop"]
    L5["5. 生命周期:连接管理 + 错误恢复"]
  end
  L1 --> L2 --> L3 --> L4 --> L5

12.1.4 MCP vs Function Calling vs LangChain Tool

维度 OpenAI Function Calling LangChain Tool MCP
本质 LLM 输出格式约束 代码里的工具抽象 跨进程通信协议
工具发现 ❌ 硬编码 ❌ 硬编码 ✅ 运行时 tools/list
跨语言 ❌ 取决于 SDK ❌ Python/JS ✅ JSON-RPC 语言无关
独立部署 ❌ 打包在应用里 ❌ 打包在应用里 ✅ Server 独立进程/服务
生态复用 ❌ 自己写 ⚠️ 社区贡献 ✅ 标准化生态
版本兼容 跟着 OpenAI API 跟着 LangChain 有明确版本协商

关键点:三者不是替代关系,而是分工不同——

LLM (Function Calling 格式) → LangChain Tool (本地抽象) → MCP Client (跨进程协议) → MCP Server

MCP 是最外层的「连接协议」,LangChain Tool 是应用内的「工具接口」,Function Calling 是 LLM 的「输出格式」。本章的目标就是把这三层串起来。


12.2 MCP 协议的设计本质

第十一章用一节解释了“向量为什么能表示语义”。这一节对应地解释 MCP 的协议本质:它到底靠什么完成跨进程、跨语言的工具调用。

ai-edited-image.png

12.2.1 为什么选 JSON-RPC 2.0

MCP 底层使用 JSON-RPC 2.0 作为消息格式。这不是随意选择——JSON-RPC 恰好提供了 MCP 需要的三种消息模式:

1. Request(请求):Client → Server,带 id,期待 Response

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "analyze_completeness",
    "arguments": { "requirementText": "作为管理员..." }
  }
}

2. Response(响应):Server → Client,匹配 id

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "{\"completenessScore\": 67, ...}" }]
  }
}

3. Notification(通知):单向消息,无 id,不期待回复

{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

这里需要区分几个概念:

MCP 选择 JSON-RPC,是因为它需要的不只是传输通道,而是一套稳定的远程过程调用约定。传输层可以是 stdio,也可以是 HTTP;但消息语义保持一致,Client 和 Server 才能跨语言、跨进程协作。

12.2.2 MCP 生命周期状态机

stateDiagram-v2
  [*] --> Initializing: Client 发起连接
  Initializing --> Initialized: 能力协商完成
  Initialized --> Normal: 双方交换 initialized 通知
  Normal --> Normal: tools/list, tools/call, resources/read...
  Normal --> Shutdown: Client 或 Server 发起关闭
  Shutdown --> [*]: 连接释放

每个阶段的实际报文:

阶段 1:initialize(能力协商)

// Client → Server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": { "roots": { "listChanged": true } },
    "clientInfo": { "name": "autix-chat", "version": "1.0.0" }
  }
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true }
    },
    "serverInfo": { "name": "requirement-analyzer", "version": "1.0.0" }
  }
}

为什么需要能力协商? Client 不知道 Server 支持什么(可能只有 Tools 没有 Resources),Server 也不知道 Client 支持什么(可能不支持 sampling)。协商确保双方只使用对方支持的功能。

阶段 2:initialized(通知)

// Client → Server(Notification,无 id)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

阶段 3:Normal(正常交互)

此时可以发起 tools/listtools/callresources/listresources/read 等请求。

阶段 4:Shutdown

任意一方可以发起关闭。对于 stdio 传输,关闭 stdin 即可;对于 HTTP 传输,发送关闭请求或断开连接。

12.2.3 Session 的两种模式

模式 描述 传输 适用场景
有状态 Server 维护 session,记住协商结果 stdio / Streamable HTTP with Mcp-Session-Id 需要订阅、需要工具列表变更通知
无状态 每次请求独立,不保持上下文 Streamable HTTP without session 简单查询、Serverless 部署

12.2.4 版本演进

版本 时间 关键变化
2024-11-05 初始版本 基础 Tools/Resources/Prompts
2025-03-26 截至写作时的稳定版 新增 Streamable HTTP 传输、annotations、elicitation
latest 开发中 OAuth 2.1 集成增强

12.3 三种核心原语的设计哲学

理解 MCP,不能只看接口怎么写,还要看三种原语分别由谁控制。这个控制模型决定了工具、资料和 Prompt 在 Agent 系统中的边界。

MCP 定义了三种核心原语(Primitive)。这里的“原语”可以理解为协议里最基础的能力单元:所有上层功能都由这些能力组合出来。

三种原语的关键区别不在于“长得像不像工具”,而在于谁决定使用它

graph LR
  subgraph "谁控制调用?"
    T["🔧 Tools<br/>模型控制"]
    R["📄 Resources<br/>应用控制"]
    P["💬 Prompts<br/>用户控制"]
  end

12.3.1 Tools(模型控制)

Tools 是 MCP 最核心的原语——让 LLM 自主决定调用什么工具、传什么参数

定义结构

{
  "name": "analyze_completeness",
  "description": "分析需求描述的完整性...",
  "inputSchema": {
    "type": "object",
    "properties": {
      "requirementText": { "type": "string", "description": "需求描述文本" }
    },
    "required": ["requirementText"]
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "openWorldHint": false
  }
}

关键设计点

结果示例

{
  "content": [
    { "type": "text", "text": "{\"completenessScore\": 67, ...}" }
  ],
  "isError": false
}

isError vs 协议错误isError: true 表示工具执行失败(但协议通信正常),比如“输入的需求文本太短无法分析”。而 JSON-RPC error 表示协议层面的失败,比如“找不到这个工具”。

12.3.2 Resources(应用控制)

Resources 是只读数据源——由应用(而非 LLM)决定何时读取。

{
  "uri": "requirement://templates/prd",
  "name": "PRD 标准模板",
  "mimeType": "text/markdown"
}

与 Tool 的本质区别:Tool 是“让模型决定是否执行一个动作”,Resource 是“让应用按自己的流程读取一份资料”。后文 12.3.4 会用一张综合表统一对比 Tool / Resource / Prompt,这里先记住控制者不同即可。

动态 Resource Templates

{
  "uriTemplate": "requirement://projects/{projectId}/requirements",
  "name": "项目需求列表",
  "mimeType": "application/json"
}

URI 模板允许参数化资源路径,应用可以根据当前上下文填入 projectId

订阅更新:Client 可以订阅 Resource 变更通知——当 PRD 模板更新时,Server 发出 notifications/resources/updated

12.3.3 Prompts(用户控制)

Prompts 是 Server 提供的预定义 Prompt 模板——由用户(通过 UI)选择使用。

{
  "name": "analyze_requirement",
  "description": "需求分析标准 Prompt",
  "arguments": [
    { "name": "requirementText", "description": "需要分析的需求描述", "required": true }
  ]
}

调用后返回一组预构建的消息:

{
  "messages": [
    {
      "role": "user",
      "content": { "type": "text", "text": "请对以下需求进行全面分析:\n..." }
    }
  ]
}

使用场景:Server 作者是领域专家,他提供的 Prompt 模板比用户自己写的更专业——比如需求分析应该从哪些维度切入,竞品调研应该关注哪些点。

12.3.4 三者控制模型对比表

原语 谁发起 谁决策 谁确认 典型场景
Tool LLM 在对话中决定 模型自主 可选 Human-in-the-Loop 执行分析、搜索、写入
Resource 应用代码程序化读取 应用逻辑 不需要 加载模板、读取配置
Prompt 用户在 UI 中选择 用户手动 用户自己就是确认者 选择“深度分析”模板

12.4 从零搭建 MCP Server:需求分析工具

前面先把概念讲清楚,从这一节开始进入实现:先写一个最小可运行的 MCP Server,再逐步增加工具、资源和 Prompt。

12.4.1 最小可运行版本:一个文件、一个 Tool

目标:10 分钟搭建一个最小 MCP Server,只暴露 analyze_completeness 一个工具。

{
  "name": "@mcp-servers/requirement-analyzer",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.0",
    "zod": "^3.24.0"
  }
}

核心代码(完整实现在 mcp-servers/requirement-analyzer/src/index.ts):

server.tool(
  'analyze_completeness',
  '分析需求描述的完整性,检查是否缺少关键维度(用户角色、功能描述、验收标准、非功能需求等)',
  {
    requirementText: z.string().describe('需求描述文本'),
  },
  async ({ requirementText }) => {
    const dimensions = [
      { name: '用户角色', keywords: ['用户', '角色', '作为', 'PM', '开发', '管理员', '运营'], found: false },
      { name: '功能描述', keywords: ['能够', '可以', '支持', '实现', '功能', '需要', '希望'], found: false },
      { name: '验收标准', keywords: ['验收', '标准', '条件', '期望', '预期结果', '应该', '必须'], found: false },
      { name: '优先级', keywords: ['优先', 'P0', 'P1', 'P2', '紧急', '重要', '高', '低'], found: false },
      { name: '非功能需求', keywords: ['性能', '安全', '可用性', '并发', '响应时间', '可靠', '稳定'], found: false },
      { name: '边界条件', keywords: ['边界', '异常', '限制', '最大', '最小', '超出', '错误', '失败'], found: false },
    ];

    for (const dim of dimensions) {
      dim.found = dim.keywords.some((kw) => requirementText.includes(kw));
    }

    const missing = dimensions.filter((d) => !d.found).map((d) => d.name);
    const covered = dimensions.filter((d) => d.found).map((d) => d.name);
    const score = Math.round((covered.length / dimensions.length) * 100);

    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(
            {
              completenessScore: score,
              totalDimensions: dimensions.length,
              coveredDimensions: covered,
              missingDimensions: missing,
              suggestion:
                missing.length > 0
                  ? `建议补充以下维度:${missing.join('、')}`
                  : '需求描述较为完整',
            },
            null,
            2,
          ),
        },
      ],
    };
  },
);

启动和验证

cd mcp-servers/requirement-analyzer
bun install
bun run dev

Server 通过 stdio 通信——它从 stdin 读取 JSON-RPC 消息,向 stdout 写入响应。你看不到交互式输出(那些诊断信息打到了 stderr)。

image.png

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.4.1"
验证点 预期
返回标准 { content: [{ type: 'text', text: ... }] } 结构
全维度覆盖时分数为 100

image 1.png

12.4.2 加入更多 Tools

analyze_completeness 的基础上,加入三个工具:

Tool 功能 关键逻辑
estimate_complexity 估算复杂度 正则匹配复杂因子,加权计分,映射 T-shirt size
check_conflicts 冲突检查 提取关键词,计算与现有需求的重叠度
generate_user_stories 生成用户故事 提取角色和动作,套用 User Story 模板
server.tool(
  'estimate_complexity',
  '估算需求的技术复杂度,返回 T-shirt size(S/M/L/XL)和估算依据',
  {
    requirementText: z.string().describe('需求描述文本'),
    techStack: z.string().optional().describe('技术栈(可选,用于更精确的估算)'),
  },
  async ({ requirementText }) => {
    let score = 0;
    const factors: string[] = [];

    if (/集成|对接|第三方|API|接口|外部/.test(requirementText)) {
      score += 3;
      factors.push('涉及外部系统集成');
    }
    if (/迁移|导入|导出|批量|同步|Excel|CSV/.test(requirementText)) {
      score += 2;
      factors.push('涉及数据处理/迁移');
    }
    if (/权限|角色|鉴权|审批|多租户/.test(requirementText)) {
      score += 2;
      factors.push('涉及权限体系');
    }
    if (/实时|推送|WebSocket|通知|消息/.test(requirementText)) {
      score += 2;
      factors.push('涉及实时通信');
    }
    if (/AI|智能|推荐|预测|模型|LLM/.test(requirementText)) {
      score += 3;
      factors.push('涉及 AI/ML 能力');
    }
    if (/加密|安全|合规|审计/.test(requirementText)) {
      score += 1;
      factors.push('有安全合规要求');
    }
    if (requirementText.length > 500) {
      score += 1;
      factors.push('需求描述较长,可能涉及多个子功能');
    }

    const size = score <= 2 ? 'S' : score <= 4 ? 'M' : score <= 6 ? 'L' : 'XL';
    const estimatedDays = { S: '1-3天', M: '3-7天', L: '1-3周', XL: '3周以上' }[size];
    // ...
  },
);

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.4"
验证点 预期
estimate_complexity 简单需求返回 S
estimate_complexity 复杂需求返回 L 或 XL
check_conflicts 关键词重叠 ≥ 3 时检出冲突
generate_user_stories 生成符合格式的用户故事
tools/list 返回 4 个工具

image 2.png

12.4.3 加入 Resources

在同一个 Server 中加入两个 Resource:

server.resource('requirement://templates/prd', 'PRD(产品需求文档)标准模板', async () => ({
  contents: [
    {
      uri: 'requirement://templates/prd',
      mimeType: 'text/markdown',
      text: `# PRD: [需求标题]

## 1. 背景与目标
- 业务背景:
- 用户痛点:
- 预期目标:

## 2. 用户角色
| 角色 | 描述 | 核心诉求 |
|------|------|----------|

## 3. 功能需求
### 3.1 核心功能
### 3.2 辅助功能

## 4. 非功能需求
- 性能:
- 安全:
- 可用性:

## 5. 验收标准
- Given [前置条件]
- When [用户操作]
- Then [预期结果]

## 6. 排期与里程碑`,
    },
  ],
}));

Resource 是只读的数据,应用可以在构建 Prompt 时主动拉取模板内容,而不是让 LLM 决定。

12.4.4 加入 Prompts

server.prompt(
  'analyze_requirement',
  '需求分析的标准 Prompt 模板,引导 LLM 对一段需求做全面分析',
  { requirementText: z.string().describe('需要分析的需求描述') },
  async ({ requirementText }) => ({
    messages: [
      {
        role: 'user' as const,
        content: {
          type: 'text' as const,
          text: `请对以下需求进行全面分析:

【需求描述】
${requirementText}

请从以下维度分析:
1. 完整性:是否缺少用户角色、功能边界、验收标准、非功能需求?
2. 可行性:技术复杂度如何?有哪些技术风险?
3. 优先级建议:基于业务价值和技术成本给出 P0/P1/P2 建议
4. 拆分建议:如果需求过大,建议如何拆分为可独立交付的子需求?
5. 潜在风险:时间风险、技术风险、依赖风险`,
        },
      },
    ],
  }),
);

Server 作者(需求分析领域专家)提供了“应该从哪几个维度分析需求”的标准 Prompt。用户在 UI 中选择后,这段专业 Prompt 会自动注入对话。

12.4.5 用 MCP Inspector 调试

MCP 官方提供了 Inspector 工具,可以可视化地与 Server 交互:

npx @modelcontextprotocol/inspector tsx mcp-servers/requirement-analyzer/src/index.ts

Inspector 会在浏览器中打开一个 UI,你可以:

Inspector 可以理解为 MCP Server 的调试台,作用类似 HTTP 开发中的 Postman:不用先写 Client 代码,就能查看工具列表、填写参数、观察返回结果和原始协议消息。


12.5 MCP Client:连接与调用

12.5.1 Client 生命周期

sequenceDiagram
  participant C as MCP Client
  participant S as MCP Server

  C->>S: connect (建立传输)
  C->>S: initialize (协商能力)
  S->>C: initialize result (Server 能力)
  C->>S: notifications/initialized
  C->>S: tools/list
  S->>C: tools (4 个工具)
  C->>S: tools/call (analyze_completeness)
  S->>C: result (completenessScore: 67)
  C->>S: close

12.5.2 最小 Client 实现

/**
 * MCP Client Service
 *
 * 封装对单个 MCP Server 的连接、生命周期管理和工具调用。
 * 生命周期:connect → initialize → listTools → callTool → close
 */
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import type { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';

export interface MCPClientConfig {
  /** Server 可执行文件的命令 */
  command: string;
  /** 命令参数 */
  args?: string[];
  /** 环境变量 */
  env?: Record<string, string>;
  /** 连接超时(ms) */
  timeout?: number;
}

export class MCPClientService {
  private client: Client;
  private transport: StdioClientTransport | null = null;
  private connected = false;
  private tools: Tool[] = [];
  private readonly config: MCPClientConfig;

  constructor(config: MCPClientConfig) {
    this.config = config;
    this.client = new Client(
      { name: 'autix-chat-client', version: '1.0.0' },
      { capabilities: {} },
    );
  }

  async connect(): Promise<void> {
    if (this.connected) return;

    this.transport = new StdioClientTransport({
      command: this.config.command,
      args: this.config.args,
      env: { ...process.env, ...this.config.env } as Record<string, string>,
    });

    await this.client.connect(this.transport);
    this.connected = true;

    const { tools } = await this.client.listTools();
    this.tools = tools;
  }

以上代码基于 @modelcontextprotocol/sdk v1.12.x。SDK 仍在快速迭代中,Client 构造函数签名可能随版本变化,建议以实际安装版本的类型定义为准。

关键设计connect() 完成后立即 listTools() 缓存工具列表。后续调用直接查缓存,无需重复网络请求。

12.5.3 MCP Tool → LangChain Tool 桥接器

这是本章最关键的适配层:把 MCP 里运行时发现的工具转换成 LangChain 的 DynamicStructuredTool,让 LangGraph Agent 可以像使用本地工具一样调用外部 MCP 工具。

/**
 * MCP Tool → LangChain Tool 桥接器
 *
 * 将 MCP Server 暴露的 Tools 动态转换为 LangChain 的 DynamicStructuredTool,
 * 使其可被 LangGraph Agent 直接使用。
 *
 * 核心转换:JSON Schema → Zod Schema,MCP content[] → string
 */
import { DynamicStructuredTool } from '@langchain/core/tools';
import { z, ZodObject, ZodRawShape, ZodTypeAny } from 'zod';
import type { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
import type { MCPClientService } from './mcp-client.service.js';

/**
 * 将 MCP Server 的所有工具转换为 LangChain Tools
 * @param client - 已连接的 MCPClientService 实例
 * @param prefix - 工具名前缀(用于多 Server 去重),如 "req_"
 */
export function bridgeMCPToLangChain(
  client: MCPClientService,
  prefix = '',
): DynamicStructuredTool[] {
  const mcpTools = client.getTools();
  return mcpTools.map((tool) => mcpToolToLangChain(tool, client, prefix));
}

function mcpToolToLangChain(
  tool: MCPTool,
  client: MCPClientService,
  prefix: string,
): DynamicStructuredTool {
  const zodSchema = jsonSchemaToZod(tool.inputSchema as JsonSchemaObject);

  return new DynamicStructuredTool({
    name: `${prefix}${tool.name}`,
    description: tool.description || tool.name,
    schema: zodSchema,
    func: async (args) => {
      const result = await client.callTool(tool.name, args);
      return serializeMCPContent(result.content as MCPContent[]);
    },
  });
}

桥接的三步转换

  1. JSON Schema → Zod:MCP 用 JSON Schema 描述参数,LangChain 用 Zod。需要运行时转换。
  2. 调用代理:LangChain 调用 func(args) → 内部转为 client.callTool(name, args)
  3. 结果序列化:MCP 返回 content[] 数组 → 序列化为 LangChain 期望的 string

动态工具发现的意义:Server 新增工具后,Client 下次 connect() 自动获取新工具列表,无需改任何 Client 代码。这就是 MCP 相对于硬编码工具的核心价值。

桥接层最容易卡在 Schema 转换和结果序列化。下面给出一个最小可用的 jsonSchemaToZod 递归转换片段;完整工程可以继续补充 formatoneOfanyOfadditionalProperties 等边界类型:

type JsonSchemaObject = {
  type?: string;
  properties?: Record<string, JsonSchemaObject>;
  required?: string[];
  items?: JsonSchemaObject;
  enum?: unknown[];
  description?: string;
};

function jsonSchemaToZod(schema: JsonSchemaObject): ZodTypeAny {
  let zod: ZodTypeAny;

  if (schema.enum?.length) {
    const values = schema.enum.map(String);
    zod = z.enum(values as [string, ...string[]]);
  } else {
    switch (schema.type) {
      case 'string':
        zod = z.string();
        break;
      case 'number':
      case 'integer':
        zod = z.number();
        break;
      case 'boolean':
        zod = z.boolean();
        break;
      case 'array':
        zod = z.array(jsonSchemaToZod(schema.items ?? {}));
        break;
      case 'object': {
        const shape: ZodRawShape = {};
        const required = new Set(schema.required ?? []);
        for (const [key, child] of Object.entries(schema.properties ?? {})) {
          const childZod = jsonSchemaToZod(child);
          shape[key] = required.has(key) ? childZod : childZod.optional();
        }
        zod = z.object(shape);
        break;
      }
      default:
        zod = z.unknown();
    }
  }

  return schema.description ? zod.describe(schema.description) : zod;
}

工具结果也需要序列化:LangChain Tool 更适合返回字符串,而 MCP 原始结果是结构化的 content[]

function serializeMCPContent(content: MCPContent[]): string {
  return content
    .map((item) => {
      if (item.type === 'text') return item.text;
      if (item.type === 'image') return `[image: ${item.mimeType}]`;
      if (item.type === 'resource') return `[resource: ${item.resource.uri}]`;
      return `[unsupported content: ${item.type}]`;
    })
    .join('\n');
}

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.5"
验证点 预期
JSON Schema → Zod:string 类型正确
JSON Schema → Zod:number 类型正确
JSON Schema → Zod:optional 字段正确
JSON Schema → Zod:array 类型正确
JSON Schema → Zod:嵌套 object 正确
桥接后工具数量 = tools/list 数量

image 3.png


12.6 传输层:从本地到远程

12.6.1 stdio:最简单的传输

graph LR
  Client["MCP Client<br/>(父进程)"] -- stdin --> Server["MCP Server<br/>(子进程)"]
  Server -- stdout --> Client
  Server -- stderr --> Log["日志输出"]

stdio(standard input / standard output) 指标准输入和标准输出。它不是网络协议,而是操作系统里最基础的进程通信方式:父进程把消息写入子进程的 stdin,子进程把响应写回 stdout。

在 MCP 场景中,Client 会把 Server 启动成一个子进程,然后通过 stdin/stdout 发送 JSON-RPC 消息。诊断日志应写到 stderr,避免污染 stdout 里的协议消息。

优势:零网络配置、无端口冲突、进程隔离,适合本地开发和桌面应用。 限制:只能本机使用,不适合多个 Client 共享同一个远程 Server。

// Client 侧初始化 stdio 传输
const transport = new StdioClientTransport({
  command: 'tsx',
  args: ['mcp-servers/requirement-analyzer/src/index.ts'],
});
await client.connect(transport);

12.6.2 Streamable HTTP:生产首选

graph LR
  C1["Client 1"] -- "POST /mcp\n(Mcp-Session-Id: abc)" --> S["MCP Server\n(HTTP)"]
  C2["Client 2"] -- "POST /mcp\n(Mcp-Session-Id: def)" --> S
  S -- "SSE stream / JSON" --> C1
  S -- "SSE stream / JSON" --> C2

Streamable HTTP 是 MCP 当前推荐的远程传输方式。它仍然使用 HTTP 作为基础协议,但允许 Server 根据请求情况返回普通 JSON 响应,或返回一段可持续输出的事件流。

这里的 SSE(Server-Sent Events) 可以理解为“服务器向客户端持续推送文本事件”的机制。它常用于长耗时任务、流式输出和服务端通知。

工作原理

有状态 vs 无状态模式

// 有状态:Server 维持 session,支持工具列表变更通知
// 适用于长期运行的 Agent
const transport = new StreamableHTTPClientTransport({
  url: 'https://mcp.example.com/mcp',
});

// 无状态:每次请求独立,适合 Serverless
// Server 不返回 Mcp-Session-Id header

部署架构

graph TB
  subgraph "生产部署"
    LB["负载均衡"] --> S1["Server 实例 1"]
    LB --> S2["Server 实例 2"]
    S1 --> Redis["Redis (Session Store)"]
    S2 --> Redis
  end

12.6.3 SSE(已过时)

早期 MCP 版本使用 Server-Sent Events 作为传输层:Client POST 发起,Server 通过 SSE 推送响应。已被 Streamable HTTP 取代,新项目不推荐使用。

12.6.4 Streamable HTTP Server 端最小示例

前面的 requirement-analyzer 使用的是 stdio。切到 Streamable HTTP 时,Client 侧只需要换 transport,但 Server 侧也要把 MCP Server 挂到 HTTP endpoint 上。下面是一个最小结构示例:

import express from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';

function createRequirementServer() {
  const server = new McpServer({
    name: 'requirement-analyzer',
    version: '1.0.0',
  });

  server.tool(
    'analyze_completeness',
    '分析需求描述的完整性',
    { requirementText: z.string() },
    async ({ requirementText }) => ({
      content: [
        {
          type: 'text',
          text: JSON.stringify({ length: requirementText.length }),
        },
      ],
    }),
  );

  return server;
}

const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
  const server = createRequirementServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => randomUUID(),
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000, () => {
  console.error('MCP HTTP server listening on http://localhost:3000/mcp');
});

这段代码只展示最小结构:HTTP endpoint 负责接收 JSON-RPC 请求,StreamableHTTPServerTransport 负责把 HTTP 请求交给 MCP Server。生产环境还需要补充认证、session 存储、限流、超时和日志。

12.6.5 传输切换的代码改动

从 stdio 到 HTTP 只需改 transport 初始化——其余业务代码完全不变:

// 开发环境:stdio
const transport = new StdioClientTransport({
  command: 'tsx',
  args: ['mcp-servers/requirement-analyzer/src/index.ts'],
});

// 生产环境:Streamable HTTP(只改这一处)
const transport = new StreamableHTTPClientTransport({
  url: process.env.MCP_REQUIREMENT_ANALYZER_URL,
});

// 后续代码完全相同
await client.connect(transport);
const { tools } = await client.listTools();
await client.callTool({ name: 'analyze_completeness', arguments: {...} });

这是 MCP 传输层抽象的核心价值:业务逻辑与通信方式解耦


12.7 第二个 MCP Server:网络搜索

12.7.1 为什么需求分析需要搜索

在实际需求分析工作中,以下场景需要外部信息:

场景 需要搜索什么 对应工具
竞品调研 竞品如何实现类似功能 search_competitors
方案参考 行业最佳实践和设计模式 search_best_practices
工期估算 类似技术方案的实施经验 search_tech_stack

与第十一章 CRAG 的关联:回顾 11.9.3 CRAG(Corrective RAG),当 RAG 检索结果质量不够时,会 fallback 到 Web 搜索。这里的 Web Search MCP Server 就是 CRAG “Web 兜底” 路径的标准化封装——同样的搜索能力,MCP 化后可以被任意 Agent 复用,不再绑定在某一条 RAG pipeline 里。

12.7.2 搭建 Web Search MCP Server

完整代码在 mcp-servers/web-search/src/index.ts

server.tool(
  'search_competitors',
  '搜索竞品的相关功能实现,了解市场上类似产品的做法。适用于需求分析时调研竞品如何实现类似功能。',
  {
    query: z.string().describe('搜索关键词,如"Jira 批量导入功能"、"Notion AI 写作助手"'),
    domain: z.string().optional().describe('限定搜索域名,如"atlassian.com"'),
  },
  async ({ query, domain }) => {
    const searchQuery = `${query} product feature implementation`;
    const results = await doSearch(searchQuery, domain);
    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(
            {
              query,
              mode: IS_MOCK ? 'mock' : 'live',
              results: results.map((r) => ({
                title: r.title,
                snippet: r.snippet,
                url: r.url,
              })),
              summary: results.length > 0
                ? `找到${results.length} 个竞品参考,涵盖:${results.map((r) => r.title).join('、')}`
                : '未找到相关竞品信息',
            },
            null,
            2,
          ),
        },
      ],
    };
  },
);

12.7.3 Mock 模式设计

const TAVILY_API_KEY = process.env.TAVILY_API_KEY;
const IS_MOCK = !TAVILY_API_KEY;

设计原则:无 API Key 时自动降级为 Mock 模式,返回预置结果,让读者无需注册第三方服务也能跑通完整流程。Mock 数据覆盖批量导入、权限设计、实时通信等常见场景,保证测试和学习过程不依赖外部网络。

生产环境配置 TAVILY_API_KEY 后自动切换为真实搜索。

12.7.4 底层搜索实现

async function tavilySearch(query: string, domain?: string): Promise<SearchResult[]> {
  const body: Record<string, unknown> = {
    query,
    max_results: 5,
    search_depth: 'basic',
  };
  if (domain) {
    body.include_domains = [domain];
  }

  const res = await fetch('https://api.tavily.com/search', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ ...body, api_key: TAVILY_API_KEY }),
  });

  if (!res.ok) {
    // 降级到 mock
    return getMockResults(query);
  }
  // ...
}

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.7"
验证点 预期
mock 模式返回预置结果
search_competitors 批量导入查询返回 Jira/Linear 参考
search_best_practices 权限设计返回 RBAC 参考
search_tech_stack WebSocket 返回技术对比
tools/list 返回 3 个工具

image 4.png


12.8 多 Server 编排与协同

12.8.1 MCPManager

一个真实 Agent 往往不会只连接一个 MCP Server。需求分析 Server 负责业务分析,Web Search Server 负责外部搜索,后续还可能接入 Jira、GitHub、数据库或内部 RAG 服务。

这时就需要一个统一管理层。本文把它称为 MCPManager:它不是 MCP 官方协议的一部分,而是本项目为了管理多个 Server 连接而实现的工程组件。

/**
 * MCP Manager
 *
 * 统一管理多个 MCP Server 连接,提供:
 * - 工具命名空间隔离(前缀策略)
 * - 按需连接 / 启动时预连接
 * - 降级策略:Server 不可用时回退本地工具
 * - 所有工具的聚合列表
 */
import { DynamicStructuredTool } from '@langchain/core/tools';
import { MCPClientService, MCPClientConfig } from './mcp-client.service.js';
import { bridgeMCPToLangChain } from './mcp-to-langchain.js';

export interface ServerRegistration {
  id: string;
  config: MCPClientConfig;
  prefix?: string;
  /** 连接失败时的降级工具 */
  fallbackTools?: DynamicStructuredTool[];
}

export class MCPManager {
  private servers = new Map<string, MCPClientService>();
  private registrations: ServerRegistration[] = [];
  private tools: DynamicStructuredTool[] = [];

  register(registration: ServerRegistration): void {
    this.registrations.push(registration);
  }

  async connectAll(): Promise<void> {
    const results = await Promise.allSettled(
      this.registrations.map((reg) => this.connectOne(reg)),
    );
    // 连接失败的 Server 使用 fallbackTools 降级...
  }

12.8.2 工具命名空间策略

当多个 Server 有同名工具时(比如两个 Server 都有 search),需要命名隔离:

manager.register({
  id: 'requirement',
  config: { command: 'tsx', args: ['mcp-servers/requirement-analyzer/src/index.ts'] },
  prefix: 'req_',  // → req_analyze_completeness, req_estimate_complexity
});

manager.register({
  id: 'websearch',
  config: { command: 'tsx', args: ['mcp-servers/web-search/src/index.ts'] },
  prefix: 'ws_',   // → ws_search_competitors, ws_search_best_practices
});

前缀策略保证了:

12.8.3 实战组合:三 Server 协同

graph TB
  Agent["LangGraph Agent"] --> Manager["MCPManager"]
  Manager --> S1["requirement-analyzer<br/>需求分析 4 工具"]
  Manager --> S2["web-search<br/>网络搜索 3 工具"]
  Manager --> S3["RAG Server<br/>知识库检索"]

  subgraph "协同场景"
    A["需求完整性不够"] --> B["搜索竞品补充"]
    C["知识库无结果"] --> D["Web 搜索兜底"]
    E["复杂度估算"] --> F["搜索类似方案参考工期"]
  end

典型协同流程

  1. 用户提交需求 → Agent 调用 req_analyze_completeness → 发现缺少“验收标准”
  2. Agent 调用 ws_search_competitors → 搜索竞品如何定义类似功能的验收标准
  3. Agent 调用 req_estimate_complexity → 结合搜索结果给出复杂度评估

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.8"
验证点 预期
两个 Server 工具列表正确合并
合计 7 个工具(4 + 3)

image 5.png


12.9 错误处理与韧性

MCP 把工具调用从“本地函数调用”变成了“跨进程 / 跨网络调用”。这意味着故障类型会变多:Server 可能启动失败,连接可能断开,参数可能不合法,工具执行可能超时,远程服务也可能返回 500。

这里的 韧性(resilience),指系统在部分组件失败时仍能继续工作的能力。对 MCP 来说,韧性主要包括三件事:错误可分类、调用可重试、服务不可用时可降级。

12.9.1 JSON-RPC 标准错误码

错误码 含义 常见原因
-32700 Parse error 发送的不是合法 JSON
-32600 Invalid Request 缺少 jsonrpc/method 字段
-32601 Method not found 调用了不存在的方法
-32602 Invalid params 参数类型/格式错误
-32603 Internal error Server 内部异常

12.9.2 Client 重试策略

下面这段属于调用层:单次工具调用失败时,判断错误是否可重试,并用指数退避重试。

async function callToolWithRetry(
  client: MCPClientService,
  name: string,
  args: Record<string, unknown>,
  options = { maxRetries: 3, baseDelay: 1000 }
): Promise<CallToolResult> {
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    try {
      return await client.callTool(name, args);
    } catch (err: any) {
      if (attempt === options.maxRetries) throw err;
      // 不可重试的错误
      if (err.code === -32601 || err.code === -32602) throw err;
      // 指数退避
      const delay = options.baseDelay * Math.pow(2, attempt);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error('unreachable');
}

重试策略要点

12.9.3 Server 崩溃恢复

下面这段属于连接管理层:不是重试某一次 tools/call,而是在 MCP Server 进程或连接断开后,由 MCPManager 负责重连或切换到 fallback 工具。

// MCPManager 中的自动重连逻辑
private async connectOne(reg: ServerRegistration): Promise<void> {
  const client = new MCPClientService(reg.config);

  client.onDisconnect(async () => {
    console.error(`[MCPManager] Server "${reg.id}" disconnected, reconnecting...`);
    try {
      await client.connect();
      console.error(`[MCPManager] Server "${reg.id}" reconnected`);
    } catch {
      console.error(`[MCPManager] Server "${reg.id}" reconnect failed, using fallback`);
      // 切换到降级工具
    }
  });

  await client.connect();
}

12.9.4 降级策略

manager.register({
  id: 'websearch',
  config: { command: 'tsx', args: ['mcp-servers/web-search/src/index.ts'] },
  prefix: 'ws_',
  fallbackTools: [
    // MCP Server 不可用时,使用本地硬编码的工具
    new DynamicStructuredTool({
      name: 'ws_search_competitors',
      description: '搜索竞品(降级模式:返回提示信息)',
      schema: z.object({ query: z.string() }),
      func: async () => '搜索服务暂时不可用,建议稍后重试或手动搜索',
    }),
  ],
});

降级策略原则:MCP 不可用时,Agent 不应该卡死——应返回“服务暂时不可用”的清晰提示,让 LLM 可以跳过这个步骤继续分析。

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.9"
验证点 预期
调用不存在的工具返回错误(不崩溃)

image 6.png


12.10 安全模型

工具一旦可以访问外部系统,就必须讨论安全。MCP Server 可能读取内部文档,也可能写入项目管理系统、调用数据库、触发审批流。协议本身提供了能力声明和 annotations,但真正的安全边界仍然要由 Host 和业务系统共同控制。

本节会用三个概念组织安全模型:

12.10.1 信任边界三层模型

graph LR
  subgraph "信任边界"
    U["👤 User"] -->|完全信任| C["🖥️ Client (Host)"]
    C -->|有限信任| S["⚙️ MCP Server"]
    S -->|零信任| E["🌐 External Service"]
  end

12.10.2 工具权限分级

type PermissionLevel = 'read' | 'write' | 'admin';

const toolPermissions: Record<string, PermissionLevel> = {
  // 只读工具:可安全自动调用
  analyze_completeness: 'read',
  estimate_complexity: 'read',
  check_conflicts: 'read',
  search_competitors: 'read',
  search_best_practices: 'read',

  // 写入工具:需要用户确认
  create_requirement: 'write',
  update_requirement: 'write',

  // 管理员工具:需要强确认
  delete_requirement: 'admin',
  purge_all_data: 'admin',
};

function requiresConfirmation(toolName: string): boolean {
  const level = toolPermissions[toolName];
  return level === 'write' || level === 'admin';
}

12.10.3 Human-in-the-Loop

对于写操作,在 Agent 执行前请求用户确认:

// LangGraph 中的 interrupt 机制(回顾第九章)
if (requiresConfirmation(toolCall.name)) {
  // 暂停 Agent,向用户展示即将执行的操作
  return interrupt({
    action: toolCall.name,
    params: toolCall.args,
    message: `即将执行${toolCall.name},是否确认?`,
  });
}

12.10.4 Prompt 注入防护

Server 返回的内容可能被注入恶意指令。防护策略:

  1. 内容清洗:移除 Server 返回中的可疑指令(如 “ignore previous instructions”)
  2. 角色隔离:Server 返回内容标记为 tool_result,不混入 system role
  3. 输出长度限制:防止 Server 返回超长内容挤占上下文

12.10.5 OAuth 2.1 授权流程(Remote Server)

对于远程部署的 MCP Server(如公司内部的 Jira MCP Server),MCP 规范定义了 OAuth 2.1 授权流程:

sequenceDiagram
  participant U as User
  participant C as MCP Client
  participant AS as Auth Server
  participant S as MCP Server

  C->>S: initialize (无 token)
  S->>C: 401 + WWW-Authenticate
  C->>U: 请求授权
  U->>AS: 浏览器授权
  AS->>C: authorization_code
  C->>AS: 换 access_token
  C->>S: initialize (Bearer token)
  S->>C: 200 OK

以上流程简化为 HTTP 语义展示。实际实现中,MCP 远程 Server 的认证错误可能在 JSON-RPC 层面返回,具体需参考 MCP 授权规范。

📋 本节配套用例

bun test test/chapter12-mcp.spec.ts -t "12.10"
验证点 预期
read 工具不需要确认
write 工具需要确认
admin 工具需要确认

image 7.png


12.11 测试策略

12.11.1 两层测试架构

graph TB
  subgraph "Layer 1: 协议层单元测试"
    T1["InMemoryTransport<br/>零网络依赖"]
    T2["验证 tool 逻辑正确性"]
    T3["验证 JSON Schema 转换"]
    T4["验证错误处理"]
  end

  subgraph "Layer 2: LLM 集成测试"
    T5["真实 LLM 调用"]
    T6["验证 Agent 工具选择"]
    T7["验证多工具协同"]
  end

  T1 --> T2 --> T3 --> T4
  T5 --> T6 --> T7

12.11.2 Layer 1:InMemoryTransport

MCP SDK 提供 InMemoryTransport.createLinkedPair() 用于测试——创建一对互联的内存传输,Server 和 Client 在同一进程中通过内存通信:

import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';

const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

// Server 用 serverTransport
await server.connect(serverTransport);

// Client 用 clientTransport
await client.connect(clientTransport);

// 现在 client.callTool() 会通过内存直达 server

优势

12.11.3 Layer 2:LLM 集成测试

// chapter12-mcp-llm.spec.ts
it('Agent 收到"分析需求完整性" → 选择 analyze_completeness', async () => {
  const agent = createReactAgent({ llm, tools });
  const result = await agent.invoke({
    messages: [new HumanMessage('请分析这个需求的完整性:作为管理员...')],
  });

  const toolCalls = result.messages
    .filter((m) => m.tool_calls?.length > 0)
    .flatMap((m) => m.tool_calls.map((tc) => tc.name));

  expect(toolCalls).toContain('analyze_completeness');
});

运行方式

# Layer 1:无需任何 API Key
bun test test/chapter12-mcp.spec.ts

# Layer 2:需要 OPENAI_API_KEY
OPENAI_API_KEY=sk-... bun test test/chapter12-mcp-llm.spec.ts

12.11.4 MCP Inspector 验证

对于手动验证和探索性测试,MCP Inspector 比自动化测试更直观:

# 启动 Inspector 连接需求分析 Server
npx @modelcontextprotocol/inspector tsx mcp-servers/requirement-analyzer/src/index.ts

# 启动 Inspector 连接搜索 Server
npx @modelcontextprotocol/inspector tsx mcp-servers/web-search/src/index.ts

原始图片缺失占位图

📋 完整测试运行

# 运行所有 Layer 1 测试
bun test test/chapter12-mcp.spec.ts

# 运行指定章节
bun test test/chapter12-mcp.spec.ts -t "12.4.1"
bun test test/chapter12-mcp.spec.ts -t "12.5"
bun test test/chapter12-mcp.spec.ts -t "12.7"

12.12 可观测性与 Token 经济学

第十章已经讨论过 Token 成本,第十一章也强调了 RAG Trace。本章接入 MCP 后,可观测性需要继续向工具调用层下沉:不仅要知道 LLM 花了多少 token,还要知道它调用了哪个 Server、哪个工具、耗时多久、是否失败、返回内容有多长。

这里的 Trace 可以理解为一次调用的“流水账”。它不只是日志,更是后续排障、评估和成本优化的基础数据。

12.12.1 MCP 调用的 Trace 结构

每次 MCP 工具调用应产生一条 trace:

interface MCPCallTrace {
  traceId: string;
  serverId: string;
  toolName: string;
  startTime: number;
  endTime: number;
  latencyMs: number;
  inputArgs: Record<string, unknown>;
  outputSize: number;      // 返回内容的字符数
  isError: boolean;
  errorCode?: number;
  transport: 'stdio' | 'http';
}

12.12.2 与第十章 Token 成本采集的协同

回顾第十章的 Token 经济学,MCP 工具调用影响成本的环节:

环节 Token 消耗 优化策略
工具描述(system prompt) 每个工具 ~50-100 tokens 只加载当前需要的 Server 的工具
工具参数(LLM 输出) 取决于参数复杂度 简化 inputSchema 描述
工具结果(注入上下文) 取决于返回内容长度 Server 端控制返回大小

实际计算:假设连接 3 个 Server 共 7 个工具,每个工具描述 ~80 tokens:

优化策略

12.12.3 监控指标

指标 告警阈值 含义
工具调用延迟 P95 > 5s Server 响应慢
工具错误率 > 5% Server 不稳定
连接失败率 > 10% 网络/Server 进程问题
工具未使用率 > 80% 未被 LLM 选择 工具描述不够清晰,LLM 不知道何时用

12.13 集成 LangGraph Agent(完整实战)

前面已经完成 Server、Client、桥接、多 Server 管理和安全策略。现在把这些部分接回第九章的 LangGraph Agent,形成一条完整链路。

12.13.1 目标架构

generated-image-1779534585171.png

graph TB
  User["用户输入需求"] --> Agent["LangGraph Agent<br/>(Supervisor + 专家)"]

  Agent --> MCPMgr["MCPManager"]
  MCPMgr --> S1["requirement-analyzer<br/>4 tools"]
  MCPMgr --> S2["web-search<br/>3 tools"]
  MCPMgr --> S3["RAG Server<br/>(第11章能力)"]

  Agent --> Budget["Token Budget<br/>(第10章)"]

  subgraph "完整流水线"
    Step1["1. analyze_completeness<br/>评估需求完整性"]
    Step2["2. search_competitors<br/>搜索竞品参考"]
    Step3["3. search_knowledge_base<br/>查内部知识库"]
    Step4["4. estimate_complexity<br/>给出复杂度评估"]
    Step5["5. 综合输出分析报告"]
  end

  Step1 --> Step2 --> Step3 --> Step4 --> Step5

12.13.2 注入 MCP 工具到专家系统

回顾第九章的 Supervisor + 专家架构,现在用 MCP 替代硬编码工具。核心变化不是“换一个工具实现”,而是把工具来源从编译时固定改成运行时发现

在第九章里,功能专家拿到的是本地写死的 readFeatureSpecToolloadPerfBaselineTool。这一章里,功能专家拿到的是 MCPManager 聚合后的工具列表。Agent 不需要知道工具来自哪个进程、哪个服务、哪种传输方式,只需要按 LangChain Tool 的接口调用即可。

import { MCPManager } from '../mcp/mcp-manager.js';
import { createReactAgent } from '@langchain/langgraph/prebuilt';

// 初始化 MCPManager
const manager = new MCPManager();

manager.register({
  id: 'requirement',
  config: { command: 'tsx', args: ['mcp-servers/requirement-analyzer/src/index.ts'] },
  prefix: 'req_',
});

manager.register({
  id: 'websearch',
  config: { command: 'tsx', args: ['mcp-servers/web-search/src/index.ts'] },
  prefix: 'ws_',
});

await manager.connectAll();

// 获取所有 MCP 工具
const mcpTools = manager.getTools();

// 创建功能专家 Agent,注入 MCP 工具
const featureExpert = createReactAgent({
  llm: model,
  tools: mcpTools,
  prompt: `你是一个需求分析专家。你可以使用以下工具:
    - req_analyze_completeness: 分析需求完整性
    - req_estimate_complexity: 估算复杂度
    - req_check_conflicts: 检查冲突
    - req_generate_user_stories: 生成用户故事
    - ws_search_competitors: 搜索竞品
    - ws_search_best_practices: 搜索最佳实践
    - ws_search_tech_stack: 搜索技术方案参考
    根据用户的需求,自主决定调用哪些工具,并给出完整的分析报告。`,
});

这段代码需要关注三点:

  1. 工具注册前置到 MCPManager:Agent 不再直接依赖某个工具文件,而是依赖统一的工具管理层。
  2. 工具名前缀保持全局唯一req_ 表示需求分析 Server,ws_ 表示 Web Search Server,避免多个 Server 都有 searchanalyze 这类重名工具。
  3. Agent 只感知 LangChain Tool:桥接层已经把 MCP Tool 转成 DynamicStructuredTool,所以 LangGraph 不需要理解 MCP 协议细节。

对比之前

维度 第九章硬编码工具 第十二章 MCP 工具
工具来源 项目代码内 独立 MCP Server
新增工具 改代码、重新部署 Agent Server 增加工具,Client 重新发现
跨项目复用 复制工具文件 多个 Agent 连接同一个 Server
故障隔离 工具异常可能拖垮主进程 Server 可独立降级、重连、替换
团队边界 所有工具跟着 Agent 团队维护 工具 Server 可由业务团队独立维护

12.13.3 接入第九章专家工具池

更贴近第九章项目结构的接入方式,是把 MCP 工具和原有专家工具合并到同一个工具池里。这样可以逐步迁移,而不是一次性替换掉所有本地工具。

// services/chat/src/llm/graph/experts.ts(示例)
export async function buildFunctionalExpertTools(deps: {
  model: BaseChatModel;
  userId: string;
}) {
  const manager = new MCPManager();

  manager.register({
    id: 'requirement',
    config: {
      command: 'tsx',
      args: ['mcp-servers/requirement-analyzer/src/index.ts'],
    },
    prefix: 'req_',
    fallbackTools: [localAnalyzeCompletenessTool],
  });

  manager.register({
    id: 'websearch',
    config: {
      command: 'tsx',
      args: ['mcp-servers/web-search/src/index.ts'],
    },
    prefix: 'ws_',
    fallbackTools: [localSearchUnavailableTool],
  });

  await manager.connectAll();

  return [
    // 第九章已有本地工具
    readRequirementTool,
    checkExistingFeaturesTool,
    loadPerfBaselineTool,

    // 第十一章 RAG 工具
    createRagTool({ model: deps.model, userId: deps.userId }),

    // 第十二章 MCP 工具
    ...manager.getTools(),
  ];
}

这种“本地工具 + RAG 工具 + MCP 工具”的混合模式,更适合真实项目迁移:

12.13.4 三 Server 协同场景

现在功能专家可以同时使用三个来源的能力:

  1. requirement-analyzer:需求完整性、复杂度、冲突、用户故事
  2. web-search:竞品、最佳实践、技术方案搜索
  3. rag-server:内部知识库检索,也就是第十一章的 RAG 能力

这三类工具的定位并不一样:

Server / 工具来源 回答什么问题 典型工具
需求分析 Server “这条需求本身写得怎么样?” req_analyze_completenessreq_estimate_complexity
Web Search Server “外部世界有没有参考做法?” ws_search_competitorsws_search_best_practices
RAG Server “我们内部已有资料怎么说?” search_knowledge_base

场景 1:需求完整性不够 → 搜索竞品补充

用户:分析这个需求 “支持批量导入功能”

Agent:
  1. 调用 req_analyze_completeness
     → 得分 33%,缺少验收标准、非功能需求、边界条件

  2. 调用 ws_search_competitors("批量导入功能")
     → 获得 Jira / Linear / Airtable 的批量导入实现参考

  3. 调用 req_generate_user_stories
     → 生成用户故事和验收条件草案

  4. 综合输出:
     - 当前需求缺口
     - 竞品参考
     - 建议补充的验收标准
     - 可拆分的用户故事

场景 2:内部知识库无结果 → Web 搜索兜底

用户:我们的安全策略文档里有没有关于 API 限流的规范?

Agent:
  1. 调用 search_knowledge_base("API 限流 安全策略")
     → 内部知识库无相关结果

  2. 调用 ws_search_best_practices("API rate limiting")
     → 获得行业最佳实践:令牌桶、漏桶、按用户/租户限流、429 返回等

  3. 输出:
     - 内部资料未命中
     - 外部最佳实践参考
     - 建议补充内部安全规范章节

场景 3:复杂度估算 → 搜索类似方案参考工期

用户:评估这个需求的复杂度 “对接第三方支付系统,支持微信/支付宝”

Agent:
  1. 调用 req_estimate_complexity
     → XL(外部集成 + 安全合规 + 异常处理)

  2. 调用 ws_search_tech_stack("第三方支付集成")
     → 获得类似方案的常见工期和风险点

  3. 调用 search_knowledge_base("支付系统 对接 经验")
     → 查询内部历史项目有没有类似实现

  4. 输出:
     - 复杂度 T-shirt size
     - 主要技术风险
     - 内外部参考依据
     - 初步排期建议

场景 4:冲突检查 → 内部知识 + 现有需求比对

用户:新增一个“管理员可批量删除用户”的需求,看看有没有冲突。

Agent:
  1. 调用 search_knowledge_base("用户删除 权限 审计")
     → 找到内部安全规范:删除用户必须保留审计日志

  2. 调用 req_check_conflicts
     → 和现有“用户禁用 / 恢复”需求存在重叠

  3. 输出:
     - 与现有需求的重叠点
     - 与安全规范的冲突点
     - 建议改成“软删除 / 禁用”并保留审计链路

12.14 RAG-as-MCP-Server:把第十一章能力服务化

第十一章实现的 RAG 能力,也可以从 LangChain Tool 进一步升级为 MCP Server。

这样做的意义是:RAG 不再只是某个 Agent 内部的一个工具,而是一个可被多个客户端复用的标准服务。Cursor、Claude Desktop、内部运维 Agent、需求分析 Agent,都可以连接同一个 RAG MCP Server。

// mcp-servers/rag-server/src/index.ts
server.tool(
  'search_knowledge_base',
  '搜索内部知识库(基于 RAG),检索与问题相关的业务文档。适用于查询内部规范、产品文档、历史决策、技术方案。',
  {
    query: z.string().describe('搜索问题,应当是自然语言完整问句'),
    topK: z.number().optional().describe('返回结果数量,默认 5'),
  },
  async ({ query, topK = 5 }) => {
    const results = await ragPipeline.search(query, { topK });

    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(
            {
              query,
              results: results.map((r) => ({
                content: r.content,
                source: r.metadata.source,
                documentId: r.metadata.documentId,
                chunkId: r.metadata.chunkId,
                score: r.score,
              })),
            },
            null,
            2,
          ),
        },
      ],
    };
  },
);

进一步,也可以暴露一个生成式工具:

server.tool(
  'answer_with_knowledge_base',
  '基于内部知识库回答问题,并返回引用来源。适用于需要按内部资料给出最终答案的场景。',
  {
    question: z.string().describe('用户问题'),
    topK: z.number().optional().describe('检索结果数量,默认 5'),
  },
  async ({ question, topK = 5 }) => {
    const result = await ragAsk({
      question,
      topK,
      model,
    });

    return {
      content: [
        {
          type: 'text' as const,
          text: JSON.stringify(
            {
              answer: result.answer,
              citations: result.citations,
            },
            null,
            2,
          ),
        },
      ],
    };
  },
);

这两个工具的职责不同:

工具 返回什么 适合谁用
search_knowledge_base 检索到的 chunk + 元信息 需要自己综合判断的 Agent
answer_with_knowledge_base 已经生成好的答案 + 引用 只需要最终回答的客户端

工程上推荐先暴露 search_knowledge_base,让上层 Agent 负责综合。因为多 Agent 系统往往需要把 RAG 结果和其他工具结果一起推理,如果 RAG Server 直接生成最终答案,反而可能让职责边界变模糊。


12.15 与预算、权限和可观测性的协同

把 MCP 工具接入 Agent 后,不代表可以无条件调用。真实系统还要把第十章的 Token 预算、第十一章的 RAG 权限过滤,以及本章的 MCP trace 串起来。

generated-image-1779534584556.png

(1) 预算协同

MCP 工具本身不一定消耗 LLM Token,但它会影响 Agent 的上下文:

因此,MCPManager 可以在返回工具列表前做按需裁剪。这里不再重复 12.12.2 的 Token 成本公式,只保留工程落点:

function selectToolsForIntent(intent: string, allTools: DynamicStructuredTool[]) {
  if (intent === 'requirement_review') {
    return allTools.filter((t) =>
      t.name.startsWith('req_') ||
      t.name.startsWith('ws_') ||
      t.name === 'search_knowledge_base'
    );
  }

  if (intent === 'smalltalk') {
    return [];
  }

  return allTools;
}

这样可以避免每轮对话都把全部工具描述塞进 system prompt,降低第十章提到的上下文膨胀问题。

(2) 权限协同

MCP Server 暴露能力,不等于所有用户都能调用。权限应分两层:

  1. Host 层权限:当前用户是否允许使用某个 Server / Tool
  2. 业务数据权限:工具内部检索或操作数据时,是否按 userIdworkspaceIdteamId 做过滤
async function guardedCallTool(input: {
  userId: string;
  toolName: string;
  args: Record<string, unknown>;
}) {
  const allowed = await permissionService.canUseTool(input.userId, input.toolName);
  if (!allowed) {
    return {
      error: 'permission_denied',
      message: `当前用户无权调用工具 ${input.toolName}`,
    };
  }

  return manager.callTool(input.toolName, input.args);
}

尤其是 RAG-as-MCP-Server,必须继承第十一章强调过的权限过滤原则:不能先检索全库,再让模型“不要回答无权限内容”。权限过滤必须发生在检索阶段。

(3) 可观测性协同

可观测性字段以 12.12 的 MCPCallTrace 为主,不在集成章节重新定义一套结构。Agent 层只需要把同一轮请求里的多次 MCP 调用串到同一个 requestId / conversationId 下,再与第十章的 token usage、十一章的 RAG trace 合并。

有了这类 trace,后续才能回答三个生产问题:


12.16 端到端全景图

sequenceDiagram
  participant U as User
  participant S as Supervisor
  participant FE as Feature Expert
  participant MCPMgr as MCPManager
  participant RA as requirement-analyzer
  participant WS as web-search
  participant RAG as rag-server

  U->>S: "分析这个批量导入需求"
  S->>FE: 路由到功能专家

  FE->>MCPMgr: callTool(req_analyze_completeness)
  MCPMgr->>RA: tools/call
  RA->>MCPMgr: {completenessScore: 33, missingDimensions: [...]}
  MCPMgr->>FE: 返回完整性分析

  FE->>MCPMgr: callTool(search_knowledge_base)
  MCPMgr->>RAG: tools/call
  RAG->>MCPMgr: {results: [内部导入规范, 历史需求]}
  MCPMgr->>FE: 返回内部知识

  FE->>MCPMgr: callTool(ws_search_competitors)
  MCPMgr->>WS: tools/call
  WS->>MCPMgr: {results: [Jira, Linear]}
  MCPMgr->>FE: 返回竞品参考

  FE->>MCPMgr: callTool(req_estimate_complexity)
  MCPMgr->>RA: tools/call
  RA->>MCPMgr: {size: 'M', estimatedDays: '3-7天'}
  MCPMgr->>FE: 返回复杂度估算

  FE->>S: 综合分析报告
  S->>U: 最终输出

这张图把第九章、第十章、第十一章和本章串在一起:

到这里,Agent 不再只是“会推理的节点”,而是具备了可发现、可扩展、可复用工具生态的执行系统。

📋 本节配套用例

# LLM 集成测试(需要 OPENAI_API_KEY)
OPENAI_API_KEY=sk-... bun test test/chapter12-mcp-llm.spec.ts -t "12.13"
验证点 预期
Agent 能拿到 MCPManager 聚合后的工具列表
功能专家可调用 req_ 与 ws_ 前缀工具
RAG-as-MCP-Server 可返回内部知识结果
三 Server 工具可在同一轮推理中协同
Server 不可用时 fallbackTools 生效
最终输出包含完整性评分、内部知识、竞品参考、复杂度

image 9.png


12.17 全景回顾

走到这里,本章已经从“为什么需要 MCP”一路讲到“如何接入 LangGraph Agent”。回头看,MCP 解决的不是某一个工具怎么写,而是工具生态如何被发现、连接、调用、治理

12.17.1 完整时序图

graph TB
  subgraph "本章构建的系统"
    direction TB
    A["12.4-12.5<br/>单 Server + Client 基础"] --> B["12.6<br/>传输层抽象"]
    B --> C["12.7<br/>第二个 Server:搜索"]
    C --> D["12.8<br/>MCPManager 多 Server 编排"]
    D --> E["12.9-12.10<br/>韧性 + 安全"]
    E --> F["12.12<br/>可观测性 + Token 经济学"]
    F --> G["12.13<br/>Agent 集成"]
    G --> H["12.14<br/>RAG 服务化"]
    H --> I["12.15<br/>预算 / 权限 / 可观测性"]
    I --> J["12.16<br/>端到端全景图"]
  end

12.17.2 数据流维度

flowchart LR
    subgraph MCP_Server ["MCP Server 侧"]
        T1["Tools<br/>可执行动作"]
        R1["Resources<br/>只读资料"]
        P1["Prompts<br/>提示词模板"]
    end

    subgraph MCP_Client ["MCP Client / Manager 侧"]
        L1["connect<br/>建立传输"]
        L2["initialize<br/>能力协商"]
        L3["tools/list<br/>运行时发现"]
        L4["bridge<br/>转 LangChain Tool"]
    end

    subgraph Agent ["LangGraph Agent 侧"]
        A1["Supervisor<br/>选择专家"]
        A2["Expert<br/>选择工具"]
        A3["Tool Result<br/>进入上下文"]
        A4["Final Answer<br/>综合输出"]
    end

    MCP_Server --> MCP_Client --> Agent

从数据流角度看,本章有三条主线:

  1. 能力从 Server 暴露出来:Tools / Resources / Prompts
  2. 能力被 Client 发现和适配:connect / initialize / listTools / bridge
  3. 能力进入 Agent 推理闭环:工具描述进入上下文,工具结果回到模型,再生成最终答案

12.17.3 决策维度地图

什么时候用 MCP?什么时候直接写 Tool?可以用下面这张表做判断:

场景 推荐方案 原因
工具逻辑简单,只在一个 Agent 使用 直接 LangChain Tool 无需跨进程通信,开发成本最低
工具需要被多个 Agent / 项目复用 MCP Server 一次实现,多处连接
连接第三方服务(Jira / GitHub / Figma / Notion) MCP Server 认证、API、错误处理都适合封装在独立服务里
工具由另一个团队维护 MCP Server 边界清晰,团队可独立迭代
工具需要独立扩缩容 MCP Server + Streamable HTTP 可单独部署和伸缩,不拖累 Agent 主服务
需要本地桌面应用调用 MCP Server + stdio 零端口、零网络配置,适合本机进程
快速原型 / 一次性脚本 直接 Tool 少写协议层样板代码

经验法则

12.17.4 成本与灵活性阶梯

硬编码 Function → LangChain Tool → MCP (stdio) → MCP (HTTP)
────────────────────────────────────────────────────────────→
  开发成本低                                    灵活性高
  部署简单                                      可独立扩展
  无运行时开销                                   跨项目复用

这条阶梯不是说越右越好,而是要看场景:

12.17.5 可观测性回扣

可观测性字段和监控指标已经统一放在 12.12,本节不再重复表格。上线时只需要记住一个原则:第十章的 Token Usage、十一章的 RAG Trace、十二章的 MCP Trace 应该合并到同一条请求链路里。否则你只能看到“最终回答慢”,却不知道慢在模型、检索、MCP Server,还是外部 API。


12.18 FAQ

Q1:Server 连接不上 / 连接超时

常见原因

  1. Server 命令路径错误:检查 commandargs 是否正确
  2. 依赖未安装:Server 目录下执行 bun install
  3. TypeScript 编译错误:先 bun run build 确认无报错
  4. 环境变量缺失:某些 Server 需要特定环境变量
  5. Server 把日志写到了 stdout,污染了 JSON-RPC 协议输出

排查方式

# 手动启动 Server,看 stderr 输出
tsx mcp-servers/requirement-analyzer/src/index.ts 2>&1

如果使用 stdio,务必记住:stdout 只能写协议消息,日志写 stderr。这是最常见的连接失败原因之一。

Q2:tools/list 返回空数组

常见原因

  1. Server 代码中 server.tool() 注册有语法错误
  2. 工具注册发生在 server.connect() 之后
  3. 能力协商时 Server 没有声明 tools capability
  4. 工具注册被条件判断跳过,例如缺少环境变量时直接不注册

排查方式:用 MCP Inspector 直接连接 Server,看工具列表是否存在。若 Inspector 能看到工具,但 Agent 看不到,问题通常在 Client / Manager 的桥接层。

Q3:stdio vs Streamable HTTP 怎么选

场景 选择 原因
本地开发、单人使用 stdio 零配置、最简单
桌面应用 / IDE 插件 stdio 本机进程隔离,无需开放端口
多人 / 多 Agent 共享 Streamable HTTP 支持远程连接与多客户端访问
Serverless 部署 HTTP 无状态模式 每次请求独立,不维护 session
需要工具变更通知 HTTP 有状态模式或 stdio 需要保持会话
CI/CD 中运行测试 InMemoryTransport 无进程、无网络,稳定可重复

Q4:认证 Token 过期后工具调用全部失败

解法

  1. Client 捕获 401 响应
  2. 触发 token refresh 流程
  3. 用新 token 重新 initialize
  4. 重试失败的调用
try {
  await client.callTool(name, args);
} catch (err) {
  if (err.code === 401) {
    const newToken = await refreshToken();
    await client.reconnect({ token: newToken });
    return client.callTool(name, args);
  }
  throw err;
}

对于远程 MCP Server,认证失败不应该直接表现成“工具不可用”。更好的做法是返回明确错误,让 Host 引导用户重新授权。

Q5:工具调用返回内容太长,导致后续回答变慢

MCP 工具返回内容会进入 Agent 上下文。返回越长,后续 LLM 调用越贵。

解决方式

Q6:模型总是不调用某个 MCP 工具

常见原因不是协议问题,而是工具描述不清楚。

检查三点:

  1. description 是否说明了适用场景
  2. inputSchema 的字段描述是否具体
  3. 工具名是否可理解,例如 req_analyze_completenesstool_1 更容易被模型选择

好的工具描述应该同时写清:

Q7:MCP 与 OpenAI Function Calling / LangChain Tool 的区别和关系

三者的完整对比已经放在 12.1.4。这里用一句话回扣:Function Calling 解决“模型如何表达工具调用”,LangChain Tool 解决“应用内如何执行工具”,MCP 解决“外部工具如何被发现、连接和复用”。它们是分层关系,不是三选一。

Q8:什么时候不要用 MCP

决策表已经放在 12.17.3。简单说:短小纯函数、只服务当前 Agent、对延迟极敏感、强依赖主进程内存状态、一次性 Demo,都不必急着 MCP 化。工程上可以先用 LangChain Tool 起步,等工具需要跨项目复用、独立部署或连接外部系统时,再升级为 MCP。


12.19 术语速查表

术语 定义
MCP Model Context Protocol,AI 工具调用的标准协议
MCP Server 暴露 Tools/Resources/Prompts 的服务端
MCP Client 连接 Server 并调用工具的客户端
Host 运行 MCP Client 的应用程序(如 IDE、Agent)
Transport 传输层实现(stdio / Streamable HTTP / SSE)
Tool 可执行操作,由模型控制调用
Resource 只读数据源,由应用控制读取
Prompt 预定义 Prompt 模板,由用户控制选择
JSON-RPC 2.0 MCP 底层消息格式标准
Capability 能力声明,用于 Client/Server 协商
Session 一次 Client-Server 连接的完整生命周期
InMemoryTransport 测试用的内存传输,无需进程/网络
Inspector MCP 官方调试工具
Annotation 工具的元信息提示(只读/破坏性/开放世界)
Notification 单向消息(无需响应)
inputSchema 工具的 JSON Schema 参数定义
content[] MCP 工具返回格式(text/image/resource 数组)
isError 工具级错误标记(区别于协议级错误)
listChanged 工具列表变更通知
Mcp-Session-Id HTTP 传输的会话标识 header
Streamable HTTP MCP 当前推荐的 HTTP 传输方式
MCPManager 多 Server 统一管理器(本项目实现)
Bridge MCP Tool → LangChain Tool 的转换层
Namespace Prefix 多 Server 工具名前缀策略
Fallback Server 不可用时的降级工具
Circuit Breaker 断路器模式,连续失败后暂停调用
Human-in-the-Loop 高危操作前的用户确认机制
Tavily Web 搜索 API 服务(本项目搜索 Server 底层)
CRAG Corrective RAG,第十一章的 Web 搜索兜底模式
T-shirt Size 复杂度估算单位(S/M/L/XL)
Primitive 协议中的基础能力单元,本章指 Tool / Resource / Prompt
Request id 的请求消息,期待对端返回响应
Response 与请求 id 对应的响应消息
Resilience 韧性,系统在部分组件失败时继续工作的能力
Prompt Injection 提示词注入,外部内容试图影响模型行为的攻击方式
OAuth 2.1 远程 MCP Server 常用的授权流程
RAG-as-MCP-Server 把 RAG 检索能力封装成 MCP Server,供多个 Agent 复用
PRD Product Requirement Document,产品需求文档

12.20 精简小结 + 后续章节预告

本章要点

前面已经完成全景回顾,这里只保留几个要点:

  1. MCP 解决的是“连接与复用”问题:它不是替代 Function Calling,而是让工具可以独立开发、独立部署、跨 Agent 复用。
  2. 协议核心是 JSON-RPC 2.0 + 能力协商 + 三种原语:Tools、Resources、Prompts 的关键区别在于控制者不同。
  3. 桥接层决定能否进入现有 Agent 生态:JSON Schema → Zod、MCP content[] → string,是接入 LangChain / LangGraph 的关键适配点。
  4. 多 Server 编排要治理命名、降级和权限:前缀解决重名,fallback 解决不可用,Human-in-the-Loop 约束高风险操作。
  5. RAG-as-MCP-Server 让知识库能力服务化:第十一章的检索能力不再绑定某个 Agent,而可以被多个客户端共享。

后续章节预告

下一章进入 Skills:从“会写一段代码、会调用一个工具”,进一步走向“把一类任务沉淀成可复用资产”。我们会讨论如何把提示词、工具、脚本、模板、检查清单和领域知识组合成一个可迁移的 Skill,让 Agent 不只是临时完成任务,而是逐步积累稳定能力。

一句话锚点:

第十一章让模型读懂业务,第十二章让模型调用工具,第十三章会讨论如何把这些能力沉淀为可复用的 Skill 资产

写在最后 🧪

这里是言萧凡的 AI 编程实验室。我会在这里持续记录和分享 AI 工具、编程实践,以及那些值得沉淀下来的高效工作方法。不只聊概念,也尽量分享能直接上手、能够复用的经验。希望这间小小的实验室,能陪你一起探索、实践和成长。2026 年,一起进步。

有兴趣的话可以添加我的微信号【Cookieboty】一起交流,不仅是编程也可以是畅谈人生。