我想蹭一波 Trae 的流量,所以我写了关于 MCP 的这篇文章并起了这个标题...

大家好,我继续来进行一个落后于热点资讯的老生常谈。

用一句话来概括本文内容:本文介绍了关于 MCP 的前世今生,通过以某个 AI IDE 举例来叙述了 MCP 的背景、配置方案、如何实现一个简单的 MCP,同时,在结尾处简单叙述了 Agent 的背景故事与基础原理。

1、什么是 MCP?

MCP 是一种开放协议,它标准化了应用向 AI 应用提供上下文的方式。您可以将 MCP 视为 AI 应用的 USB-C 端口。正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。

简单的说,MCP 就像是一个万能接口,可以让 AI Client 调用外部本地的命令文件,或是服务器上的 API 服务,来拓展并增强 Client 的能力。

2、我该如何使用 MCP?

  1. 找到开源的 MCP 服务 / 自己编写一个
shell 复制代码
https://github.com/ferrislucas/iterm-mcp  
https://github.com/GLips/Figma-Context-MCP
  1. 配置网络 API 服务 / 本地脚本服务
shell 复制代码
pnpx figma-developer-mcp --figma-api-key=\<\your-figma-api-key\>\
  1. 在 Cursor 的设置中,找到 MCP 的配置并进行配置
json 复制代码
"iterm-mcp": {  
    "command": "npx",  
    "args": ["-y", "iterm-mcp"]  
},  
"figma-context-mcp": {  
    "url": "http://localhost:3333/sse"  
}

万事俱备!

我们现在可以使用 MCP 来调用本地应用程序 / 网络服务中间件了

我们只需要在对话框中显式 / 隐式的调用 MCP 服务即可

例如:

我们也可以通过 github.com/modelcontex... 查看更多的 MCP 服务

3、我该如何写一个属于自己的 MCP?

无论营销号们将 MCP 吹的多么的上天,MCP 终究只是类似于 Schema、HTTPS、Type-C 等的一种协议。

也就是说,无论我们用任何语言,用任何方式,只要遵循 MCP 的规范,我们的应用就可以被叫做:(符合)MCP(规范的应用)

此章节会以一个简单的获取实时天气的 MCP 服务为例来实现一个 MCP 服务。

前置准备:彩云天气API高德地图API

前置知识储备

· MCP 使用 JSON-RPC 2.0 作为其传输格式。传输层负责将 MCP 协议消息转换为 JSON-RPC 格式进行传输,并将收到的 JSON-RPC 消息转换回 MCP 协议消息

· MCP 使用 Zod 进行严格的类型校验

· MCP 有两种调用方式:stdio | sse,stdio 使用标准输入 / 输出进行通信;sse 使用 Server-Sent Events 进行通信

plaintext 复制代码
stdio(standard input/output)

stdio(standard input/output)模式指的是: 你的程序作为子进程运行,通过标准输入(stdin)读取请求,通过标准输出(stdout)返回响应

Cursor(主进程)  
│  
├── 启动你的 CLI 工具 → [node index.js]  
│  
├── 写入 stdin(请求)  
│  
└── 读取 stdout(响应)

1· 完全本地运行(无需开启端口、没有防火墙/SSL问题)  
2· 安全、简单(不暴露服务,不占端口)  
3· 支持自动退出(工具执行完毕可自动关闭进程)  
bash 复制代码
SSE(Server-Sent Events)

SSE(Server-Sent Events)模式指的是: 你的程序作为本地 HTTP 服务运行,提供一个支持事件流(event-stream)的 `/sse` 路径,供 Cursor 建立长连接并通过 `/messages` 接收请求。

Cursor(主进程)  
│  
├── 发起 GET 请求连接你的服务:`http://localhost:9614/sse`  
│  
├── 发起 POST 请求传递消息:`/messages`  
│  
└── 等待服务端通过 SSE 推送响应

1· 适合流式推送(如对话工具、代码生成、实时结果)  
2· 支持并发连接,可服务多个客户端  
3· 更像真实服务,可由 Nginx 代理、部署在常驻进程中

如果我们想实现一个例如 PDF 解析、天气查询、RSS 抓取等服务的即用即走的插件,我们可以使用 stdio。

如果我们有可以常驻,或是启动过于缓慢,并且需要缓存的服务,例如图像处理、向量检索等需求,我们可以使用 sse。

我们本次以 stdio 的方式来实现一个简单的 MCP 服务。


我们通过大量的 mcp 官方库的方法来搭建我们的 MCP 应用。

  1. 设置应用参数
typescript 复制代码
// index.ts

const argv = yargs(hideBin(process.argv))
  .option('amapToken', {
    type: 'string',
    describe: 'Amap token',
  })
  .option('caiyunToken', {
    type: 'string',
    describe: 'Caiyun token',
  })
  .help()
  .parseSync();
  1. 设置 MCP 应用的入口方法
typescript 复制代码
// server.ts

this.server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'get_real_time_weather',
        description: '获取实时天气信息',
        inputSchema: {
          type: 'object',
          properties: {
            location: {
              type: 'string',
              description: '地理位置, 格式为 "天安门广场"',
            },
            region: {
              type: 'string',
              description: '地区, 格式为 "北京市"',
            },
          },
          required: ['location', 'region'],
        },
      },
    ],
  };
});
  1. MCP 相应方法的具体实现函数
typescript 复制代码
// server.ts

const getLocation = () => {
  // 根据地区、关键词通过高德地图 API 获取经纬度
}

const getWeather = () => {
  // 根据经纬度通过彩云天气 API 获取实时天气
}

this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    if (request.params.name !== 'get_real_time_weather') {
      throw new Error(`Unsupported tool: ${request.params.name}`);
    }

    const args = request.params.arguments as {
      location: string;
      region: string;
    };

    if (!args.location) {
      throw new Error('Location is required');
    }

    if (!args.region) {
      throw new Error('Region is required');
    }
    const location = await this.getLocation(args.location, args.region);

    if (!location) {
      throw new Error('Location not found');
    }

    const weather = await this.getWeather(location);

    if (!weather || weather.error || weather.status !== 'ok') {
      throw new Error('Caiyun API error: ' + weather.error);
    }

    const { realtime } = weather.result;

    // 一些天气的常量枚举
    const weatherSkyConDesc = ...

    // 生成天气报告的模版字符串
    const weatherTemplate = ``;
    
    return {
      content: [
        {
          type: 'text',
          text: weatherTemplate,
        },
      ],
    };
  } catch (error) {
    throw new McpError(
      ErrorCode.InternalError,
      '[Error] failed to call tool: ' +
        (error instanceof Error ? error.message : String(error))
    );
  }
});
  1. 别的没了

你可能不敢相信,营销号口中的颠覆 AI Client 使用体验的全新 AI 能力竟然只需要这么点代码就可以了???

不 要 相 信 营 销 号

如你所见,如我上文所述。

MCP 只是一个协议,不管我的服务是什么样的(我可以去获取天气、我可以查询互联网内容、我可以通过 AI 优化我的内容的文采、我甚至可以通过 API 去操控我米家中的智能家居...),只要我遵循其结构,我就可以让 AI Client 来通过 MCP 协议来调用我的服务。

4、一些关于调试和发布的后话

调试

有很多种调试 MCP 服务的方式,大家可以自行搜索,此处以 @wong2/mcp-cli 为例

我们可以通过运行 npx trc 打包之后,通过以下代码来进行调试。

npx @wong2/mcp-cli node ./build/index.js --amapToken=xxx --caiyunToken=xxx

发布

我们可以在 AI Client 的设置中看到类似于

json 复制代码
"iterm-mcp": {  
    "command": "npx",  
    "args": ["-y", "iterm-mcp"]  
}

的写法。

MCP 并没有一个类似于 Chrome Extension 或 Vscode Plugin 这样的公共的平台,以以上内容为例,我可以直接将我的 MCP 应用发布到 NPM、或是 Python 类似的其他的任何平台。我们只需要保证可以通过命令行调用即可。

5、总结

没啥可总结的。


正文内容到此为止,以下是关于 Agent 的介绍,有兴趣的可以看一看。

6、Agent 基本原理

起源

Agent 一词源自拉丁语 "Agere",意为"行动"或"执行(to do)"。在大语言模型(LLM)的语境中,Agent 可以被看作是一种智能体,它具备自主理解指令、规划决策并执行复杂任务的能力。

对于我们日常的使用场景而言,Agent 类似于私人秘书或助理。当我们设定了一个最终目标,例如:"请帮我查询一下我在 Github 上 Star 的仓库,并将其整理为一个表格,同时编写一个脚本,在这些仓库更新时通知我",或"帮我创建一个使用 Next.js 和 Shadcn/UI 框架开发的个人网站,并部署在 Vercel 上",Agent 能够自主地将这些复杂任务自动拆解成具体的子步骤,并逐步执行,直至实现我们的最终目标。


如何明确地定义 Agent?

人们对 Agent 的理解存在着一定差异:

有些观点认为 Agent 是完全自主运行的系统,比如《生化危机》中的"红皇后",或者《钢铁侠》中的"贾维斯",它们能够独立执行决策,主动解决问题。

另一些观点则认为 Agent 是一种严格遵循预定义流程的系统,更类似于以预先编写好的代码和流程来执行规范性任务的 Workflow。

然而,在 Anthropic 的定义中,Workflow 与 Agent 是有严格区分的两个概念:

  • Workflow:是通过 LLM 以及其他(第三方)工具,根据明确定义的代码或流程,执行特定任务的系统。Workflow 的执行路径通常是预先设计并严格限定的。

  • Agent:则具备自主规划、信息检索、工具调用及记忆能力的智能体,能够在无需人为干预的情况下独立完成任务。

因此,我们可以更清晰地界定:

Agent 是一种经过增强的大语言模型,它不仅能处理语言,还能自主地进行规划、信息检索、工具使用和任务记忆,从而自动、高效地完成复杂的任务目标。


Agent 概述

Agent 通常由以下几个关键部分组成:

LLM

Agent 都是由 LLM 来驱动的,LLM 作为 Agent 的大脑,加上一些辅助的关键组件组成了 Agent。

Planning System

Memory System

Tool Use


Planning
  • Planning System 会将大的任务分解成一些较小的、易于管理的小任务,从而更加高效的处理复杂任务。
  • Planning System 会进行自我反省,在错误中吸取教训来改正未来的工作。

Planning 是 Agent 系统的核心能力之一,它允许 Agent 将复杂任务分解为可管理的子任务,并制定执行策略。有效的规划系统使 Agent 能够处理超出简单指令响应范围的复杂、多步骤任务。

Planning 的核心原理是目标驱动与任务分解。

  1. 一切的出发点是一个明确的目标,例如用户输入:"帮我制定一个去上海玩的规划"。

「制定一个去上海玩的规划」就是一个明确的目标

  1. 有了目标之后 Planning 会将一个大目标拆分成多个子任务。

查询车票 -> 预定酒店 -> 安排每日节点 -> 准备物品清单

这个过程通常用到

ReAct 模式(Reasoning + Acting)

ReAct 的核心理念是在每一步中先 Reasoning,再 Acting,然后再继续 Reason。

思维链推理(Chain of Thought | CoT)

CoT 允许 Agent 将复杂推理过程一步步的进行推理,而不是直接给出答案。

树状规划(Tree of Thoughts)

树状规划扩展了 CoT,允许 Agent 同时展开多个思路,然后再沿着最优的路径继续探索。

举例说明

我们通过「写一份周报」这个任务来对比一下这三种方法的流程对比

用户说帮我写一份本周的周报,内容包括

  1. 本周完成的工作
  2. 下周的计划
  3. 可能存在的问题和风险

方法一:ReAct | 边想边做,边做边想

Thought: 我要写一份周报,先问用户要"本周完成的内容" Action: 向用户提问「请列出你本周完成的工作」 Observation: 用户回复了三项工作内容

Thought: 然后收集"下周计划" Action: 向用户提问「请列出你下周的工作计划」 Observation: 用户回复了四项计划

Thought: 然后收集"风险与问题" Action: 向用户提问「请列出目前存在的问题和风险」 Observation: 用户回复了两项风险

Thought: 收集完毕,生成周报正文 Action: 编写并格式化完整周报

方法二:CoT | 一步步逻辑推理 -> 输出结果

  1. 思考:用户需要一份周报。周报通常包括:完成内容、计划内容、问题与风险。
  2. 思考:我需要先收集用户这周做了什么。
  3. 思考:再问用户下周准备做什么。
  4. 思考:最后获取用户遇到的困难或风险。
  5. 整理所有信息后,拼接成标准格式周报。

方法三:ToT | 展开多个写法/结构方案 -> 选最优路径生成周报

第一阶段:思考写法风格 Thought A1: 我可以用正式企业风格 Thought A2: 我可以用轻松口语风格 Thought A3: 我可以用结构化 Markdown 风格

第二阶段:对每种风格写出"完成内容"段落 Thought A1 → 本周完成了以下三项核心任务... Thought A2 → 这周我干了三件事,还挺忙的哈哈... Thought A3 → ## 本周完成事项\n - 任务 1\n - 任务 2...

评分阶段:根据"格式整洁度 + 语气适配度"选择 A3 为最优

第三阶段:基于 A3 模板,继续展开"下周计划"、"问题风险"

最终输出:完整的 Markdown 风格周报

什么时候使用这三种方式?

先说结论:LLM 会根据用户的提问来动态的选择推理的方式

例如:以 Claude 为例

当我们输入「帮我写一篇关于 LLM 安全的论文摘要」

Claude 可能会

好的,我们可以先梳理思路:

  1. 明确什么是 LLM 安全
  2. 分析其风险来源(幻觉、有害内容、Prompt Injection 等)
  3. 概述现有的防护机制
  4. 给出未来研究方向 ... (最后生成摘要)

这是典型的 Chain of Thought。

当我们输入「帮我生成一个合适的商业计划书」

Claude 会:

  1. 首先提问:你打算做什么类型的业务?
  2. 根据你的回答,继续提问你要融资多少、主要竞争者是谁......
  3. 最后再输出完整商业计划书

这是一种「模拟 ReAct」的行为(虽然不显式"Action + Observation",但过程上是 Reason → 用户反馈 → 再 Reason)。

Claude 会根据上下文自动生成最合适的推理路径,Claude 先进行任务识别,然后再根据以往的经验案例,最后在执行中随时修正推理路径。

例如: 假如我想用 Claude 完成一份周报,我输入的信息十分完整:

本周完成了 A/B/C,下周计划做 D/E/F,遇到的问题是 G/H。

这时 Claude 认为你已经给了所有的先决条件,所以通过纯粹的 CoT 来执行你的操作

假如我想用 Claude 完成一份项目总结,我直接说「给我写一份项目总结」

这时 Claude 判断你什么信息都没有给出,所以开始切换为 ReAct 风格的反问:「你的项目是什么样的?做了那些内容?」然后等你补充完成之后,再进行下一步的推理。

• ReAct 模式尤其适用于需要动态信息检索或实时反馈交互的任务; • CoT 更适合已明确信息且逻辑顺序较为简单的推理任务; • ToT 更适合多方案探索和优化、创造性的场景。


Memory

记忆系统分为两个部分:

短期记忆

短期记忆指的是 Agent 在执行单个任务或对话过程中临时存储的信息,类似人类大脑在短暂思考或进行简单推理时所用的记忆。

短期记忆通常通过 Sliding Window / 有限窗口 机制来实现,这个机制可以保证只保留最近的几个对话轮次中的内容,有时也用其他更灵活的方式来实现,例如 Stateful Cache / 状态缓存 等等

短期记忆用来确保 Agent 能够在单一对话中保持连贯性,也就是说,可以理解为短期记忆只用来储存较少的轮次的对话中的信息。例如智能客服中可以在短暂的时间内来保证对话连贯,或是在用户进行对话交互时记住用户刚刚提到的要求或者关键词。

长期记忆

长期记忆类似于知识库、经验库,可以让 Agent 根据以往的经验来做出更加合理的决策。

长期记忆通常通过 RAG(Retrieval-Augmented Generation)、向量数据库或知识图谱等技术实现,将非结构化数据(如文档、网页)或结构化数据(如用户偏好、历史交互记录)转化为可检索的知识形式,帮助 Agent 在未来的决策中参考以往的经验。

长期记忆可以让 Agent 根据以往的经验来做出更加合理的决策。

长期记忆和短期记忆在运行中的协作关系

以一个简单的对话为例:

用户对 Agent 提问"今天上海天气如何?"

(短期记忆记录):用户关注上海今天的天气 (读取长期记忆):用户的语言偏好、天气预报来源偏好等等

用户追问"适合穿什么衣服?"

(读取短期记忆):用户关心的是今天在上海适合穿什么衣服 (读取长期记忆):用户以往的穿衣风格

当用户持续使用一段时间后,Agent 的长期记忆的内容会越来越丰富,使得每一次交互都更加高效、智能。


Tool Use

Agent 的 Tool Use 是它实现"动手能力"的关键模块。它让语言模型不只是"在脑中想",还能"用手去做"。比如调用搜索引擎、数据库、函数、插件、浏览器、API 等等,把语言思维落地为真实操作。

它的工作流程通常是

  1. 用户发出一个复杂请求
  2. Agent 判断是否需要调用工具
  3. Agent 构造一个调用指令(工具名称 + 参数)
  4. 中间系统(如插件机制、Tool Router)接收指令并执行
  5. 把执行结果返回给 LLM
  6. LLM 根据结果继续思考 / 执行下一步

人们通常通过 专门训练 / 微调 让 LLM 看例子,然后来让 LLM 知道对于什么样的场景要调用什么样的工具

类似于

用户:请告诉我今天北京的天气

助手: Thought: 我不知道当前天气,需要使用 weather API 查询 Action: get_weather("Beijing") Observation: 北京今天多云,温度 21°C Thought: 得到了结果,可以回答用户了 Answer: 北京今天是多云,气温在 21°C 左右。

Agent 要怎样调用各种工具?

Agent 会通过外部的 Tool Router、Function Handler、Plugin Runtime 等模块调用工具。

Tool Router

Tool Router 是一个判断语言调用意图,并分发任务的中间组件。

它负责

  1. 看懂 LLM 的输出
  2. 判断 LLM 要使用哪个工具
  3. 把指令交给服务去执行

比如说,我们举一个「请帮我在项目中初始化 tailwind」的例子,LLM 可能发出了这样的一些调用命令:

json 复制代码
{
  "tool_call": {
    "name": "search",
    "arguments": {
      "query": "tailwind 初始化命令"
    }
  }
}

{
  "tool_call": {
    "name": "execute_terminal_command",
    "arguments": {
      "query": "npx tailwind init"
    }
  }
}

{
  "tool_call": {
    "name": "read_terminal",
  }
}

Tool Router 会判断 LLM 想要调用的是哪个工具,里面带有哪些参数,然后分发给相应的被绑定的 Search Handler

Function Handler

如果把 LLM 类比为司令,把 Tool Router 类比成参谋,Function Handler 就是下面的大头兵。

上文我们提到,Tool Router 会将相对应的任务分发给 Function Handler,Function Handler 就类似于一个函数

javascript 复制代码
function search({ query }) {
  return realGoogleSearchAPI(query);
}

这样的一个函数,Function Handler 执行完之后再把结果返回给 Router,然后 Router 再把结果交给 LLM。

Plugin Runtime

类似于我们会将服务运行在 Docker 中,Agent 的 Function Handler 也是被放在类似于 Docker 的 Plugin Runtime 中,Plugin Runtime 为 Agent 提供安全、受控的隔离执行环境(如容器或沙箱),管理插件生命周期,执行 Function Handler,并处理安全权限控制、资源限制、上下文传递等功能。

MCP

MCP(Model Context Protocol)是一种可以让 LLM 与外界系统进行通信的协议。

在实际使用中,人们经常用『MCP 插件』来泛指「遵循 MCP 协议的服务模块」,例如「支付宝 MCP」「高德地图 MCP」,本质上是指这些服务作为 Agent 插件,按照 MCP 协议暴露了可调用的方法。

以 Cursor 为例,我们可以在其 mcp.json 配置文件中添加自定义的 MCP 服务。Cursor 会将这些服务所暴露的方法统一注册到其内部的函数调用表中,作为可供 Agent 调用的工具。这些方法本质上就是 MCP 协议定义的接口,实现了对应的 Function Handler,并由 Tool Router 动态路由和触发调用。

注意!LLM 的调用并不完全遵循 MCP 协议,MCP 协议主要是为了解决不同厂商的 LLM 之间的协议不互通的问题,也就是为了让第三方服务可以通过一套方案来适配不同的 LLM。类似 ChatGPT 和 Claude,在其 LLM 内部都有属于自家厂商的私有协议。拿 search 这个动作来举例,LLM 可能通过私有协议调用 LLM 本身的网络搜索能力,也可能通过 MCP 来调用第三方插件来获取更加强大更具有拓展性的搜索。

使用哪一种方案取决于 Router 调度系统的决策。比如模型能力优先、插件优先、条件分发、混合对比模式。

MCP 的大致结构

  1. 请求(Requests) 请求消息用于从客户端向服务器发起操作,或者从服务器向客户端发起操作。

请求消息的结构如下:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": "string | number",
  "method": "string",
  "params": {
    "[key: string]": "unknown"
  }
}

jsonrpc:协议版本,固定为"2.0"。 id:请求的唯一标识符,可以是字符串或数字。 method:要调用的方法名称,是一个字符串。 params:方法的参数,是一个可选的键值对对象,其中键是字符串,值可以是任意类型。

  1. 响应(Responses) 响应消息是对请求的答复,从服务器发送到客户端,或者从客户端发送到服务器。

响应消息的结构如下:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": "string | number",
  "result": {
    "[key: string]": "unknown"
  },
  "error": {
    "code": "number",
    "message": "string",
    "data": "unknown"
  }
}

jsonrpc:协议版本,固定为"2.0"。 id:与请求中的 id 相对应,用于标识响应所对应的请求。 result:如果请求成功,result 字段包含操作的结果,是一个键值对对象。 error:如果请求失败,error 字段包含错误信息,其中: code:错误代码,是一个数字。 message:错误描述,是一个字符串。 data:可选的附加错误信息,可以是任意类型。

  1. 通知(Notifications) 通知消息是一种单向消息,不需要接收方回复。

通知消息的结构如下:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "string",
  "params": {
    "[key: string]": "unknown"
  }
}

jsonrpc:协议版本,固定为"2.0"。 method:要调用的方法名称,是一个字符串。 params:方法的参数,是一个可选的键值对对象,其中键是字符串,值可以是任意类型。

相关推荐
姜太小白1 小时前
【前端】CSS Grid布局介绍及示例
前端·css
风继续吹..4 小时前
后台管理系统权限管理:前端实现详解
前端·vue
yuanmenglxb20044 小时前
前端工程化包管理器:从npm基础到nvm多版本管理实战
前端·前端工程化
新手小新5 小时前
C++游戏开发(2)
开发语言·前端·c++
我不吃饼干5 小时前
【TypeScript】三分钟让 Trae、Cursor 用上你自己的 MCP
前端·typescript·trae
小杨同学yx6 小时前
前端三剑客之Css---day3
前端·css
Mintopia8 小时前
🧱 用三维点亮前端宇宙:构建你自己的 Three.js 组件库
前端·javascript·three.js
故事与九8 小时前
vue3使用vue-pdf-embed实现前端PDF在线预览
前端·vue.js·pdf
Mintopia9 小时前
🚀 顶点-面碰撞检测之诗:用牛顿法追寻命运的交点
前端·javascript·计算机图形学
wb1899 小时前
企业WEB应用服务器TOMCAT
运维·前端·笔记·tomcat·云计算