前端开发者的 AI 笔记(二):Agent、MCP 与上下文管理 🤖

279 阅读8分钟

本文聚焦三个问题:Agent 是什么,MCP 扮演什么角色,以及在 AI IDE 中如何通过 Rules / Commands / Skills 做好上下文管理。

一、从 LLM 到 Agent:缺了什么,补了什么

从最早的 GPT-3.5、Claude、DeepSeek-R1,这些大语言模型(LLM)本身就具备思考与规划的能力。

但那个阶段,它们有几个明显的局限:

问题本质原因
几天前的对话记不住缺少 Memory
给它一个链接只能给建议,不能实际处理缺少 Tools
无法获取最新信息知识有训练截止日期
不能基于私有文档稳定回答缺少 RAG / 私有知识上下文

后来,Copilot、Cursor 等 AI IDE 出现,带来了 Agent 模式, 本质上就是在 LLM 外再补几层能力:

  • Tools:读写文件、执行命令、调用浏览器、访问远端服务
  • Memory:保留任务状态与历史上下文
  • RAG:接入项目文档、私有知识库
  • Loop:模型可以反复“思考 -> 调工具 -> 拿结果 -> 再思考”

可以直接把 Agent 理解为:

Agent = LLM + Tools + Memory + RAG + 可迭代执行循环


二、用一段代码理解 Agent 的最小闭环

拿 LangChain.js 举例,创建一个 Agent 的核心结构非常清晰:

import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { HumanMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
import { z } from "zod";
import fs from "node:fs/promises";

// 1. 初始化大语言模型
const model = new ChatOpenAI({
  model: "gpt-5",
  temperature: 0,
});

// 2. 定义工具:读取文件内容
const readFileTool = tool(
  async ({ filePath }) => {
    const content = await fs.readFile(filePath, "utf-8");
    return `文件内容:\n${content}`;
  },
  {
    name: "read_file",
    description: "读取文件内容,输入文件路径(相对或绝对路径均可)",
    schema: z.object({
      filePath: z.string().describe("要读取的文件路径"),
    }),
  }
);

// 3. 将工具绑定到模型,Agent 可自主决定是否调用
const modelWithTools = model.bindTools([readFileTool]);

// 4. 发起对话,模型自动决策是否调用工具
const messages = [
  new SystemMessage("你是一个代码助手,可以使用工具读取并解释文件内容。"),
  new HumanMessage("请读取 ./index.ts 并解释这段代码"),
];

let response = await modelWithTools.invoke(messages);
messages.push(response);

// 5. 循环处理工具调用,直到模型不再需要工具
while (response.tool_calls?.length > 0) {
  for (const toolCall of response.tool_calls) {
    const result = await readFileTool.invoke(toolCall.args);
    messages.push(
      new ToolMessage({ content: result, tool_call_id: toolCall.id })
    );
  }
  // 将工具结果传回模型,继续推理
  response = await modelWithTools.invoke(messages);
}

console.log(response.content); // 最终回复

image.png

关键理解model 是大脑,tools 是手脚。bindTools 并不是强制模型调用工具,而是告知模型"有这些能力可用",由模型自主决策是否调用。

工具调用的完整闭环是:模型返回 tool_calls → 宿主代码执行对应工具 → 将结果以 ToolMessage 写回消息历史 → 模型再次推理,直到不再需要工具为止。 这个循环,就是 Agent "自主迭代"能力的核心。


三、MCP:让工具调用从“私有接线”变成“标准协议”

在 MCP 出现之前,模型调用外部能力主要依赖 Function Calling
问题在于:不同模型厂商的接入方式不同,工具很难复用,IDE 与 Agent 之间也缺少统一标准。

MCP(Model Context Protocol)  解决的就是这个问题。

可以把它理解为:

MCP 是一套让 Agent 发现、调用外部工具的标准协议。

它和 Function Calling 做的是同一类事,但更标准化、更可复用、也更适合跨工具链协作。

MCP 的两种常见模式

MCP 支持两种通信方式,适用场景不同:

模式通信方式典型场景
Stdio(标准输入输出)本地进程间通信本地文件操作、命令执行、浏览器桥接
HTTP(远程服务)网络请求云端 API、跨机器调用,如 Context7

对应配置示例:

{
  "mcpServers": {
    // Stdio 模式:启动一个本地 Node.js 子进程
    "browsermcp": {
      "command": "npx",
      "args": ["@browsermcp/mcp@latest"]
    },
    // HTTP 模式:直连远端服务
    "context7": {
      "url": "https://mcp.context7.com/mcp",
      "headers": {
        "CONTEXT7_API_KEY": "xxxxxxx"
      }
    }
  }
}

Cursor 启动时做了什么

当 Cursor 启动时,会读取本地 MCP 配置文件,并立即将每个 Stdio 类型的 MCP 作为一个子进程启动。以 browsermcp 为例,实际上是在后台执行了:

npx @browsermcp/mcp@latest

npx node /Users/streaker303/.nvm/versions/node/v22.17.0/lib/node_modules/mcp-chrome-bridge/dist/mcp/mcp-server-stdio.js

这个 Node.js 进程持续运行,通过 stdin/stdout 与 Cursor 保持通信。Cursor(作为 MCP Client)发送工具调用请求,MCP Server 处理后将结果写回 stdout,再返回给大模型。

下图以 mcp-chrome-bridge 这类"浏览器桥接型" MCP 为例。 它的特殊之处在于:不创建新浏览器实例,而是复用当前正在使用的浏览器及其登录状态,通过 Chrome Extension 作为中间桥接层传递消息。

┌──────────┐   stdio   ┌──────────────────────┐   HTTP MCP   ┌────────────────────────────┐   Native Messaging   ┌────────────────┐   Chrome API   ┌─────────────┐
│          │ ────────► │                      │ ───────────► │                            │ ───────────────────► │                │ ─────────────► │             │
│  Agent   │           │ mcp-server-stdio.js │              │ Native Host + HTTP Server  │                      │ Chrome Ext     │                │ Chrome Tab  │
│  / IDE   │ ◄──────── │  (stdio adapter)     │ ◄─────────── │     (127.0.0.1:12306)      │ ◄─────────────────── │  (插件后台)     │ ◄───────────── │  / 页面      │
└──────────┘           └──────────────────────┘              └────────────────────────────┘                      └────────────────┘                └─────────────┘

sequenceDiagram
    participant U as User
    participant A as Agent IDE
    participant S as mcp-server-stdio.js
    participant H as 127.0.0.1:12306 MCP Server
    participant N as Native Host
    participant E as Chrome Extension
    participant B as Browser Tab/Page

    U->>A: 使用 MCP 打开链接 / 获取页面内容
    A->>S: tools/call(stdio)
    S->>H: POST /mcp(HTTP)
    H->>N: 调用工具
    N->>E: Native Messaging 请求
    E->>B: 调用 Chrome API / 注入脚本
    B-->>E: 返回执行结果
    E-->>N: Native Messaging 响应
    N-->>H: 工具结果
    H-->>S: MCP 响应(HTTP)
    S-->>A: tools/call 响应(stdio)


四、为什么 Agent 工程化一定会遇到“上下文污染”

Agent 虽强,但上下文窗口始终有限。

如果把所有规范、组件文档、脚本说明、团队约定全部直接塞进 Prompt,很快就会出现三个问题:

  • 信噪比下降:真正关键的信息被淹没
  • 推理效率下降:Token 增加,响应变慢
  • 幻觉风险上升:上下文太杂,模型更容易误判

因此,AI IDE 不会把所有信息都做成“永远注入”。
更合理的做法是分层管理上下文,这也是 Rules / Commands / Skills 存在的原因。


五、Rules、Commands、Skills:三种上下文机制怎么分工

机制触发方式适合放什么上下文成本常见误用
Rules宿主强制注入项目红线、技术栈、绝对约束始终消耗把长篇教程塞进去
Commands用户显式触发高频、稳定、可命名的工作流入口调用时消耗命令越拆越多
SkillsAgent 按需匹配可复用的工程策略、SOP、领域能力命中时渐进加载做成“伪全局规则”

1. Rules

Rules 更像系统级 Prompt,适合放:

  • 项目技术栈
  • 代码红线
  • 输出语言
  • 不能违反的工程约束

不适合放:

  • 长篇开发教程
  • 低频场景说明
  • 大段外部文档

2. Commands

Commands 是用户主动触发的入口,适合放:

  • 重复性强的工作流
  • 希望开发者显式启动的流程
  • 需要固定输入输出边界的任务

例如:

  • /code-review
  • /refactor
  • /opsx-apply

3. Skills

Skills 适合承载复杂策略,而不是做成“另一个命令系统”。

它的价值有两个:

  • 可复用:同一个能力可以被不同任务复用
  • 渐进加载:先匹配,再按需展开,不会一上来污染上下文

你可以把 Skill 理解成:

面向 Agent 的工程能力模块。


六、以 OpenSpec 为例:Command 和 Skill 应该怎么拆

这是最容易设计失衡的部分。

如果 Command 设计得太多

很容易演变成:

  • quick-feat
  • feature-design
  • doc-driven-feat
  • ui-change-design

问题是:

  • 难记
  • 边界会重叠
  • 命令列表持续膨胀
  • 维护成本越来越高

如果 Command 设计得太少,但又太厚

又会变成一个巨型流程脚本,在里面塞满判断:

  • 有没有接口文档
  • 有没有需求文档
  • 是小改动还是大改动
  • 是否涉及 UI 风格读取
  • 是否要先做 explore
  • 是否要调用 MCP 查版本

结果是 Command 自己承担了过多复杂度,也变得脆弱。

更合理的拆法

原则很简单:

Command 管入口,Skill 管判断,MCP 查事实。

以 OpenSpec 为例,适合做成 Command 的,是这些稳定、可命名的用户意图:

  • new
  • continue
  • apply
  • verify
  • archive

它们解决的是: “我要做什么”

而适合沉淀到 Skill 的,是这些依赖上下文判断的逻辑:

  • 是否先读取 wiki / 钉钉 / 需求文档
  • 是轻量变更,还是需要完整 proposal / spec / design / tasks
  • 是否涉及 UI 调整,需要先读取现有页面风格
  • 是否遇到版本/API 分歧,需要调用 MCP
  • 当前更适合 explore,还是直接 implement

它们解决的是: “具体该怎么做”


七、一个更稳的实践模式

可以把这一套总结成下面的分层:

  • OpenSpec Command:提供主流程入口
  • 文档类 Skills:负责读取 wiki、钉钉、接口文档、需求文档
  • 工程类 Skills:负责设计深度判断、UI 风格分析、任务拆解
  • MCP:在版本、API、外部事实不确定时提供权威信息

这套组合有几个直接好处:

  • 开发者只需要记住少量命令
  • 团队规范集中沉淀在 Skills
  • 复杂判断不会散落在多个命令中重复维护
  • 框架升级或流程调整时,优先改 Skill,而不是重写一堆 Command
  • 模型保留必要的自主判断空间,不会被过硬的流程绑死

核心不是“把流程写得越细越好”,而是:

固定关键节点,不固定模型的推理路径。


八、结论

如果只保留一句实践结论,可以是下面这句:

Rules 放红线,Commands 放入口,Skills 放策略,MCP 负责查外部事实。

对应到 OpenSpec 这类场景,就是:

  • 少量 Command 保持稳定、好记
  • 厚 Skill 吸收复杂判断和工程策略
  • MCP 只在需要权威依据时介入

这比“为每个场景发明一个命令”更稳,也比“把所有复杂度都塞进一个命令”更可维护。

太固化的流程,可能会限制大模型的输出。(比如 Anthropic 成员采访中说的,todolist 设计反而会限制模型,要相信大模型的能力)

真正限制模型效果的,往往不是“有没有计划”,而是:计划写得过死,边界又写得不清。