基于nodejs(typeScript)搭建的MCP Client和 MCP Server并整合到web项目中(代码已全部开源)
本文介绍了基于MCP协议实现大模型交互的技术方案。系统采用前后端分离架构,前端使用Vue3,后端采用SpringBoot+MySQL+Redis,通过Node.js实现的MCP客户端和服务端进行协议转换。重点讲解了MCPClient和MCPServer的工作流程:前端请求通过MCPClient获取格式化接口数据(Tools)并提交给大模型,处理后返回结果。文章提供了关键代码示例,包括Express
一:什么是MCP Client和MCPserver
网上到处有解释,作者就不过多赘述了,简单来说可以通过MCP协议实现产品项目和大模型进行交互。
二:环境依赖
业务接口由springboot项目提供,后台管理系统使用vue3+vite,MCP项目使用的是node+typescript,所以所需的环境有:
后端:jdk1.8,mysql8.0,maven3.6,redis5.0
前端:node22.12.0,vue3。
三:项目说明

mall-backend:springboot项目
mall-mcp-client:mcp面向客户端
mall-mcp-server:mcp面向服务端
mall-web:后台管理前端代码
这个工作流程为:首先客户端(mall-web)通过接口,将提的问题传给mall-mcp-client,mall-mcp-client获取到数据之后,通过mcp sdk获取到mall-mcp-server提供的与之最符合的tools(tools实际上就是一个格式化后的接口数据),随后将获取到的tools提交给到大模型。支持mcp的大模型,会调用tools,等大模型调用好之后,mall-mcp-client将大模型的处理数据返回给到mall-web(前端)
温馨提示:使用MCP链接到数据库,只能用于查询和数据展示,最好不要对数据集进行写操作,因为大模型做写操作参数容易给错或者会抽风调用与之不相干的tools。
mall-mcp-client使用express实现https模式对外提供api
四:部分代码
前端项目mall-vite-web:vue3
下载依赖:
npm install
运行项目
npm run dev
const sendMessage = () => {
if (message.value) {
let user: any = JSON.parse(localStorage.getItem("user"));
let token = "";
if (user && user["token"]) {
token = user.token;
}
sendLOading.value = true;
const data = {
role: "user",
content: message.value,
token,
};
messageList.value.push({
role: "user",
content: message.value,
});
message.value = "";
nextTick(() => {
scrollToBottom();
});
getAiquestion(data)
.then((res: any) => {
const messageData = {
role: "assistant",
content: res.data.message,
};
console.log("res", res);
messageList.value.push(messageData);
localStorage.setItem("messageList", JSON.stringify(messageList.value));
nextTick(() => {
scrollToBottom();
});
})
.finally(() => {
sendLOading.value = false;
});
}
};
mall-mcp-client项目
依赖下载
npm install
先打包再运行
打包
npm run build
运行
npm run dev
import { Client } from "@modelcontextprotocol/sdk/client";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import OpenAI from "openai";
import bodyParser from "body-parser";
const MODEL = "qwen-plus";
import express from "express";
const app = express();
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
// 连接 mcp 服务器,这里使用node启动weather.js
// 请根据实际情况更改 weather.js 文件路径
const transport = new StdioClientTransport({
command: "node",
args: ["E:/template-mcp/mall-mcp-server/build/index.js"],
});
const mcpClient = new Client({
name: "mcp-client",
version: "1.0.0",
});
await mcpClient.connect(transport);
// 获取 mcp server tools 目前两个工具
const ret = await mcpClient.listTools();
const { tools } = ret;
console.log(
"Connected to mcp server with tools:",
tools.map(({ name }) => name)
);
console.log(`mcpTools: ${JSON.stringify(tools, null, 2)}\n`);
// 转换MCP tools到OpenAI格式
const convertToOpenAITools = (mcpTools: any) => {
return mcpTools.map((tool: any) => ({
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
}));
};
const openaiTools = convertToOpenAITools(tools);
console.log(`openaiTools: ${JSON.stringify(openaiTools, null, 2)}\n`);
// 初始化OpenAI
const openai = new OpenAI({
apiKey: "sk-xxxxx",
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
});
// 调用OpenAI
async function callFunction(toolCall: any, token = "") {
const args = JSON.parse(toolCall.function.arguments);
const result = await mcpClient.callTool({
name: toolCall.function.name,
arguments: { ...args, token: token },
});
console.log(`工具返回结果: ${JSON.stringify(result, null, 2)}`);
return result.content;
}
async function runConversation(query: string, token = "") {
const messages: any = [{ role: "user", content: query }];
// 初始调用,可能会触发工具调用
const completion = await openai.chat.completions.create({
model: "qwen-plus",
messages,
tools: openaiTools,
tool_choice: "auto",
});
const responseMessage = completion.choices[0].message;
// 第一次回复
// 判断是否需要调用工具
if (responseMessage.tool_calls) {
// 将AI回复加入对话历史
messages.push(responseMessage);
// 处理每个工具调用
for (const toolCall of responseMessage.tool_calls) {
const result = await callFunction(toolCall, token);
// 将工具调用结果加入对话历史
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: result,
});
}
// 使用工具调用结果再次调用模型获取最终回复
const secondCompletion = await openai.chat.completions.create({
model: MODEL,
messages,
});
const finalMessage = secondCompletion.choices[0].message;
return finalMessage.content;
} else {
// 如果没有工具调用,直接返回AI的回复,同样的问题,模型可能会有不同的回答,但是不会再调用工具
if (responseMessage.content) {
return responseMessage.content;
} else {
return "没有内容回复";
}
}
}
app.post("/questioning-ssm", (req, res: any) => {
console.log("请求数据", req.body);
let content = req.body.content;
let token = req.body.token;
// 运行对话流程
runConversation(content, token)
.then((result) => {
res.setHeader("Content-Type", "application/json; charset=utf-8");
// 返回 JSON 数据
res.send(JSON.stringify({ message: result }));
// res.send(result);
})
.catch((error) => {
console.error("发生错误:", error);
process.exit(1);
});
});
app.listen(3043, () => {
console.log("示例应用正在监听 3043 端口!");
});
其中
args: ["E:/template-mcp/mall-mcp-server/build/index.js"],
表示mall-mcp-server打包后的主入口文件,apiKey我用的是通义千问的,这个可以到通义千问官网自行申请获取。
mall-mcp-server项目
由于该项目是通过对外提供入口文件的方式,只需要打包项目即可,无需运行
下载依赖
npm install
打包项目
npm run build
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
getGoodListTool,
geRandListTool,
getShangChengTool,
getModelInfoTool,
getContactTool,
getCreateProductTool,
delProductTool,
getCategoryTool,
getSaleDateToole,
} from "./tools/index";
import { z } from "zod";
// 创建 server instance
const server = new McpServer({
name: "weather",
version: "1.0.0",
capabilities: {
resources: {},
tools: {},
},
});
// Helper function 用于发送 NWS API 请求
server.tool("get-good-list", "获取到产品列表", {}, async () => {
let infoList: any = await getGoodListTool();
return infoList;
});
// Helper function 用于发送 NWS API 请求
server.tool(
"get-rand-list",
"获取销售列表",
{
token: z.string().describe("用户token"),
num: z.string().describe("前多少名销售排行"),
},
async ({ num, token }) => {
let data = {
num,
token,
};
let infoList: any = await geRandListTool(data);
return infoList;
}
);
// Helper function 用于发送 NWS API 请求
server.tool("get-kefu-info", "获取模型信息", {}, async () => {
let infoList: any = await getModelInfoTool();
return infoList;
});
server.tool("get-shangcheng-info", "获取主营信息", {}, async () => {
let infoList: any = await getShangChengTool();
return infoList;
});
server.tool("get-contact-info", "获取联系方式", {}, async () => {
let infoList: any = await getContactTool();
return infoList;
});
server.tool(
"create-product-tool",
"创建商品",
{
discount: z
.string()
.describe("折扣:无折扣为1,9折为0.9,8折为0.8,依次类推"),
name: z.string().describe("商品名称"),
description: z.string().describe("商品描述"),
categoryId: z.string().describe("商品分类id"),
imgs: z.string().describe("商品封面"),
categoryList: z.array(
z
.object({
value: z.string().describe("规格名称"),
price: z.string().describe("该商品规格对应的价格"),
store: z.string().describe("商品规格对应的库存"),
})
.describe("产品规格对应的信息数组")
),
},
async ({ discount, name, description, categoryId, imgs, categoryList }) => {
let categoryArr: any = await getCategoryTool();
let _categoryId = "";
if (isNaN(Number(categoryId))) {
let _data = categoryArr.filter((item: any) => {
return item.name.includes(categoryId);
});
if (_data.length > 0) {
_categoryId = _data[0].id;
}
} else {
_categoryId = categoryId;
}
let data = {
discount,
name,
description,
categoryId: _categoryId,
imgs,
categoryList,
};
let infoList: any = await getCreateProductTool(data);
return infoList;
}
);
server.tool(
"del-product-tool",
"通过is删除商品",
{
id: z.string().describe("需要删除的产品id"),
},
async ({ id }) => {
let infoList: any = await delProductTool(id);
return infoList;
}
);
// getSaleDateToole
server.tool(
"get-sale-date-tool",
"获取到销售情况",
{
token: z.string().describe("权限token"),
},
async ({ token }) => {
let infoList: any = await getSaleDateToole(token);
return infoList;
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
其中引入的例如getGoodListTool为查询产品列表后返回的数据,该返回格式为:
{
content: [
{
type: "text",
text: "未能检索警报数据",
},
]
}
具体详情可下载项目代码自行观看。
mall-backend
这个不说了 ,就是springboot项目,用来为mcp提供数据库数据。怎么运行springboot项目网上搜就行了
五:测试情况



六:开源项目下载地址
以下为本例子的gitee下载地址,可自行下载,如果觉得这个开源小项目对你学习mcp有帮助,可以给个star!!!在此感谢!
更多推荐



所有评论(0)