UI + MCP Client + MCP Server(并且链接多个Server)

发布于:2025-07-10 ⋅ 阅读:(24) ⋅ 点赞:(0)

在这里插入图片描述

项目结构

在这里插入图片描述
前端项目--------->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的仓库
在这里插入图片描述

开始调用

这样就称得上是成功了
在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到