搞不懂 MCP?那就动手搓一个...(续)

大模型向量数据库机器学习

picture.image

猴哥的第 173 期分享,欢迎追看

上篇文章,带大家动手实现了 MCP Client,将 MCP 协议接入 OpenAI 兼容的 API,给 LLM 接入丰富的外部能力。

搞不懂 MCP?那就动手搓一个...

有朋友问:Client 和 LLM 到底什么关系?

来吧,一图胜千言:

picture.image

不过,上篇实操用 Python 实现,而 MCP sdk 本身是异步的,Python 基于 asyncio 异步编程,在处理事件循环时相对繁琐。

本文继续给出 node.js 版实现,并尝试回答:

  • 接入 MCP server,对 LLM 回复延时,有多少影响?
  • 如何本地部署一个 MCP server 并通过 url 提供服务?

两个版本的代码,放GitHub了,需要的朋友自取:

  1. 客户端实现

具体流程和上篇一致,4 步搞定。

step 1 :定义 server 配置文件。

step 2 :初始化客户端实例:包括 OpenAI 客户端、MCP 客户端列表、工具列表和工具映射。

  
class MCPClient {  
  constructor(stream = false) {  
    this.clients = [];  
    this.availableTools = [];  
    this.toolClientMap = {};  
    this.openai = new OpenAI({  
      apiKey: process.env.OPENAI\_API\_KEY,  
      baseURL: process.env.OPENAI\_BASE\_URL  
    });  
    this.model = process.env.MODEL\_NAME;  
    this.stream = stream;  
    this.config = this.parseConfig(  
      path.join(\_\_dirname, '../mcp\_server\_config.json')  
    );  
  }  

step 3 :连接服务器:

  • 3.1 连接不同类型的 server:对于含有 url 字段的,优先采用 StreamableHTTPClient ,否则向下兼容 SSEClient
  • 3.2 获取server提供的工具列表,并将工具转换为 OpenAI tools 参数所需的格式。
  • 3.3 将工具名称映射到执行该工具的 Client 实例,方便后续根据工具名称找到正确的 Client 实例进行调用。
  
async connectServer() {  
    for (const [serverName, server] of Object.entries(this.config)) {  
      const client = new Client({name: serverName, version: "0.1.0"});  
      if (server.url) {  
        // SSE连接  
      } else {  
        // Stdio连接  
      }  
      const response = await client.listTools();  
      this.clients.push(client);  
      this.availableTools.push(tools);  
    }  
    // 构建tool到client的映射  
    this.clients.forEach((client, idx) => {  
      this.availableTools[idx].forEach(tool => {  
        this.toolClientMap[tool.function.name] = client;  
      });  
    });  
    console.log("Connected to all servers");  
  }  

step 4 :工具调用和响应处理

  
async processQuery(query) {  
    // 创建消息列表  
    const messages = [{role: "user", content: query}];  
    while (true) {  
      const {tool\_functions, response} = await this.getResponse(messages);  
      messages.push(...response);  
      if (!tool\_functions || tool\_functions.length === 0) {  
        break;  
      }  
      for (const tool\_call of tool\_functions) {  
        const tool\_call\_id = tool\_call.id;  
        const tool\_name = tool\_call.function.name;  
        let tool\_args = {};  
          
        try {  
          tool\_args = JSON.parse(tool\_call.function.arguments);  
        } catch (e) {  
          console.error("Failed to parse tool arguments:", e);  
        }  
        console.log("Tool call:", tool\_name, "Tool args:", tool\_args);  
        const client = this.toolClientMap[tool\_name];  
        const result = await client.callTool({name: tool\_name, arguments: tool\_args});  
          
        messages.push({  
          role: "tool",  
          tool\_call\_id,  
          content: JSON.stringify({  
            result: result.content.map(c => c.text),  
            meta: String(result.meta),  
            isError: String(result.isError)  
          })  
        });  
      }  
    }  
    return messages;  
  }  

问题来了:

接入了 MCP server,对 LLM 答复的延时,有多少影响?

实测发现,这部分的延时增加非常显著,主要包括两部分

  • MCP server 处理工具调用本身的延时;
  • LLM 需判断是否要工具调用,带来的延时。
  1. 服务端部署方式对比

2.1 MCP server 延时实测

不同类型服务端,延时差异较大,下面我们以地图服务为例:

  • 高德地图 MCP server
  • 百度地图 MCP server

2.1.1 测试高德地图

最简单 的方式,直接采用 ModelScope 上部署的服务。

填入你在高德地图申请的 API KEY:

picture.image

server 就给你准备好了:

picture.image

但是简单的代价,就是延时非常高,近 4s

  
2025-05-27T01:41:22.664Z Calling tool: maps\_weather  
2025-05-27T01:41:26.305Z Got tool result: {  

如果对延迟要求不高,那就尽情薅阿里的服务器资源吧。

当然,要想自主可控,还是用高德提供的 npm 包,本地起一个服务:

https://www.npmjs.com/package/@amap/amap-maps-mcp-server

picture.image

如果报错提示找不到 npx,是因为 server 子进程找不到 node 安装包,可以参考如下,指定 commandPATH

  
"amap-maps": {  
    "command": "/root/tools/node-v22.14.0-linux-x64/bin/npx",  
    "args": [  
        "-y",  
        "@amap/amap-maps-mcp-server"  
    ],  
    "env": {  
        "AMAP\_MAPS\_API\_KEY": "xxx",  
        "PATH": "/root/tools/node-v22.14.0-linux-x64/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"  
    }  
}  

本地服务起来后,你看只需 0.5s

  
2025-05-27T01:40:20.785Z Calling tool: maps\_weather  
2025-05-27T01:40:21.226Z Got tool result: {  

2.1.2 测试百度地图

https://github.com/baidu-maps/mcp

同样,采用 ModelScope 上部署的服务,需要 2s

  
2025-05-27T01:36:19.070Z Calling tool: map\_weather  
2025-05-27T01:36:21.201Z Got tool result: {  

而本地服务,只要 0.3s

  
2025-05-27T01:38:35.188Z Calling tool: map\_weather  
2025-05-27T01:38:35.455Z Got tool result: {  

2.2 stdio VS SSE

上述对比,你会发现:

  • StreamHTTP/SSE 通过 url 连接,更简洁且方便管理
  • stdio 服务就在本地,延时更低

如果一个 server 需要服务多个 client,显然第一种方式更合理。

问题来了:如何本地部署一个 MCP server 并通过 url 来提供服务?

2.3 使用 MCP 代理

https://github.com/sparfenyuk/mcp-proxy

这个项目为 StreamHTTP/SSE 和 stdio 的 MCP 传输之间搭建了一座桥梁。

安装也非常简单,比如采用 uv 管理工具,只需一行命令:

  
uv tool install mcp-proxy  

然后,把本地的百度地图Server切换成StreamHTTP/SSE,只需两行命令:

  
export PATH="/root/.local/bin:$PATH"  
mcp-proxy --port=9000 -e BAIDU\_MAP\_API\_KEY xxx -- npx -y @baidumap/mcp-server-baidu-map  

你看启动后的日志,就是用 uvicorn 搭了一座桥:

  
INFO:     Started server process [239341]  
INFO:     Waiting for application startup.  
[I 2025-05-27 11:09:03,610.610 mcp.server.streamable\_http\_manager] StreamableHTTP session manager started  
[I 2025-05-27 11:09:03,610.610 mcp\_proxy.mcp\_server] Application started with StreamableHTTP session manager!  
INFO:     Application startup complete.  
INFO:     Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)  

服务启动后,只需修改 server 配置为:

  
"baidu-map": {  
        "url": "http://127.0.0.1:9000/sse"  
    },  

实测本地服务 + mcp-proxy,仅 0.2s ,Nice!

  
2025-05-27T03:10:43.145Z Calling tool: map\_weather  
2025-05-27T03:10:43.348Z Got tool result: {  

2.4 结果展示

注:以下测试 LLM 为阿里云的qwen-plus

  
北京明后两天天气怎么样  

可以看到模型调用了 map\_weather 工具,最终回复如下:

  
2025-05-27T03:23:34.074Z Calling tool: map\_weather  
2025-05-27T03:23:43.485Z llm: 北京明后两天的天气预报如下:  
5月28日,星期三,晴朗,最高温度为33度,最低温度为19度,白天和夜晚的风向都是南风和西南风,风力小于3级。  
5月29日,星期四,晴朗,最高温度为34度,最低温度为20度,白天和夜晚的风向均为南风,风力小于3级。   
请记得根据天气情况调整穿着和出行计划!  

继续测试:

  
故宫附近有哪些美食推荐  

可以看到调用了 map\_search\_places 工具,最终回复如下:

  
2025-05-27T03:24:46.005Z Calling tool: map\_search\_places  
2025-05-27T03:25:09.826Z llm: 在北京故宫附近,我为您找到了一些美食推荐:  
1. 老北京炸酱面老店(南长街店):位于南长街乙30号。  
2. 故宫冰窖餐厅:位于北长街故宫博物院慈宁宫南侧。  
3. 故宫快餐:位于南长街护照签售处旁。  
4. 泽园酒家(南长街店):位于南长街20号(近中山公园西门)。  
5. 崂山渔家(西单店):位于北新华街29号昌盛大厦1层1010室。  
6. 川湘家常菜(西单店):位于大六部口胡同13号(音乐厅西侧)。  

连环问:

  
北京今天天气怎么样,故宫附近有哪些美食推荐  

可以看到模型分别调用了map\_weathermap\_search\_places 工具:

  
2025-05-27T06:03:36.831Z llm: start  
2025-05-27T06:03:39.432Z Calling tool: map\_weather  
2025-05-27T06:03:39.516Z Got tool result  
2025-05-27T06:03:42.406Z Calling tool: map\_search\_places  
2025-05-27T06:03:42.641Z Got tool result  

过程中的所有 message 包括:

  
{ role: 'user', content: '北京今天天气怎么样,故宫附近有哪些美食推荐' },  
{ role: 'assistant', content: null, tool\_calls: [ [Object] ] },  
{ role: 'tool', tool\_call\_id: 'call\_db20e6291ec044daa38876',content: '{"result":["Weather searth failed: 查询的经纬度值范围无效"],"meta":"undefined","isError":"true"}'},  
{ role: 'assistant', content: null, tool\_calls: [ [Object] ]},  
{ role: 'tool', tool\_call\_id: 'call\_c66fde200d8949d1b5d5b5', content: }  

  1. LLM 延时实测

看到这里,还会遇到一个问题:MCP server 返回的结果很长,导致用户拿到结果的延时太长!

你看,上面这个案例,从提问到模型回答,足足有近 30s

  
2025-05-27T03:24:40.214Z llm: start  
2025-05-27T03:25:09.826Z llm: 在北京故宫附近,我为您找到了一些美食推荐:  

所以,流式输出 ,对于实时对话应用至关重要。

还是上面那个例子,改为流式输出,首句延时从 30s 降到 5s 左右

  
Connected to all servers  
2025-05-27T06:43:32.327Z llm: start stream  
2025-05-27T06:43:36.027Z Calling tool (stream): map\_weather { location: '116.307526,40.056945', districtId: '110108' }  
2025-05-27T06:43:36.113Z Got tool result (stream)  
2025-05-27T06:43:37.526Z llm: 今天是2025年5月27日星期二,  
2025-05-27T06:43:39.443Z llm: 北京海淀的天气情况如下:当前天气:晴当前温度:31度体感温度:32度相对湿度:30%风力等级:2级风向:西南风今天白天天气预计为多云,  
2025-05-27T06:43:39.780Z llm: 夜晚也将是多云。  
2025-05-27T06:43:40.950Z llm: 风向为西南风,  
2025-05-27T06:43:41.512Z llm: 请注意适时增减衣物,  
2025-05-27T06:43:41.666Z llm: end  

如果不进行任何工具调用 呢?

首句延时需要 0.9-1.5s

  
Connected to all servers  
2025-05-27T06:48:22.071Z llm: start stream  
2025-05-27T06:48:23.346Z llm: 你好,  

如果不加入任何 MCP Server 呢?

首句延时 0.5s

  
2025-05-27T06:51:33.457Z llm: start stream  
2025-05-27T06:51:33.990Z llm: 你好,  

显然,接入 MCP Server,LLM 需要要判断是否要进行工具调用,大大增加了延时。

写在最后

本文分享了 MCPClientnodejs 实现,并实测对比了不同服务端部署带来的延时。

如果对你有帮助,欢迎点赞收藏 备用。

完整代码见:https://github.com/hougeai/mcp-client

👇 关注猴哥,快速入门AI工具

picture.image

# AI 工具:

本地部署大模型?看这篇就够了,Ollama 部署和实战

盘点9家免费且靠谱的AI大模型 API,统一封装,任性调用!

免费GPU算力本地跑DeepSeek R1,无惧官方服务繁忙!

# AI应用** :**

弃坑 Coze,我把 Dify 接入了个人微信,AI小助理太强了

我把「FLUX」接入了「小爱」,微信直接出图,告别一切绘画软件!

202K 下载!最强开源OCR:本地部署,邀你围观体验

阿里开源TTS CosyVoice 再升级!语音克隆玩出新花样,支持流式输出

借 WeChatFerry 东风,我把微信机器人复活了!

成本不到50的AI对话机器人,如何自建服务端?自定义角色+语音克隆

0
0
0
0
关于作者

文章

0

获赞

0

收藏

0

相关资源
火山引擎大规模机器学习平台架构设计与应用实践
围绕数据加速、模型分布式训练框架建设、大规模异构集群调度、模型开发过程标准化等AI工程化实践,全面分享如何以开发者的极致体验为核心,进行机器学习平台的设计与实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论