MCP,一篇文章学会MCP 【通过Nodejs 搭建MCP服务】

在当今快速发展的技术领域,大语言模型与外部世界的互动方式正经历着革新。MCP(Model Context Protocol)协议作为一个开源项目,旨在通过提供标准化的方法,让任何语言模型都能无缝连接各种数据源和工具,从而重新定义这种互动模式。本文将深入探讨如何基于MCP协议开发服务器和客户端,并将其发布到NPM。

MCP官网

https://modelcontextprotocol.io
在这里插入图片描述

MCP市场

https://mcp.so/
在这里插入图片描述

一、MCP 协议简介

首先,MCP协议的核心功能包括资源管理、提示词处理、工具集成、采样机制等,这些构成了一个强大的框架,支持开发者创建复杂的应用场景。以网络搜索工具为例,展示了如何利用MCP协议实现从用户输入到结果展示的完整流程。该工具使用了@modelcontextprotocol/sdk、axios以及zod等依赖库,其中@modelcontextprotocol/sdk是官方提供的SDK,简化了与MCP协议相关的操作;axios用于发送HTTP请求;zod则确保数据验证的准确性。

github地址:https://github.com/alphabetabc/fe-to-ai/tree/main/packages/mcp-chinese-guide-nodejs

MCP 有以下几个核心功能:

Resources 资源
Prompts 提示词
Tools 工具
Sampling 采样
Roots 根目录
Transports 传输层
packcge.json

"scripts": {
  "start-simple-client": "tsx --env-file=.env --max-old-space-size=4096 ./src/mcp-simple-client.ts",
  "start-client": "tsx --env-file=.env --max-old-space-size=4096 ./src/mcp-cllient.ts",
  "start-mcp-server": "tsx --env-file=.env --max-old-space-size=4096 ./src/mcp-server-web-search.ts",
  "mcp-inspector-mcp-server": "mcp-inspector pnpm start:mcp-server",
  "start-mcp-server-sampling-del-file": "tsx --env-file=.env --max-old-space-size=4096 ./src/sampling/mcp-server-sampling-del-file.ts",
  "start-mcp-client-sampling-del-file": "tsx --env-file=.env --max-old-space-size=4096 ./src/sampling/mcp-client-sampling-del-file.ts"
},
"dependencies": {
  "@modelcontextprotocol/sdk": "^1.8.0",
  "axios": "^1.8.4",
  "@modelcontextprotocol/inspector": "^0.8.0",
  "zod": "^3.24.2",
  "openai": "^4.91.1"
}

二、开发 MCP 服务器

(一)创建项目并安装依赖

开发MCP服务器的过程相对直接。开发者需要先初始化Node.js项目并安装必要的依赖。
随后,创建一个MCP服务器实例,定义其名称、版本及能力集,并实现具体的工具逻辑。
在这个例子中,服务器被配置为接受用户查询并通过智谱青言API进行搜索,返回相关结果或错误信息。
为了运行服务器,需确保.env文件内包含所需的环境变量,并通过npm start-mcp-server命令启动服务。

首先,我们需要创建一个 Node.js 项目并安装必要的依赖。在项目目录下运行以下命令:

npm init -y
npm install @modelcontextprotocol/sdk axios zod

这里,

@modelcontextprotocol/sdk 

是 MCP 协议的官方 SDK,axios 用于发送 HTTP 请求,zod 用于数据验证

(二)实现网络搜索工具

接下来,我们创建一个简单的 MCP 服务器,实现一个网络搜索工具。创建文件 mcp-server-web-search.ts,并编写以下代码:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import axios from 'axios';
import { z } from 'zod';

const server = new McpServer({
  name: 'webSearch',
  version: '1.0.0',
  capabilities: {
    resources: {},
    tools: {},
  },
});

server.tool('webSearch', 'webSearch', { input: z.string() }, async (state) => {
  const input = state.input; // 获取用户输入的搜索内容
  try {
    const response = await axios.post(
      process.env.ZHI_PU_QING_YAN_BASE_URL as string, // 智谱青言的 API 地址
      {
        tool: process.env.ZHI_PU_QING_YAN_TOOL as string, // 使用的工具名称
        messages: [{ role: 'user', content: input }],
        stream: false,
      },
      {
        headers: {
          Authorization: process.env.ZHI_PU_QING_YAN_KEY, // API 密钥
        },
      }
    );

    const resData: string[] = [];
    for (const choice of response.data.choices) {
      for (const message of choice.message.tool_calls) {
        const searchResults = message.search_result;
        if (!searchResults) {
          continue;
        }
        for (const result of searchResults) {
          resData.push(result.content); // 收集搜索结果的内容
        }
      }
    }

    return {
      content: [
        {
          type: 'text',
          text: resData.join('\n\n\n'), // 将所有搜索结果拼接成一个字符串
        },
      ],
    };
  } catch (error: any) {
    return {
      content: [
        {
          type: 'text',
          text: error.message, // 返回错误信息
        },
      ],
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('Web Search MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Fatal error in main():', error);
  process.exit(1);
});

(三)运行服务器

在运行服务器之前,确保在 .env 文件中配置了必要的环境变量,例如智谱青言的 API 地址和密钥。然后运行以下命令启动服务器:

这里运行没有任何效果~~~~~

npm start-mcp-server

在这里插入图片描述

三、开发 MCP 客户端

(一)连接服务器并调用工具

创建文件 mcp-client.ts,并编写以下代码:

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import OpenAI from 'openai';
import readline from 'readline/promises';

const config = {
    apiKey: process.env.ALI_TONGYI_API_KEY,
    baseURL: process.env.ALI_TONGYI_BASE_URL,
    model: process.env.ALI_TONGYI_MODEL as string,
};

class McpClient {
    client: OpenAI;
    mcp: Client;
    exitStack = [] as Array<() => Promise<void>>;
    tools: any;
    rl: readline.Interface;

    constructor() {
        this.client = new OpenAI({
            apiKey: config.apiKey,
            baseURL: config.baseURL,
        });
        this.mcp = new Client({ name: 'mcp-client', version: '0.0.1' });

        /**
         * Run an interactive chat loop
         */
        this.rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });
    }

    processQuery = async (query: string) => {
        const systemPrompt = [
            'You are a helpful assistant.',
            'You have the function of online search. ',
            'Please MUST call web_search tool to search the Internet content before answering.',
            "Please do not lose the user's question information when searching,",
            'and try to maintain the completeness of the question content as much as possible.',
            "When there is a date related question in the user's question,",
            'please use the search function directly to search and PROHIBIT inserting specific time.',
        ];

        const messages: any = [
            { role: 'system', content: systemPrompt.join('\n') },
            { role: 'user', content: query },
        ];

        const completion = await this.client.chat.completions.create({
            model: config.model,
            messages,
            temperature: 0,
            tools: this.tools, // 这里需要传入工具列表
        });

        const content = completion.choices[0];

        // console.log('log----------------', content.message);

        // python代码: messages.append(content.message.model_dump())
        // https://github.com/openai/openai-node/blob/master/helpers.md
        // 工具消息应该是对工具调用的直接响应,而不能独立存在或作为其他消息类型的响应。
        messages.push(content.message);

        if (content.finish_reason === 'tool_calls') {
            // 如何是需要使用工具,就解析工具
            for (const toolCall of content.message.tool_calls!) {
                const toolName = toolCall.function.name;
                const toolArgs = JSON.parse(toolCall.function.arguments);

                // 调用工具
                const result = await this.mcp.callTool({ name: toolName, arguments: toolArgs });
                messages.push({
                    role: 'tool', // 工具消息的角色应该是 tool
                    content: result.content, // 工具返回的结果
                    tool_call_id: toolCall.id,
                    name: toolName,
                });
            }
        }

        //
        const response = await this.client.chat.completions.create({
            model: config.model,
            messages, // 这里需要传入工具调用的结果
            tools: this.tools, // 这里需要传入工具列表,这里必填
        });

        return response.choices[0].message.content;
    };

    connectToServer = async () => {
        // 通常用于实现基于标准输入输出(stdin/stdout)的客户端与服务器之间的通信
        const transport = new StdioClientTransport({
            // 这块直接调用了我的命令行
            command: 'pnpm start-mcp-server',
            args: [],
        });
        await this.mcp.connect(transport);

        const toolsResult = await this.mcp.listTools();
        this.tools = toolsResult.tools.map((tool) => {
            return {
                type: 'function', // 添加工具类型
                function: {
                    name: tool.name,
                    type: 'function', // 添加工具类型
                    description: tool.description,
                    input_schema: tool.inputSchema,
                    // openai的function参数格式,和mcp的inputSchema格式不同,需要转换
                    parameters: tool.inputSchema.properties,
                },
            };
        });
    };

    chatLoop = async () => {
        const rl = this.rl;
        try {
            console.log('\nMCP Client Started!');
            console.log("Type your queries or 'quit' to exit.");

            const message = await rl.question('\nQuery: ');

            if (message.toLowerCase() === 'quit') {
                rl.close();
                process.exit(0);
            }

            const response = await this.processQuery(message);

            console.log('\n======================================');
            console.log(response);
            console.log('======================================\n');
            this.chatLoop();
        } finally {
            // process.exit(0);
        }
    };

    cleanup = async () => {
        for (const exit of this.exitStack) {
            await exit();
        }
    };
}

const main = async () => {
    const mcp = new McpClient();

    try {
        await mcp.connectToServer();
        await mcp.chatLoop();
    } catch (error) {
        console.error('Error:', error);
        await mcp.cleanup();
    }
};

main();

(二)运行客户端

运行客户端代码,调用服务器上的工具:
npm start-client
在这里插入图片描述

四、发布到NPM上

将你的 Node.js 项目发布到 NPM(Node Package Manager)上是一个相对简单的过程。以下是详细的步骤:


1. 准备工作

在发布之前,确保你的项目已经准备好,并且符合以下要求:

  • 项目代码已完成并测试通过。
  • 项目根目录下有一个 package.json 文件。
  • 确保你已经在 npmjs.com 注册了一个账号。

2. 初始化项目(如果尚未初始化)

如果你的项目还没有 package.json 文件,可以通过以下命令生成一个:

npm init

按照提示填写项目的相关信息,例如:

  • 包名(name):必须是唯一的,不能与现有的 npm 包冲突。
  • 版本号(version):推荐使用语义化版本号(如 1.0.0)。
  • 描述(description):简要描述你的包功能。
  • 入口文件(main):通常是 index.js 或其他主文件。
  • 关键词(keywords):用于搜索的关键词。
  • 许可证(license):如 MIT

完成初始化后,会生成一个 package.json 文件。


3. 登录到 npm

在终端中运行以下命令登录到 npm:

npm login

系统会提示你输入用户名、密码和邮箱地址。如果登录成功,你会看到类似以下的输出:

Logged in as <your-username> on https://registry.npmjs.org/.

4. 检查包名是否唯一

在发布之前,建议检查你选择的包名是否已经被占用。你可以访问 npmjs.com 并搜索你的包名,或者直接尝试发布。

如果包名已被占用,你需要更改 package.json 中的 name 字段为一个唯一的名称。


5. 发布包

在项目根目录下运行以下命令发布包:

npm publish

如果一切正常,你会看到类似以下的输出:

+ <your-package-name>@1.0.0

此时,你的包就已经成功发布到 npm 上了!


6. 验证发布

访问 npmjs.com 并搜索你的包名,确认它已经上线。

你也可以通过以下命令安装你的包进行验证:

npm install <your-package-name>

7. 更新包

如果需要更新你的包,可以按照以下步骤操作:

  1. 修改代码或添加新功能。
  2. 更新 package.json 中的 version 字段(遵循语义化版本规则)。
    • 修复问题:增加补丁版本号(如 1.0.1)。
    • 添加功能:增加次版本号(如 1.1.0)。
    • 不兼容的重大更改:增加主版本号(如 2.0.0)。
  3. 再次运行 npm publish

8. 注意事项

  • 私有包:默认情况下,发布的包是公开的。如果你想发布私有包,需要订阅 npm 的付费计划。
  • 敏感信息:不要在包中包含敏感信息(如 API 密钥)。可以使用 .npmignore 文件或 .gitignore 文件来排除不需要发布的文件。
  • 许可证:确保你的项目包含一个合适的开源许可证(如 MIT、Apache-2.0)。

9. 删除已发布的包(可选)

如果你需要删除已发布的包,可以使用以下命令:

npm unpublish <your-package-name>@<version>

注意:

  • 只能在发布后的 72 小时内删除。
  • 如果包已经被其他人依赖,不建议删除。

通过以上步骤,你就可以成功地将自己的 Node.js 项目发布到 npm 上,并与全球开发者分享!

五、MCP资源网站

1、mcp.so

https://mcp.so/
在这里插入图片描述

2、MCP广场-摩搭社区

https://modelscope.cn/mcp?category=browser-automation&page=1
在这里插入图片描述

内容概要:本文详细介绍了MCP(Model Context Protocol,模型上下文协议)的原理与开发流程。MCP协议起源于2024年11月25日Anthropic发布的文章,旨在解决不同AI模型间工具调用的碎片化问题,提供标准化接入方式,实现多功能应用与创新体验。文章首先解释了MCP的核心概念及其重要性,接着深入探讨了MCP Server和Client的开发步骤,包括项目搭建、工具注册、资源创建、提示符配置以及调试方法。此外,还讲解了MCP的工作原理,特别是C/S架构、JSON-RPC 2.0协议的应用,以及模型如何选择和执行工具。最后,通过一个实际案例展示了如何利用MCP协议构建一个企业微信机器人,实现查询知识库并将结果发送到群聊中。 适合人群:具备一定编程基础,对AI模型集成、标准化开发感兴趣的开发者,尤其是从事大语言模型相关工作的工程师。 使用场景及目标:①解决多模型集成中的工具调用标准化问题;②构建具备多种功能的企业级AI应用,如邮件发送、图像生成等;③学习如何通过MCP协议实现模型决策与工具执行的解耦,提升开发效率和用户体验。 阅读建议:本文适合有一定编程经验的读者,尤其是对AI模型集成有需求的技术人员。建议读者跟随文中提供的示例代码进行实践,理解MCP协议的核心机制,并尝试构建自己的MCP应用。同时,关注官方文档和社区动态,以获取最新的技术支持和开发指南。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员猫爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值