在当今快速发展的技术领域,大语言模型与外部世界的互动方式正经历着革新。MCP(Model Context Protocol)协议作为一个开源项目,旨在通过提供标准化的方法,让任何语言模型都能无缝连接各种数据源和工具,从而重新定义这种互动模式。本文将深入探讨如何基于MCP协议开发服务器和客户端,并将其发布到NPM。
MCP官网
https://modelcontextprotocol.io
MCP市场
一、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. 更新包
如果需要更新你的包,可以按照以下步骤操作:
- 修改代码或添加新功能。
- 更新
package.json
中的version
字段(遵循语义化版本规则)。- 修复问题:增加补丁版本号(如
1.0.1
)。 - 添加功能:增加次版本号(如
1.1.0
)。 - 不兼容的重大更改:增加主版本号(如
2.0.0
)。
- 修复问题:增加补丁版本号(如
- 再次运行
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
2、MCP广场-摩搭社区
https://modelscope.cn/mcp?category=browser-automation&page=1