MCP(下)——跟着官方实现一个MCP

图为 2025.04.27 拍摄于杭州美丽洲公园


前面两节我们讲清楚了 function/tool call 是什么以及如何手动调用工具 ,接下来我们再来理解 MCP 其实就很轻松了。

本文会通过一个实际的例子,带大家具象的看清楚 MCP 到底是个啥。

(大家下载代码后去配置一下 apikey ,就能把文中的例子跑起来)。

行文思路:

  1. 概念和流程讲清楚
  2. 把官方的例子跑起来
  3. 讲清楚我的理解

MCP 概念

我们先认清一个事实:

  1. 大模型训练完成后,其权重和参数就已经固定了
  2. 无法自主学习或进化
  3. 它对事物的认知局限于历史训练数据,之外的东西它是不知道的,比如天气预报、新闻;

模型的能力在于:

  1. 拥有既定的认知框架
  2. 具备"理解"和"推理"能力

第二点很重要,大模型能理解你的意图!还能根据你给的输入做出推理!最终预测出你要的答案。

我对于大模型的具象理解,感觉他就是一个受过高等教育的"人"。

如果仅局限于他自己的固定认知,无法和外界交互,那么在实际场景可能发挥不了多大的作用。

MCP 就是提供了一套标准,让大模型可以和外界发生交互,以发挥他最大的能量!


按照官方的定义:

MCP is an open protocol that standardizes how applications provide context to LLMs.

  1. MCP 是一个开放协议/标准;
  2. MCP 标准化了应用如何提供上下文给 llms (底层其实就是 function call,现在你应该很容易理解)

官方还贴了一张图:

现在我们尝试理解一下:

  1. MCP-client 基于 MCP 协议和 MCP server 建立连接,client 从 server 侧拉取它支持的工具列表;
  2. MCP-client 接收你的问题,传入刚刚从 server 侧拉取的 tools,然后发起大模型调用;
  3. 大模型会智能决策是否需要调用工具,如果是就返回工具调用信息,然后发起工具调用请求;
  4. 工具的具体执行在 server 侧,可能读取一些本地数据,也可以通过 web APIs 来发起远程请求;
  5. server 侧把工具执行的结果返回给 client
  6. client 拼接工具调用 message(这一步其实就是拼接上下文),然后再次调用大模型拿到回答。

来跑一个 MCP 实例吧

参考官方的实现,我已经写了一个例子放到 github 上了:github.com/StannyDai20...

开启服务的方式是,在根目录下启用一条命令行:

xml 复制代码
node <path_to_client_script> <path_to_server_script>

先来讲讲里面的核心实现:

MCP-client

入口函数的核心伪代码如下,核心就是连接 mcp-server 然后开启命令行的对话服务。

这里的 chatLoop 和我们第一节里的案例如出一撤

js 复制代码
async function main() {
    const mcpClient = new MCPClient();
    await mcpClient.connectToServer(serverPath);
    await mcpClient.chatLoop();
}

main();

client 类伪代码如下:

connectToServer 核心目的就是拉取 server 端的工具啦

js 复制代码
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";


class MCPClient {
  constructor {
    this.tools = [];
    this.mcp = new Client();
  }

 /**
  * 基于 mcp sdk
  * 1. 建立和 client 和 server 端的连接
  * 2. 拉取 mcp-server 提供的工具列表(tool list)
  * 
  */
  async connectToServer(serverScriptPath) {
    this.transport = new StdioClientTransport({
        command: process.execPath,
        args: [serverScriptPath],
    });
    this.mcp.connect(this.transport);
    // 核心目的就是拉取 server 端的工具呀
    const toolsResult = await this.mcp.listTools();
    this.tools = toolsResult.map(() => ...)
  }

  /**
   * 根据给定输入,调用大模型
   * 模型会自己感知需要调哪些工具
   * 
   * 在需要工具调用的场景下
   * 利用 mcp 提供的能力实现工具执行并拿到结果,然后再次调用大模型就可以拿到最后回答
   */
  async processQuery(query) {
  }

  /**
  * 开启一个命令行 chatbot,内部调用 processQuery(query) 拿到模型回答结果
  */
  async chatLoop() {
  }
}

StdioClientTransport 类

StdioClientTransport 类是 MCP 客户端的传输层实现,负责通过标准输入输出(stdin/stdout)与 MCP 服务器进程通信。

【核心功能】

  • 启动服务器进程
  • 通过标准输入输出传输消息
  • 管理进程生命周期
  • 处理错误和关闭事件

简化版伪代码如下

js 复制代码
class StdioClientTransport {
  constructor(server) {
    this.process = null;
    this.serverConfig = server;
  }

  async start() {
    // 启动子进程
    this.process = spawn(this.serverConfig.command, this.serverConfig.args);

    // 设置事件监听
    this.process.stdout.on('data', (data) => {
        // 处理服务器返回的消息
    });

    this.process.on('error', (error) => {
        // 处理错误
    });
  }

  send(message) {
    // 发送消息到服务器
    this.process.stdin.write(JSON.stringify(message));
  }

  close() {
    // 关闭连接
    this.process.kill();
  }
}

【核心总结】

结合案例的启动方式:node <path_to_client_script> <path_to_server_script>

这里我们发现:mcp-client 是运行在主进程,mcp-server 是运行在子进程,两者通过 stdio(标准输入输出) 实现进程间通信


接下来再看下 processQuery 的核心实现

js 复制代码
async processQuery(query) {
    // 1. 构建初始消息
    const messages = [{ role: "user", content: query }];

    // 2. 调用模型获取响应
    const response = await this.callModel({
        messages,
        tools: this.tools
    });

    // 3. 处理模型响应
    const { tool_calls, content } = response.choices[0].message;

    // 4. 如果需要调用工具
    if (tool_calls) {
        // 并行执行所有工具调用
        const results = await Promise.all(tool_calls.map(call => 
            this.mcp.callTool({
                name: call.function.name,
                arguments: JSON.parse(call.function.arguments)
            })
        ));

        // 5. 将工具结果添加到消息历史
        messages.push(...);

        // 6. 再次调用模型获取最终答案
        return await this.callModel({ messages });
    }

    // 7. 直接返回内容
    return content;
}

MCP-server

server 侧相比 client 要直观的很多:

  1. 调用 sdk 初始化一个 server 实例
  2. 注册工具
  3. 建立连接,把服务跑起来

核心伪代码如下:

js 复制代码
// Create server instance
const server = new McpServer({
  name: "weather",
  version: "1.0.0",
});

// 注册工具
server.tool(name, description, schema, handler);

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server running on stdio");
}
main()

工具的定义格式

关于工具的定义我们可以统一写成对象声明:

js 复制代码
{
    name: "calculate",
    description: "Calculate the sum of two numbers",
    schema: {
        // 这里定义的是【入参类型】,代码里基于 zod,是一个工具库,可以直观的生成 schema 裸数据
    },
    handler: async (params) => {
        // 函数体内容...
    }
};

代码里一共三个工具,前两个都是官方提供的,第三个是自己尝试加了一个工具

  1. get-alerts:查询天气预警的,会发生实际 http 调用
  2. get-forecast:查询天气预报的,会发生实际 http 调用
  3. calculate 简版计算器,本地计算

测试截图

case 1:夏威夷的天气情况查询,包含 1 次工具调用

case 2: 一句话包含 2 次工具调用

case 3:我自己加的计算器工具,测试一下

当然也有一些不太对的地方,比如这里提问 8 的 10次方,ai 回复的工具调用信息不太对,num2 应该也是 8 而非10.

当然我们目前实现中,也不支持多次工具调用

MCP 有啥好处

MCP 提供了 AI 连接外部应用的标准,这个标准大家都认

形成标准之后,各家都去开发自己的 MCP 服务器,例如 github-mcp-server、Google-map-server

设想在标准之前,想让大模型实现获取 github 数据、获取谷歌地图数据,是不是只能手动定制调用他们的开放 API,可能还不一定提供;

另外不同 LLM 提供商返回格式有差别,开发者可能需要手动处理兼容,标准出来之后一定会促进各厂商遵循基础的统一格式,允许开发者方便的在不同 LLM 之间来回切换;

所以我觉得核心的好处就是:标准形成后,生态起来了。

生态起来之后,大家的力量都汇聚在一起,未来在易用性、灵活性、安全性都会做的越来越好。

总结

本文首先结合官网关于 MCP 的概念以及官方的示意图,尝试解读 MCP 的系统流程。

然后结合一个具体的代码实例,阐述了 mcp-client 和 mcp-server 侧的核心实现,并且跑了几个工具调用的case;大家动手把代码跑起来,会有更深的体会。

最后说明了MCP标准出来后带来哪些好处。

当然了,以上都是我目前的个人理解,不一定对,欢迎大家一起参与讨论。

我相信拥有了和外界交互能力的大模型,将会渗透到我们工作和生活的方方面,发挥其更大的能量!

相关推荐
工呈士13 分钟前
CSS in JS:机遇与挑战的思考
javascript·css
至尊童15 分钟前
五个JavaScript 应用技巧
javascript
举个栗子dhy19 分钟前
编辑器、代码块、大模型AI对话中代码复制功能实现
javascript
hyyyyy!20 分钟前
《从分遗产说起:JS 原型与继承详解》
前端·javascript·原型模式
六边形66623 分钟前
一文搞懂JavaScript 与 BOM、DOM、ECMAScript、Node.js的用处
前端·javascript·面试
渭雨轻尘_学习计算机ing24 分钟前
手把手玩转MCP:从入门到实战,解锁AI的“万能插头”
aigc·mcp
Mars狐狸28 分钟前
你踩过console.log的坑吗?从performace说起
前端·javascript
慕雪华年1 小时前
【Python】使用uv管理python虚拟环境
开发语言·python·ai·uv·mcp
学习机器不会机器学习2 小时前
深入浅出JavaScript常见设计模式:从原理到实战(2)
开发语言·javascript·设计模式
富能量爆棚2 小时前
如何搭建spark yarn 模式的集群
大数据·javascript·spark