項目結構
前端項目--------->MCP Client----------->MCP Server
server就不過多贅述了,他只是相當于添加了多個的tools
鏈接前后端
http.createServer創建一個服務器
// ----------------------------------------------------------------
// server.js
import http from "http";
import url from "url";
// ---------------------------------------------------------------- const server = http.createServer(async (req: any, res: any) => {const parsedUrl = url.parse(req.url, true); // 解析 url(含 query 參數)const name = parsedUrl.query.name; // ? 獲取 ?name=xxx 的值res.setHeader("Access-Control-Allow-Origin", "*"); // 允許跨域res.setHeader("Content-Type", "application/json; charset=utf-8");// 簡單路由匹配if (req.method === "GET" && parsedUrl.pathname === "/api/hello") {console.log("收到請求參數 name =", name);const ans = await mcpClient.processQuery(name as string);console.log("AI 回復:", ans);res.end(JSON.stringify({ message: ans }));} else {res.statusCode = 404;res.end(JSON.stringify({ error: "接口未找到" }));}});// ----------------------------------------------------------------// 啟動服務器server.listen(3002, () => {console.log("🚀 本地服務器啟動成功:http://127.0.0.1:3002");});
如何區分多個Server
通過獲取配置的server,來進行循環,解構出來所有的tools并且綁定對應的serverName來區分屬于哪個Server
const paths = [{name: "mcp",args: "/Users/v-yangziqi1/Desktop/work/mcpserver/mcp/build/index.js",},{name: "mcp1",args: "/Users/v-yangziqi1/Desktop/work/mcpserver/mcp1/build/index.js",},
];// 初始化mcp server連接async connectToServer(serverScriptPath?: string) {if (!serverScriptPath) return;const res = await Promise.all(paths.map((item, index) => {this.mcpServer.push(new Client({ name: "mcp-client-cli-" + item.name, version: "1.0.0" }));const mcp = this.mcpServer[index]; // 創建 MCP Client 實例// 判斷文件類型const state = item.args.endsWith(".py")? process.platform === "win32"? "python": "python3": process.execPath;// 建立mcp傳輸層const transport = new StdioClientTransport({command: state,args: [item.args],});mcp.connect(transport); // connect 方法用于連接到 MCP Server。// 獲取 MCP Server 上的所有工具名稱return mcp.listTools();}));const nestedArr = res.map(({ tools }, index) => {return tools.map((item) => ({ ...item, serverName: index })); // serverName是服務器的index所引致});const toolsArr = nestedArr.flat();// console.log("obj", JSON.stringify(toolsArr, null, 2));// 生成工具數組this.tools = toolsArr.map((t) => ({name: t.name, // 工具名稱description: t.description || "", // 工具描述parameters: t.inputSchema, // 工具參數定義Schema的格式execute: async (args: any) => {// 執行工具的邏輯const result = await this.mcpServer[t.serverName].callTool({name: t.name,arguments: args,});return result.content as string;},}));console.log("已連接 MCP Server,工具:",this.tools.map((t) => t.name).join(", "));}
完整代碼
// mcp-client-cli.ts
import axios from "axios"; // 引入axios用于發送HTTP請求
import readline from "readline/promises"; // 獲取用戶的輸入命令
import dotenv from "dotenv"; // 用于加載環境變量配置文件(.env)
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; // MCP Client 的核心類
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; // StdioClientTransport 是 MCP Client 的傳輸層,// ----------------------------------------------------------------
// server.js
import http from "http";
import url from "url";
// ----------------------------------------------------------------dotenv.config();const QIHOOGPT_KEY = process.env.QIHOOGPT_KEY;
if (!QIHOOGPT_KEY) throw new Error("請在 .env 中配置 QIHOOGPT_KEY");interface ToolCall {id: string;function: {name: string;arguments: string;};
}interface ToolDefinition {name: string;description: string;parameters: Record<string, any>;execute: (args: any) => Promise<string>;
}
const paths = [{name: "mcp",args: "/Users/v-yangziqi1/Desktop/work/mcpserver/mcp/build/index.js",},{name: "mcp1",args: "/Users/v-yangziqi1/Desktop/work/mcpserver/mcp1/build/index.js",},
];
class MCPClient {private model = "360gpt-pro";private tools: ToolDefinition[] = [];private mcpServer: Client[] = [];// 初始化mcp server連接async connectToServer(serverScriptPath?: string) {if (!serverScriptPath) return;const res = await Promise.all(paths.map((item, index) => {this.mcpServer.push(new Client({ name: "mcp-client-cli-" + item.name, version: "1.0.0" }));const mcp = this.mcpServer[index]; // 創建 MCP Client 實例// 判斷文件類型const state = item.args.endsWith(".py")? process.platform === "win32"? "python": "python3": process.execPath;// 建立mcp傳輸層const transport = new StdioClientTransport({command: state,args: [item.args],});mcp.connect(transport); // connect 方法用于連接到 MCP Server。// 獲取 MCP Server 上的所有工具名稱return mcp.listTools();}));const nestedArr = res.map(({ tools }, index) => {return tools.map((item) => ({ ...item, serverName: index })); // serverName是服務器的index所引致});const toolsArr = nestedArr.flat();// console.log("obj", JSON.stringify(toolsArr, null, 2));// 生成工具數組this.tools = toolsArr.map((t) => ({name: t.name, // 工具名稱description: t.description || "", // 工具描述parameters: t.inputSchema, // 工具參數定義Schema的格式execute: async (args: any) => {// 執行工具的邏輯const result = await this.mcpServer[t.serverName].callTool({name: t.name,arguments: args,});return result.content as string;},}));console.log("已連接 MCP Server,工具:",this.tools.map((t) => t.name).join(", "));}// 將工具定義轉換為模型可以理解的格式private convertTool(tool: ToolDefinition) {return {type: "function",function: {name: tool.name,description: tool.description,parameters: tool.parameters,},};}// 360模型的方法private async call360GPT(payload: any) {const { data } = await axios.post("https://api.360.cn/v1/chat/completions",{ ...payload, stream: false },{headers: {"Content-Type": "application/json",Authorization: `Bearer ${QIHOOGPT_KEY}`,},});return data;}// 處理用戶查詢// 該方法會將用戶的查詢發送到360GPT模型,并處理返回的async processQuery(query: string) {const messages: any[] = [{ role: "user", content: query }];// 發送用戶消息和mcp server的tools到360GPT模型const res = await this.call360GPT({model: this.model, // 模型名稱messages, // 用戶消息tools: this.tools.map((t) => this.convertTool(t)), // 工具列表tool_choice: "auto", // 自動選擇工具});const choice = res.choices[0]; // 獲取模型返回的第一個選擇const toolCalls = choice.message.tool_calls as ToolCall[] | undefined; // 獲取工具調用列表console.log(choice);console.log(toolCalls);// 如果有工具調用,則執行工具if (toolCalls?.length) {console.log("工具調用列表:", toolCalls);for (const call of toolCalls) {// 在 tools 數組中查找與調用名稱匹配的工具const tool = this.tools.find((t) => t.name === call.function.name);if (!tool) continue; // 如果沒有找到對應的工具,跳過const args = JSON.parse(call.function.arguments || "{}"); // 解析工具調用的參數const result = await tool.execute(args); // 執行工具并獲取結果messages.push({ role: "user", content: result }); // 將工具結果添加到消息中console.log(messages);// 再次調用360GPT模型,傳入更新后的消息進行潤色console.log("🤖 正在思考...");const final = await this.call360GPT({ model: this.model, messages });// 如果有工具調用結果,則返回最終的內容return final.choices[0].message.content;}}return choice.message.content;}// 聊天循環方法async chatLoop() {// 創建 readline 接口,用于讀取用戶輸入const rl = readline.createInterface({input: process.stdin,output: process.stdout,});console.log("輸入內容,輸入 quit 退出:");while (true) {const query = await rl.question("請輸入: "); // 提示用戶輸入查詢內容if (query.toLowerCase() === "quit") break;console.log("🤖 正在思考...");const ans = await this.processQuery(query);console.log("\nAI:", ans, "\n");}rl.close();}
}(async () => {const mcpClient = new MCPClient();const scriptArg = process.argv[2];// 初始化if (scriptArg) await mcpClient.connectToServer(scriptArg);// 服務器監聽// await mcpClient.chatLoop();// process.exit(0);// ----------------------------------------------------------------// server.js// 創建一個簡單的服務器const server = http.createServer(async (req: any, res: any) => {const parsedUrl = url.parse(req.url, true); // 解析 url(含 query 參數)const name = parsedUrl.query.name; // ? 獲取 ?name=xxx 的值res.setHeader("Access-Control-Allow-Origin", "*"); // 允許跨域res.setHeader("Content-Type", "application/json; charset=utf-8");// 簡單路由匹配if (req.method === "GET" && parsedUrl.pathname === "/api/hello") {console.log("收到請求參數 name =", name);const ans = await mcpClient.processQuery(name as string);console.log("AI 回復:", ans);res.end(JSON.stringify({ message: ans }));} else {res.statusCode = 404;res.end(JSON.stringify({ error: "接口未找到" }));}});// ----------------------------------------------------------------// 啟動服務器server.listen(3002, () => {console.log("🚀 本地服務器啟動成功:http://127.0.0.1:3002");});
})();
驗證是否成功
已經獲取到兩個倉庫的參數了
trae的倉庫
開始調用
這樣就稱得上是成功了