前端转型 Agent 开发 04 之 MCP 与 Skill(赋予 Agent 更广工作能力)

一、MCP 连接

1.1、核心概念

标准化暴露工具(Tools)、资源(Resources)和提示(Prompts)的开放协议,使不同系统能统一接入。

从上面的描述当中,我们其实可以知道 MCP 其实就相当于是一些关于一组业务类型相关的 tools 的集合体,也就是 MCP 底层其实调用的就是 agent tool 这个功能。

1.2、Agent 接入 MCP 的实现核心

三大能力(Tools / Resources / Prompts)

能力 是 什么 例子
Tools 模型可以调用的函数 read_file(path)send_email(to, subject)
Resources 模型可以读取的数据 file:///docs/api.mdpostgres://users
Prompts 预定义的提示模板 summarize_doc(uri)code_review(snippet)

关键区别

  • Tools 是"动作"(do something)------有副作用;
  • Resources 是"数据"(read something)------只读;
  • Prompts 是"模板"(render something)------生成 Prompt 文本。

Client / Server 架构

arduino 复制代码
┌────────────────┐         ┌────────────────┐
│  MCP Client    │ <-----> │  MCP Server    │
│ (在你的 Agent  │  JSON-  │ (独立进程,    │
│  应用中)       │  RPC    │  暴露 tools)   │
└────────────────┘         └────────────────┘
  • Client:你的 Agent 应用中的一个组件,负责连接 Server、发现能力、调用 tool;
  • Server:独立进程,可能由你或第三方维护(如官方 GitHub MCP Server);
  • 通信:用 JSON-RPC 2.0 协议(和 LLM API 用的格式相同)。

Transport(传输方式)

Transport 特点 适用
stdio 通过子进程的标准输入/输出通信 本地 server(同机部署)
HTTP(SSE) 通过 HTTP + Server-Sent Events 通信 远程 server(跨机部署)
  • stdio 适合本地工具(文件系统、本地数据库);
  • HTTP 适合远程服务(云上的 GitHub MCP server)。

1.3、完整简单例子

以 LangChain 提供的 Client + 一个模拟的文件系统 MCP Server 为例,演示 Agent 如何通过 MCP 协议读取远端文件。这里用 @langchain/mcp-adapters 把 MCP server 的能力桥接成标准的 LangChain tool:

javascript 复制代码
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { ChatAnthropic } from '@langchain/anthropic';
import { createAgent } from '@langchain/langgraph';

// ① 创建 MCP Client,连接一个 stdio 类型的 MCP Server
//    (假设已有一个暴露 read_file / list_files 能力的 server 进程)
const transport = new StdioClientTransport({
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
});

const mcpClient = new Client(
  { name: 'agent-mcp-client', version: '1.0.0' },
  { capabilities: {} },
);
await mcpClient.connect(transport);

// ② 发现 server 暴露的所有 tools(这里手动模拟转换过程)
const { tools: mcpTools } = await mcpClient.listTools();
console.log('MCP Server 暴露的工具:', mcpTools.map(t => t.name));
// ["read_file", "list_files", "write_file", ...]

// ③ 把 MCP tools 包装成 LangChain 兼容的 tool 数组
//    (实际开发中常用 @langchain/mcp-adapters 的 loadMcpTools 一行搞定)
import { loadMcpTools } from '@langchain/mcp-adapters';
const tools = await loadMcpTools(mcpClient);

// ④ 像普通 tool 一样喂给 Agent
const agent = createAgent({
  llm: new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }),
  tools, // MCP 来的工具和本地定义的工具可以混用
  prompt: '你可以通过 MCP 访问 /workspace 目录下的文件。',
});

const result = await agent.invoke({
  messages: [
    { role: 'user', content: '列出 /workspace 下所有文件,然后读 README.md 给我看' },
  ],
});

// Agent 会自主调用 list_files → read_file,最后总结内容
console.log(result.messages.at(-1).content);

// ⑤ 用完记得关闭连接
await mcpClient.close();

关键洞察 :对 Agent 来说,MCP 工具和本地定义的 tool() 没有本质区别 ------都是"带 description 和 schema 的可调用函数"。MCP 只是提供了一种跨进程/跨网络发现和调用工具的标准协议。所以你可以混搭:本地定义几个核心工具 + 从 N 个 MCP server 加载几十个外部工具,Agent 统一调度。这正是 MCP 协议的价值------让 Agent 的能力可以无限扩展,而代码不用改。

1.4、MCP Resources 实际用法

Resources 是 MCP 协议中只读数据的能力(区别于有副作用的 Tools)。Agent 可以读取远程资源(文件、数据库行、API 响应)作为上下文。

javascript 复制代码
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

const mcpClient = new Client(/* ... */);

// ① 列出 server 暴露的所有 resources
const { resources } = await mcpClient.listResources();
console.log(resources.map(r => ({
  uri: r.uri,        // 如 "file:///docs/api.md"
  name: r.name,      // 如 "API 文档"
  mimeType: r.mimeType,
})));
// [
//   { uri: 'file:///docs/api.md', name: 'API 文档', mimeType: 'text/markdown' },
//   { uri: 'postgres://users', name: '用户表', mimeType: 'application/json' },
// ]

// ② 读取指定 resource
const content = await mcpClient.readResource({
  uri: 'file:///docs/api.md',
});
console.log(content.contents[0].text);
// "# API 文档\n\n## 接口列表\n..."

Resources vs Tools:Resources 不会改变 server 状态(只读),Tools 会改变(写文件、发请求)。Agent 用 Tool 主动操作,用 Resource 拉取上下文。

1.5、MCP Prompts 实际用法

Prompts 是 MCP 协议中的预定义提示模板------server 维护一组可复用的 prompt,client 按名称和参数调用,避免每次都拼 prompt:

javascript 复制代码
// ① 列出所有可用 prompt 模板
const { prompts } = await mcpClient.listPrompts();
console.log(prompts.map(p => ({ name: p.name, description: p.description })));
// [
//   { name: 'code_review', description: '代码审查模板' },
//   { name: 'summarize_doc', description: '文档总结模板' },
// ]

// ② 用参数渲染 prompt
const rendered = await mcpClient.getPrompt('code_review', {
  language: 'typescript',
  snippet: `function add(a: number, b: number) { return a + b }`,
});

// rendered.messages 是可以直接发给模型的消息列表
for (const msg of rendered.messages) {
  console.log(`[${msg.role}] ${msg.content}`);
}
// [user] 请审查以下 TypeScript 代码:
//
// function add(a: number, b: number) { return a + b }
//
// 关注点:类型安全、边界情况、性能。
// [user] 输出格式:优点 → 问题 → 改进建议

// ③ 直接喂给 Agent
const result = await model.invoke(rendered.messages);

1.6、MCP 错误处理 + 重连

MCP 连接可能因网络、server 崩溃、超时而中断。生产代码必须做重连 + 错误分类

typescript 复制代码
async function connectWithRetry(url: string, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const transport = new StreamableHTTPClientTransport(new URL(url));
      const client = new Client(/* ... */);
      await client.connect(transport);
      console.log(`✅ MCP 连接成功(attempt ${attempt})`);
      return client;
    } catch (err: any) {
      // 分类错误:可重试 vs 不可重试
      const retryable =
        err.code === 'ECONNREFUSED' || // 连接被拒(server 没起)
        err.code === 'ETIMEDOUT' ||    // 超时
        err.code === 'ECONNRESET';     // 连接被重置

      if (!retryable) throw err; // 不可重试错误(认证失败、协议错误)

      // 指数退避:1s, 2s, 4s, 8s, 16s
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 16_000);
      console.warn(`MCP 连接失败(attempt ${attempt}),${delay}ms 后重试:`, err.message);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error(`MCP 连接失败(${maxAttempts} 次重试后仍不成功)`);
}

// 运行时断线监测 + 自动重连
client.on('disconnect', async () => {
  console.warn('MCP 连接断开,尝试重连...');
  await connectWithRetry(serverUrl);
});

MCP 错误码速查

错误码 含义 处理
-32700 JSON 解析错误 重试(可能是传输损坏)
-32600 无效请求 不要重试(请求本身错)
-32601 方法不存在 不要重试(client 调用了 server 不支持的方法)
-32602 参数无效 不要重试(参数错)
-32603 内部错误 重试(server bug)
-32000 ~ -32099 服务端自定义错误 看具体含义

1.7、MCP Server 认证

远程 MCP server 通常需要认证。协议支持在连接时传 auth 参数:

arduino 复制代码
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

// ① Bearer Token 认证
const client1 = new Client(
  { name: 'agent-mcp-client', version: '1.0.0' },
  {
    capabilities: {},
    auth: {
      type: 'bearer',
      token: process.env.MCP_API_TOKEN,
    },
  },
);

// ② OAuth 2.0 认证
const client2 = new Client(
  { name: 'agent-mcp-client', version: '1.0.0' },
  {
    capabilities: {},
    auth: {
      type: 'oauth2',
      clientId: process.env.MCP_CLIENT_ID,
      clientSecret: process.env.MCP_CLIENT_SECRET,
      tokenEndpoint: 'https://auth.example.com/oauth/token',
      scopes: ['read', 'write'],
    },
  },
);

// ③ 自定义 Header 认证(如企业内部 SSO)
const client3 = new Client(
  { name: 'agent-mcp-client', version: '1.0.0' },
  {
    capabilities: {},
    headers: {
      'X-API-Key': process.env.MCP_API_KEY,
      'X-Tenant': 'tenant-123',
    },
  },
);

Token 安全:永远不要把 token 硬编码或写进 git。生产环境用环境变量 / 密钥管理服务(AWS Secrets Manager / HashiCorp Vault)。一种常见的封装是从一个凭证抽象层(Credential Layer)读取 token,再注入到 MCP client,避免 token 散落在各处。


二、Skill 插件系统

2.1、核心概念

Skill 不是"执行体",而是"给 LLM 的指令文档"

也就是说 Skill 并不是 LLM 直接执行的代码逻辑,而是一段让 LLM 知道要做什么,怎么做的一些提示词描述。因此 Skill 不会作为一个带执行逻辑的工具直接发给 LLM,而是通过两条通道分阶段披露给模型。

基于上面的一个概念,我们能够把 Skill 拆解为三件事:磁盘加载 → 内存对象 → 分阶段注入 。一次 loadSkills() 调用把磁盘上的所有 Skill 加载到内存,后续每次新会话都从内存对象出发,按需把不同层级的内容注入到模型的上下文中。

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│  ① 加载:SKILL.md (磁盘)  →  Skill[] 内存对象                      │
│     loadSkills()  [engine/skills.ts:117]                         │
│     合并 builtin(BUILTIN_SKILLS 常量) + userData/skills/*          │
│     ├─ *.md            → 单文件 Skill                             │
│     └─ 目录(SKILL.md)  → 目录式 Skill (+references/ scripts/)     │
│     解析 frontmatter → splitFrontmatter + skillFromMeta            │
└───────────────────────────┬─────────────────────────────────────┘
                            │ Skill[]
              ┌─────────────┴──────────────┐
              ▼                            ▼
   ② 通道A: 注入系统提示          ② 通道B: 注册为工具
   (渐进披露 · 第1层)              (渐进披露 · 第2/3层入口)

2.2、Agent 对 Skill 系统的优化

业务类型的划分

根据不同的业务领域和使用频率,把 Skill 划分成不同类别,有助于模型快速定位该用哪个 Skill,也便于你管理。常见的划分维度:

划分维度 例子 为什么这样分
按领域 frontend / backend / devops / docs 不同领域知识不互相污染,模型按当前任务领域激活对应 Skill
按频率 core(高频,始终可用) / optional(低频,按需加载) 高频 Skill 常驻 system prompt,低频 Skill 走渐进披露第 2/3 层
按风险 readonly(只读,安全) / mutating(会改文件/发请求,需审批) 风险高的 Skill 可以挂审批 Hook,风险低的直接执行

常见的 Skill 业务划分做法:用 SKILL.md 的 frontmatter tags 字段做分类(如 tags: [frontend, react]),Skill 加载器按 tag 聚合,便于在 UI 上分组展示和按需筛选。

渐进式披露处理

内容 进入上下文时机
1 name + description 始终在 system prompt
2 [SKILL.md](https://SKILL.md) 的全文 body 模型调 Skill 工具时
3 references/ scripts 模型调 Skill_Resource

第 3 层「绝不预加载」由 <font style="background-color:rgba(255, 255, 255, 0);">SKILL_TOOL</font> 的 description 强约束(<font style="background-color:rgba(255, 255, 255, 0);">skill-tool.ts:36-38</font>):正文没提的资源,模型不会主动加载,清单只是"备选目录"。


参考资料:

Agent 开发系列文章

前端转型 Agent 开发 01 之 Agent API 调用(和 Agent 的基础对话):juejin.cn/post/767744...

前端转型 Agent 开发 02 之 Provider 与 Structured Output(规范化模型输入输出):juejin.cn/post/767745...

前端转型 Agent 开发 03 之 Agent Tools(给 Agent 装上手脚)介绍 Tool Calling:juejin.cn/post/768007...

相关推荐
刘发财3 小时前
前端2秒生成500页矢量PDF,rust真的强到没朋友
前端·javascript·rust
郑州光合科技余经理3 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
梦想平凡4 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
yume_sibai5 小时前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
冬奇Lab6 小时前
DeepSeek Harness 系列(07):能力 Seam——换一行配置,能力全换
人工智能·agent·deepseek
计算机魔术师6 小时前
特朗普上台打给黄仁勋:AI末日论是骗局,我们绝不让它发生
前端
troy1286 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
计算机魔术师6 小时前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
kyriewen7 小时前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员