停止炒作 MCP,面向技术人员的解释

旧文,经调整后发布

——————

近期,MCP (Model Context Protocol) 莫名成为AI领域的热门话题(实际去年就有了),各种文章将其描述为Function Calling 升级, 什么"统一USB接口" ,甚至是革命性的 "Agent框架" 。这些名词任何看来无法准确表达 MCP 什么 甚至产生重大误解严重可能带歪业务决策


本文不讲 "升级"、“USB接口” 虚的, 从纯技术 + 功能角度,基于对MCP TypeScript实现的源码分析,揭示MCP的真实面貌。  主要目的是, 希望你看完,深入了解它的源码层实现,作用,并且能以这些依据支持技术决策作出判断停止炒作理性周边沟通


(文本受众最好有一定的 Agent 相关工作基础)


我们首先从设计一个简单的代码生成 Agent 开始,逐步引入并分析MCP的核心设计,最后讨论MCP的适用场景和局限性。


从Agent设计说起:我们真正需要解决什么问题?


想象我们正在开发一个自动编写代码的Agent 。

先约束下场景:用户通过网页进行写代码 + 聊天编程(比如类似 cursor)

从架构角度,我们面临的核心问题包括:

1. 获取上下文:如何让Agent访问项目文件、文档和其他资源?

2. 执行操作:如何让Agent调用外部工具,例如打开文件夹、打开文件、搜索代码执行命令等?

3. 交互模式:如何设计Agent内部的交互方式

4. 通信协议:前后端如何高效、稳定地交换数据?

一个基础实现可能是这样的:(不到50 行伪代码)

// 简单的非MCP实现前端代码
classCodeAgent{
// 工具调用——获取文件内容的API
asyncreadFile(path:string):Promise<string>{
    returnawaitfetch(`/api/files?path=${path}`).then(=> r.text());
}

//工具调用—— 执行shell命令的API
asyncexecuteCommand(command:string):Promise<string>{
    returnawaitfetch('/api/shell',{
      method:'POST',
      body:JSON.stringify({ command })
    }).then(=> r.text());
}

    // 工具调用—— 打开文件夹的API
asyncOpenfiler(command:string):Promise<string>{
    returnawaitfetch('/api/openfolder',{
      method:'POST',
      body:JSON.stringify({ command })
    }).then(=> r.text());
}


// 调用LLM聊天
asyncllmchat(prompt:string, context:string[]):Promise<string>{
    returnawaitfetch('/api/llm/chat',{
      method:'POST',
      body:JSON.stringify({ prompt, context })
    }).then(=> r.json()).then(=> r.code);
}

// Agent核心逻辑
asyncAgent(task:string, filepath:string):Promise<string>{
    // 读取相关文件作为上下文
    const projectStructure =awaitthis.executeCommand('find . -type f | grep -v "node_modules"');
    const mainFile =awaitthis.readFile(filepath);
    
    // 调用大模型返回规划的伪代码
    for循环下面代码{
        constresult=awaitthis.generate(
          `请根据任务描述,调用工具
          任务${task}`,
          //代码内容
          [projectStructure, mainFile]
          //工具列表
           '
           工具:openfile
             作用打开指定文件
             参数file_path
             ————
             工具名:finish
             作用完成任务
             ...
           ';
       }
         );
    
    // 保存代码
    awaitfetch('/api/files',{
      method:'POST',
      body:JSON.stringify({ path: filepath, content: code })
    });
    
    return code;
}
}

如上实现了打开文件保存文件传统的IDE 功能, 实现了for  AI 的工具调用:执行shell命令、打开文件。 实现了任务规划逻辑我们使用方式简单 


1. 标准的 HTTP REST   通信

2. 后台接受参数自动执行对应接口比如打开文件夹,执行shell命令等,再返回结果。

3.  Agent 不断循环直到完成任务停止


随着时间的推移,你的后台接口的场景不断扩大。 举个简单例子这个 shell 命令另一个团队的用,让他们客户端操作你的 shell  REST 接口文档他们,但是他们拿到文档后该怎么做,是用于 reAct还是 Agent策略等,REST 接口层不管。


REST ——这就是MCP试图解决的问题空间。在这个层面 REST 代表了 MCP,它定义了前端和后端通信规范。 而 REST的具体的接口代表了 MCP的具体的服务接口。 


你的设计简单明了,但随着系统复杂度增加,我们会面临几个问题支持复杂功能比如后台能力封装成工具发现能力、服务端获取提示词不是硬编码前端每个工具提供不同版本前端协商比如 shell 1.0 shell2.0 这些功能逐渐变得庞大需要维护 作为软件工程师怎么抽象设计


聪明肯定工具调用抽象一个分类这些在代码上通常是专门执行函数 肯定也会提示词专门设计一个前端后台获取提示词 可能也会长远访问文件夹图片编码一个资源管理。然后你把这个代码封装成SDK ,把抽象的标准告诉了其他团队的人,让他们也按这个规范设计。 恭喜,大聪明,你想法 MCP 作者一样


但与市场宣传不同,MCP并非一种全能的Agent框架或者LLM 调用,而是专注于通信协议标准化的一部分解决方案。可以理解 MCP 仅仅替代了上面案例的 50 行代码——我们如何让后台和前端通信,和它对等的有比如 LSP(vscode语言服务协议),它不解决前端拿到了数据后,该怎么做,后台接受到请求后做什么。(比如上面的 Agent 接受后台数据,调用 LLM ,执行规划等这些能力


如果你的需求没有超过上面 50  demo 可以不用MCP如果遇到了数据通信调用的复杂性问题可以继续 MCP 源码如何解决


MCP的真实面貌:源码解析


深入MCP TypeScript SDK的源码,我们可以清晰地看到MCP的本质:一个基于JSON-RPC的通信协议,专注于标准化客户端与服务器之间的数据交换


MCP的核心抽象


MCP主要定义了三种核心抽象:

 1.  Tools(工具):执行操作的函数调用机制

   a.  /api/file

   b.   /api/shell

2.    Resources(资源):标准化数据访问的方式

   a.  /api/openfolder

3.   Prompts(提示词):统一提示模板的定义和使用

   a.  /api/llm/chat''/api/llm/chat(提示词模板写在后台的情况)


这种抽象分别对应我们上面 50 行 demo 的几个接口(类比)。这3 功能让我们从源码角度分析这些抽象:


Tools的实现


/**
   * Registers a tool `name` accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
   */
tool<Args extends ZodRawShape>(
    name:string,
    paramsSchema: Args,
    cb: ToolCallback<Args>,
):void;

/**
   * Registers a tool `name` (with a description) accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
   */
tool<Args extends ZodRawShape>(
    name:string,
    description:string,
    paramsSchema: Args,
    cb: ToolCallback<Args>,
):void;

tool(name:string,...rest:unknown[]):void{
    if(this._registeredTools[name]){
      thrownewError(`Tool ${name} is already registered`);
    }

    let description:string|undefined;
    if(typeof rest[0]==="string"){
      description = rest.shift()asstring;
    }

    let paramsSchema: ZodRawShape |undefined;
    if(rest.length >1){
      paramsSchema = rest.shift()as ZodRawShape;
    }

    const cb = rest[0]as ToolCallback<ZodRawShape |undefined>;
    this._registeredTools[name]={
      description,
      inputSchema:
        paramsSchema ===undefined?undefined: z.object(paramsSchema),
      callback: cb,
    };

    this.setToolRequestHandlers();
}


从源码中可以看到,Tools本质上是将函数调用标准化,包括:

  工具名称和描述(用于LLM理解,但也可做其他用途)

  强类型的参数定义(用于验证)

  标准化的返回格式 z.object(paramsSchema))


Resources的 demo


// 静态资源
server.resource(
"API文档",
"file:///docs/api.md",
async(uri)=>({
    contents:[{
      uri: uri.toString(),
      mimeType:"text/markdown",
      text:"# API文档\n## 端点1..."
    }]
})
);

// 动态资源模板
server.resource(
"用户数据",
newResourceTemplate("users://{userId}",{ list:undefined}),
async(uri,{ userId })=>({
    contents:[{
      uri: uri.toString(),
      mimeType:"application/json",
      text:JSON.stringify({ id: userId, name:"张三"})
    }]
})
);

Resources是对数据访问的抽象,统一了:

   URI寻址方式(包括模板支持)

  ●  内容类型声明

   标准化的数据返回格式


Prompts的 demo


server.prompt(
"code-review",
{
    code: z.string(),
    language: z.string(),
    focus: z.enum(["性能","安全","可读性"]).optional()
},
({ code, language, focus })=>({
    messages:[{
      role:"user",
      content:{
        type:"text",
        text:`请对这段${language}代码进行代码审查,重点关注${focus || "代码质量"}:\n\n${code}`
      }
    }]
})
);


Prompts标准化了提示词模板:

   参数化提示词定义

   类型安全

   标准化的消息格式


为何提供 3 功能抽象源码时候tools  完全可以接受提示词工具,并通过类型区分,可以返回资源理论完全可以这样 hack


 
    mcpServer.tool(
      "getprompts",
      {
        name: z.string(),
        value: z.number(),
      },
      async({ name, value })=>({
        content:[
          {
            type:"text",
      text:`请对这段${language}代码进行代码审查,重点关注${focus || "代码质量"}:\n\n${code}`         
      },
        ],
      }),
    );


更进一步,比如 50 行 demo 的接口也能获取提示词,本质上都是前后端通信标准的设计。但 MCP说一种“范式协议”, tools 和PromptsResources抽象分离一种有意“设计”实际笔者之前设计内部的 Agent 时候就是 Resources 作为  tools 一种调用MCP 是 for ai 最佳的范式吗?这取决于你看待的视角。就像你评价 REST  http 是否最佳的策略一样。但一旦很多人准守这种策略,开发者就能获得资源共享的好处,比如后台都用 HTTP 暴露接口,就能实现接口在不同客户端被调用,值需要引入 发送 http 的 sdk 即可。mcp 则试图for AI 特征而设计这种通信协议。 



MCP的Transport层


MCP的另一个核心部分是Transport抽象,它定义了通信的过程

// Transport接口定义
exportinterfaceTransport{
start():Promise<void>;
send(message: JSONRPCMessage):Promise<void>;
close():Promise<void>;
  onclose?:()=>void;
  onerror?:(error: Error)=>void;
  onmessage?:(message: JSONRPCMessage)=>void;
  sessionId?:string;
}


刚刚我们泛泛描述“前后端通信”,但 MCP 不仅仅支持传统的后端,还支持客户端进程通信,说直接点,就是在一台设备中软件和软件之间的调用。Transport 是一个多态的设计接口,具体的实现分别在两个不同的类中,用户调用的时候动态调用最终底层。比如 StdioServerTransport 就是定义进程的实现。(node 的sdk 用了cross-spawn用于跨平台)


export classStdioServerTransportimplementsTransport{...}
exportclassSSEServerTransportimplementsTransport{...}
exportclassInMemoryTransportimplementsTransport{...}


如果上面的 3 大功能可以理解成我们的 50 行代码抽象,那如何理解 Transport ?答案是数据交互过程,如果直接用 http ,本质浏览器的 ajax 帮你做了这个事情,MCP 的数据通信在 http 和 RPC 的基础上再封装了一层共用抽象。 具体的实现它对底层是开发者透明,但你的后台是传统的服务器,客户端按接口从服务端获取, 底层是 基于HTTP的Server-Sent Events + json rpc 通信。


如果对应进程,比如cluade desktop  调用有道词典,那有道词典作为服务的提供数据,客户端就走进程通信(不同的语言的 sdk 实现不一样,但基本是跨平台的,你不用关注兼容问题)。


现在你理解了Transport实现本质上就是个传输管道, 你完全可以用HTTPS 替代和后台通信,或者在客户端开发用来做进程通信都可以。 只是MCP封装了更多的标准。比如协商标准,数据获取标准 。你有没有发现,这和我们平时理解的"AI"一点关系都没有?对,它就是个纯通信组件。它是通信协议,不是 AI 具体实现。


我举个例子,假设我们要实现一个在线记事本的前后端通信,MCP完全可以胜任,不需要和 AI 挂钩。


能力协商


MCP的另一个可选高级功能是所谓的"能力协商", 其实就是版本升级等握手过程。

代码会是这样的:

// 初始化请求中的能力声明
const result =await client.request({
  method:"initialize",
  params:{
    protocolVersion:"2024-11-05",
    capabilities:{
      sampling:{},// 客户端支持被反向调用llm
      roots:{}     // 客户端支持文件系统访问
    },
    clientInfo:{ name:"MyClient", version:"1.0"}
}
});


服务器会回复它自己支持什么:


return {
  protocolVersion:"2024-11-05",
  capabilities:{
    tools:{},      // 服务器支持工具
    resources:{},// 服务器支持资源
    prompts:{}     // 服务器支持提示词
},
  serverInfo:{ name:"MyServer", version:"1.0"}
};


这个协商机制有点像两个人见面互相自我介绍:"嗨,我是客户端,我支持这些功能。""你好,我是服务器,我支持这些功能。"然后双方根据对方支持的能力决定如何交互。


比如某个工具升级了,废弃了,可以通过这种协商告诉客户端这个协商基本就是客户端服务端自己约定业务逻辑MC只是接口建议这个能力必须每个服务每个客户端 1 单独开发,无法多个服务通用


再回到前后端通信


我们回到最初的例子,如果把我们简单的50行代码重构成MCP风格,会是什么样呢?


const client =newClient({ name:"MyClient", version:"1.0.0"});
await client.connect(transport);

// 读取文件
const resource =await client.readResource({ uri:"file:///path/to/file.js"});

// 执行命令
const result =await client.callTool({
  name:"execute-command",
  arguments:{ command:"ls -la"}
});

// 获取提示词
const prompt =await client.getPrompt({
  name:"generate-code",
  arguments:{ task:"排序算法", language:"Python"}
});
 


对,就是这么多。也就我们把 http 协议替代成 MCP 协议,最终即完成接入。没有 mcp 之前,我们会通过 http 进行前后端通信,拿到自己的数据,然后再给大模型使用。替换成 mcp 后,其他逻辑仍然不需要变化。但你需要引入MCP整个SDK,理解它的概念模型,基于它做会话建立、调用、处理它的错误格式,还要适应它的类型系统,然后不断 debug 调基础的东西。对于简单应用,这可能是杀鸡用牛刀。具体不恰当的例子,有点做一个简单的列表页,从前端用 GraphQL 去调用 后台操作数据库,而不是用 HTTP,最终引入太多复杂性。


基于 mcp 协议拿到数据以后,客户端要做什么事情,和 mcp 无关,mcp不定义这个。那我们为什么要接入它?


这个问题很好。基于 http 的背景下,每个后台返回的接口格式不一样,客户端和服务端需要“联调”,并专门写数据解析逻辑。而 MCP 定义了这部分接口组成的规范,为什么我们类比 REST,而不是接口本身,因为 REST 定义了post 就是修改数据,delete 方法就是删除数据等行为,大家按这个约定进行。同样 MCP 定义了 tools 是获取工具,prompt 获取提示词。这样大家约定 tools 拿到工具,按统一的方法解析就行。有没有可能某个服务基于 MCP 暴露接口但不遵守规范?完全可以,在那种尝试定义私域生态协议,比如小程序之基于 HTML5。只要某个平台拥有足够的话语权,那生态的协议自然归它掌控。



MCP适合哪些场景?


从源码和设计来看,MCP适合以下场景:


1. 你在构建一个需要扩展的平台,希望第三方开发者能为你的平台提供工具,或者你是第三方开发者想为平台提供 AI 工具。比如 Claude Desktop想让其他公司为它开发插件; 比如有道词典 AI 能力(我真的不知道他们有没有提供)想同时 被 cursor 和 Claude Desktop使用。这就是 MCP 最大的价值。(最近微信也开始支持 MCP 以进入平台生态)


2. 你需要在多种环境中复用同一套工具。 比如同一组工具需要在网页版、桌面版和移动版中使用。这种情况提示词可能需要版本管理后台统一分发,并且工具能力不断变化MCP能力协商可以提供抽象省去自己设计时间。(但要慎重考虑是否有独特的业务属性)


3. 你需要标准化大量工具和资源的访问方式,希望有统一的错误处理、类型检查和参数验证。如果内部 AI 工具本身没有自己标准可以考虑  MCP sdk  作为标准这个基础构建是不错的选择


但MCP绝对不适合的场景包括:


1. 小型内部项目。如果你只是在做一个团队内部使用的Agent,没有第三方扩展需求,用MCP可能会增加不必要的复杂度


 2. 性能敏感的应用。MCP的抽象层和JSON-RPC格式会约束 AI 的优化。

直接就是 MCP 定义这套标准目前性能最佳是Claude AI自己模型主要体现工具调用风格协议。通常这种工具是不会暴露给第三方用的,比如 cursor 的核心能力。


3. 简单的一次性集成。如果你只需要调用几个工具API,直接用HTTP请求可能更简单明了。


内部 Agent 只有需要吸引跨部门开发工具进入才有意义,如果评估是内部自己人,一年下来就几个确定的工具,那 MCP 无意义,会增加调试和开发负担。市面上很多客户端内部Agent实现,,比如 cursor 、github copilot 等实现策略是,自己的核心 Agent 调用(文件打开、搜索等工具)特别优化实现, 而非核心的外部工具(搜索数据库等),暴露标准 MCP 接口。


市场上几种常见的MCP误解


很多文章把MCP描述为"AI的USB接口"或"Function Calling的升级版",这些都是对MCP的误解。


1. "AI的USB接口": MCP  客户端服务端数据通信接口, 而客户端拿到数据怎么调用 AI 大模型这是自由的,MCP不关心。 从 MCP 设计的参数, 风格接近 Function Calling如果 LLM 模型函数调用支持不佳完全可以转化组合成优化的提示词这个层面 Function Calling 像是 USB 虽然目前不算统一(qwen 、deepseek 系列对 Function Calling 兼容还不太好)


2. "Function Calling的升级版":前面已经提到 Function Calling 是大模型的一种能力,让模型能够以结构化方式输出函数调用。而MCP是一种通信协议,两者不在一个层次上。虽然MCP的Tools 概念接口与Function Calling类似,但它们解决的是不同问题。MCP 数据客户端最终 Function Calling 调用大模型两个不同层面的描述。(有好几篇流量挺大所谓的科普文把 MCP 当成是 Function Calling替代...囧... )


 3.  "万能的Agent框架":有些文章把MCP描述为一种全能的Agent框架,这就更扯了(很怀疑这些文章都是 AI 吐出来,再加工的,都没有看MCP 官方文档...)。MCP只定义了通信标准,不包含Agent的核心逻辑,如决策、规划、记忆等。




MCP只是一个通信协议,不是AI的救世主。它可能对某些场景有价值(平台生态,工具扩展),但对其他场景可能是不必要的复杂性。


开发内部 Agent 过程,一般通信标准是工作量很少的部分。我们大部分工作是在提示工程,Agent 规划、记忆等。


希望这篇文章能帮你看清MCP的真面目,做出明智的技术决策,不要花在浪费时间的事情上。 如果你正在构建平台或生态系统,MCP可能是个好选择;如果你只是开发一个特定用途的Agent,简单的HTTP API可能已经足够了。


——————

本文基于对MCP TypeScript SDK源码的深入分析,旨在从技术角度揭示MCP的真实面貌,而非跟随市场炒作。如有不同见解,欢迎讨论。




 


更新时间: