返回市场
MCP类似服务器

MCP类似服务器

作者:freakynit2 星标更新:2025-03-05

项目介绍

【技术文档摘要】: 这是一个为了理解此类工具背后核心概念而创建的自定义实现的MCP服务器。整个过程在不到3小时内完成,因此没有关注代码质量或其他方面。这纯粹是为了学习(和实验)。

示例聊天运行

日志级别设置为info,而不是debug,因此不会显示背景消息。

搜索最新的美国政治新闻

> custom-mcp-server-implementation@1.0.0 example_chat
> node src/example_chat.js

查询(输入exit/quit/bye退出)...搜索最新的美国政治新闻
info: [最终答案]
[{"type":"ORGANIC","link":"https://www.cnn.com/politics","description":"据消息人士透露,国税局正在起草计划,削减多达其90,000人工作队伍的一半……特朗普表示,美国已逮捕了负责此事的“顶级恐怖分子”……","title":"CNN - 政治"},{"type":"ORGANIC","link":"https://www.nbcnews.com/politics","description":"在NBCNews.com上找到最新的政治新闻故事、照片和视频。阅读涵盖国会、民主党、共和党和更多内容的头条新闻……","title":"最新新闻和头条 | NBC News"},{"type":"ORGANIC","link":"https://www.nytimes.com/international/section/politics","description":"关于美国政治的最新报道和分析,包括白宫、国会、最高法院等的最新报道……","title":"纽约时报国际版 - 美国政治"},{"type":"ORGANIC","link":"https://www.theguardian.com/us-news/us-politics","description":"唐纳德·特朗普总统在美国国会联席会议上发表讲话。特朗普在长时间的演讲中强调了右翼政策的复兴,而民主党人则嗤之以鼻……","title":"卫报 - 美国政治"},{"type":"ORGANIC","link":"https://www.politico.com/politics","description":"支持乌克兰的抗议者在美国国会大厦外集会。特朗普:泽伦斯基说乌克兰已经准备好和平。由DASHA BURNS撰写。03/……","title":"POLITICO - 最新和突发的政治新闻"},{"type":"ORGANIC","link":"https://abcnews.go.com/Politics","description":"特朗普下令暂停对乌克兰的军事援助。这一举动是在美国总统与乌克兰领导人的激烈椭圆形办公室会议后发生的。03月03日……","title":"ABC新闻 - 最新的政治新闻、视频和分析"},{"type":"ORGANIC","link":"https://www.bbc.com/news/topics/cwnpxwzd269t","description":"7小时前——总统宣布“美国回来了”,他享受着共和党的掌声,并与民主党人针锋相对。现在。世界。","title":"BBC新闻 - 美国政治"},{"type":"ORGANIC","link":"https://www.independent.co.uk/news/world/americas/us-politics","description":"美国政治——特朗普关税背后的真相以及美国经济的走向——随着墨西哥和加拿大的关税开始,股市下跌……","title":"独立报 - 最新的新闻、评论和分析"},{"type":"ORGANIC","link":"https://www.politico.com/","description":"民主党认真对待政府关门——共和党人在特朗普的关税影响到他们的州时感到不安——泽伦斯基说现在是“纠正错误”的时候了……","title":"POLITICO - 政治、政策、政治新闻"}]
查询(输入exit/quit/bye退出)...

服务器CPU数量

> custom-mcp-server-implementation@1.0.0 example_chat
> node src/example_chat.js

查询(输入exit/quit/bye退出)...告诉我服务器CPU数量
info: [最终答案]
8
查询(输入exit/quit/bye退出)...

不调用工具然后页面抓取工具调用

> custom-mcp-server-implementation@1.0.0 example_chat
> node src/example_chat.js

查询(输入exit/quit/bye退出)...今天你过得怎么样?
info: [最终答案]
我只是个计算机程序,所以我没有感情,但我在这里,随时准备帮助您需要的一切!今天我能为您做些什么?
查询(输入exit/quit/bye退出)...提取此页面内容:https://example.com/
info: [最终答案]
# 示例域名

此域用于文档中的说明性示例。您可以在此文献中使用该域,无需事先协调或请求许可。

[更多信息...](https://www.iana.org/domains/example)
查询(输入exit/quit/bye退出)...退出

安装和运行

  1. 设置qdrant向量存储。见下文(设置Qdrant作为向量存储)
  2. 克隆仓库
  3. 复制.env.example.env并填写所需值。FIRECRAWL_KEY仅在您想使用页面抓取工具时需要。其他都是必需的。
  4. CHAT_LOG_LEVEL暂时设置为debug
  5. 启动服务器(确保qdrant正在运行且一切默认):npm run server
  6. 在另一个窗口中,启动透明地使用此类似MCP服务器进行工具调用的示例聊天循环:npm run example_chat
  7. 首先尝试一个不需要调用任何工具的简单消息:今天你过得怎么样
  8. 现在,让我们测试工具调用。functions目录中有几个可用工具。查看它们。
  9. 输入:搜索最新的美国政治新闻。控制台会出现很多消息……这些都是调试消息。最终结果以最终答案开头。您也可以将日志级别设置为info,只查看最终答案。
  10. 另一个例子:告诉我服务器CPU数量
  11. FireCrawl测试(免费注册一个密钥):提取此页面内容:https://example.com/
  12. 令人困惑的一个:加这些数字:10, 20。为什么这个令人困惑?因为对于如此简单的加法,LLMs不需要依赖工具调用。因此,这并没有使用任何工具调用。
  13. 让我们强制这个工具调用:加这些数字:10, 20 使用工具调用。这次,我们的工具:functions/add_numbers.js被调用。随时启用debug日志记录以查看幕后消息。

设置Qdrant作为向量存储

docker run -p 6333:6333 -p 6334:6334 \
    -v $(pwd)/qdrant_storage:/qdrant/storage \
    qdrant/qdrant

UI位于:http://localhost:6333/dashboard

理解幕后发生的事情

  1. 广泛来说,MCP暴露两个端点:第一个用于查询注册的功能/工具,第二个用于执行选定函数及其参数。
  2. 这个选择执行哪个函数是由LLM完成的。
  3. 如果您查看我们的聊天代码,您会看到我们在一开始就定义了两个这样的函数/工具,使用变量:toolsServerTools
  4. 这些完全对应于两个服务器端点:/api/functions/search/api/functions/execute
  5. 聊天循环基本上将这两个工具调用定义添加到每个用户聊天消息中。
  6. 然后,它依赖于LLM决定当前问题是否可以原生回答(不依赖任何外部工具),或者它需要工具的帮助来正确回答用户的问题。
  7. 重要的是要记住:目前(第一次用户聊天消息),它没有任何已注册工具/函数的知识。它只知道有一个工具可以用来搜索函数(或更多的工具),其中一个可能能够回答用户的问题。
  8. 这就是toolsServerTools中第一个注册工具发挥作用的地方。
  9. 现在,LLM在其响应中返回工具调用。这被example_chat循环捕获:
    let content = result?.choices?.[0]?.message?.content;
    let toolCalls = result?.choices?.[0]?.message?.tool_calls;
    
    if(content) {
        logger.info(`[最终答案]\n${typeof content === 'object' ? JSON.stringify(content) : content}`);
    } else {
        // ... 工具调用在这里被捕获    
    }
    
  10. 现在,在提取函数/工具名称及其相应参数后,调用handleToolCall方法。由于这是第一次工具调用,实际上是searchFunctions工具调用,对应于我们的/api/functions/search端点。
  11. 我们使用LLM提供的参数调用此端点,并返回最相关的函数(目前是前三)。然后将这些添加到现有的toolsServerTools变量中:
    for(const availableTool of toolCallResult?.result) {
        toolsServerTools.push({
            "type": "function",
            "function": availableTool?.definition
        });
    }
    
  12. 现在,原始消息再次发送,但这一次,我们的LLM已经拥有最有可能的函数,可以最终回答用户的问题(因为我们已经添加了搜索的函数,如上所示)。
  13. 现在,LLM返回最终的工具调用及其相应的参数。我们再次调用handleToolCall方法,但这一次,最终的else情况与正确的注册函数匹配,并传递相应的参数。
  14. 这个工具由我们的服务器在/api/functions/execute端点执行。
  15. 此函数/工具/服务器API调用的结果是最终答案,显示给用户。
  16. 请注意,此聊天循环不保留聊天历史。这只是单次聊天。

各种输入的示例流程

  1. 今天你过得怎么样 => 用户 -> LLM -> 完成。
  2. 搜索最新的美国政治新闻 => 用户 -> LLM -> 工具调用(searchFunctions)=> [最匹配的三个函数] => 添加到工具调用定义中 => 再次调用LLM => 工具调用(googleSearch)=> 最终结果 => 用户。
  3. 提取此页面内容:https://example.com/ => 流程与上述相似,但第二次工具调用是extractPageContent