最小闭环manus,langchainjs+mcp-client+mcp-server

  1. 什么是mcp(modelcontextprotocol)
  2. mcp和manus的关系
  3. function calling
  4. mcp server
  5. mcp client
  6. 结语

1. 什么是MCP(Model Context Protocol)?

Model Context Protocol(模型上下文协议)是由Anthropic于2024年11月推出的开放标准,旨在建立大型语言模型(LLM)与外部数据源/工具间的统一通信协议。其核心价值在于:

  • 协议标准化:定义LLM与外部系统交互的通用接口规范
  • 上下文管理:支持动态维护对话/会话上下文状态
  • 工具集成:通过声明式API集成数据库、API服务、业务系统等
  • 性能优化:支持异步调用、批量处理等高级特性

与传统手工编写prompt的方式相比,MCP通过结构化协议实现:上下文状态自动管理(减少token浪费)、工具发现机制(动态加载功能)、错误重试策略(提升可靠性)等优势。

2. MCP和Manus的关系

Manus是首个将MCP协议成功商业化的AI应用平台(2025年初推出),其爆火直接推动了MCP的普及。二者的关系表现为:

  • 技术验证:Manus验证了MCP在大规模商业场景中的可行性
  • 生态共建:Manus贡献了20+官方适配器(数据库/云服务等)
  • 协议演进:Manus的实战经验反哺MCP协议迭代(如新增流式响应支持)
  • 市场教育:通过Manus的案例展示,企业更易理解MCP的价值

3. Function Calling的演进

MCP对传统Function Call机制的增强:

特性 传统方式 MCP方案
调用方式 同步单次调用 支持异步/批量调用
错误处理 简单重试 策略化重试+熔断机制
工具发现 静态预定义 动态注册/版本管理
权限控制 应用层实现 协议级OAuth2.0集成
监控指标 自定义埋点 标准化遥测数据输出

示例:通过MCP批量处理10个API调用,系统会自动优化为并行执行+统一结果聚合。

4. MCP Server

javascript 复制代码
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
    CallToolRequestSchema,
    ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { JSDOM } from "jsdom";
const server = new Server(
  {
    name: "example-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);
const WEATHER_TOOL = {
  name: "weather_search",
  description: "Can be used to query the weather",
  inputSchema: {
    type: "object",
    properties: {
      province: {
        type: "string",
        description: "Check the weather in the province",
      },
      city: {
        type: "string",
        description: "Check the weather for city",
      },
    },
    required: ["query"],
  },
};

const WIKI_TOOL = {
  name: "wiki_search",
  description: "It can be used to query the knowledge of a single keyword",
  inputSchema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "Query information",
      },
    },
    required: ["query"],
  },
};

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [WEATHER_TOOL, WIKI_TOOL],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  let res;
  switch (name) {
    case "weather_search":
      const { province, city } = args;
      res = await fetch(`https://uapis.cn/api/weather?name=${city}`);
      const data = await res.json();
      if (data.code !== 200) {
        return {
          content: [
            { type: "text", text: `查询天气失败,错误码:${data.code}` },
          ],
          isError: true,
        };
      }

      return {
        content: [{ type: "text", text: `查询到天气:${JSON.toString(data)}` }],
        isError: false,
      };
    case "wiki_search":
      if (!args.query) {
        return {
          content: [
            { type: "text", text: "The query parameter query is missing" },
          ],
          isError: true,
        };
      }
      const { query } = args;
      res = await fetch(`https://baike.baidu.com/item/${query}`);
      const info = await res.text();
      return {
        content: [{ type: "text", text: extractTextFromClasses(info) }],
        isError: false,
      };
  }
});

function extractTextFromClasses(htmlStr) {
  const classPrefix = "mainContent_";
  // 创建一个新的 JSDOM 实例
  const dom = new JSDOM(htmlStr);
  // 获取 window 对象,以便访问 document
  const { window } = dom;
  // 使用 document.querySelectorAll 来选择所有匹配的元素
  const elements = window.document.querySelectorAll(
    `[class^="${classPrefix}"]`
  );
  let allText = "";

  elements.forEach((element) => {
    // 使用 element.textContent 来获取元素及其所有子节点的文本内容
    allText += element.textContent.trim() + "\n";
  });

  return allText.trim(); // 返回所有文本内容,并去除末尾的换行符
}

async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server running on stdio");
}
runServer().catch((error) => {
  console.error("Fatal error running server:", error);
  process.exit(1);
});

关键组件

  • 天气查询:支持模型在线查询天气
  • wiki查询:支持模型在线提取知识

5. MCP Client

  • langchainjs+mcp-client实现ReAct Agent基本结构
javascript 复制代码
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { config } from "dotenv";

// 加载环境变量
config();

// 确保设置了百炼API密钥
if (!process.env.DASHSCOPE_API_KEY) {
  console.error("错误: 请在.env文件中设置DASHSCOPE_API_KEY环境变量");
  process.exit(1);
}

// 创建阿里通义千问模型实例
const llm = new ChatOpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  modelName: "qwen-plus", // 可以根据需要选择不同的模型
  configuration: {
    baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  },
});

const transport = new StdioClientTransport({
  command: "node",
  args: ["mcp-server.js"],
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

async function main() {
  await client.connect(transport);
  const tools = await client.listTools();
  console.log("工具集", tools.tools);
  const mTools =tools.tools.map((tl) =>
    tool(
      async (toolInput) => {
        const result = await client.callTool({
          name: tl.name,
          arguments: toolInput,
        });
        return result.content;
      },
      {
        ...tl,
        schema: tl.inputSchema,
      }
    )
  );

  const toolMap = new Map(mTools.map((t) => [t.name, t]));
  const llmWithTools = llm.bind({
    tools: mTools,
  });
  const messages = [new HumanMessage("查询下山东省济南市的天气")];
  let response = await llmWithTools.invoke(messages);
  messages.push(response);

  while (response.tool_calls && response.tool_calls.length > 0) {
    console.log("工具被调用:", JSON.stringify(response.tool_calls, null, 2));

    for (const toolCall of response.tool_calls) {
      const toolResult = await toolMap.get(toolCall.name).call(toolCall.args);
      console.log("工具结果: ", toolResult);
      messages.push(
        new ToolMessage({
          content: toolResult,
          tool_call_id: toolCall.id,
        })
      );
    }

    console.log("将工具结果发送回模型...");
    response = await llmWithTools.invoke(messages);
    messages.push(response);
  }
  console.log("最终模型回答:", response.content);
}

main()
  • 响应结果
shell 复制代码
demo % node mcp
node mcp
MCP Server running on stdio
工具集 [
  {
    name: 'weather_search',
    description: 'Can be used to query the weather',
    inputSchema: { type: 'object', properties: [Object], required: [Array] }
  },
  {
    name: 'wiki_search',
    description: 'It can be used to query the knowledge of a single keyword',
    inputSchema: { type: 'object', properties: [Object], required: [Array] }
  }
]
工具被调用: [
  {
    "name": "weather_search",
    "args": {
      "city": "济南市",
      "province": "山东省"
    },
    "type": "tool_call",
    "id": "call_d43959bd6cbd40c880d976"
  }
]
工具结果:  [ { type: 'text', text: '查询到天气:[object JSON]' } ]
将工具结果发送回模型...
最终模型回答: 山东省济南市的天气情况如下:

- 温度:20℃
- 天气状况:多云
- 风速:微风
- 空气湿度:69%
  • 以上案例仓库地址

github.com/koujialong/...

6. 结语

MCP正在重塑LLM应用开发范式,其价值已在金融、电商、IoT等领域得到验证。未来mcp生态的构建可能成为大模型落地的重要一环。

相关推荐
搬砖-无恙11 分钟前
vue uniapp里照片多张照片展示
前端·vue.js·uni-app
菜又爱编程12 分钟前
【uni-app运行错误】SassError: expected selector @import “@/uni.scss“;
前端·uni-app·scss
草明19 分钟前
使用 Chrome Flags 设置(适用于 HTTP 站点开发)
前端·chrome·http
愚昧之山绝望之谷开悟之坡21 分钟前
共享内存shm_size和内存锁ulimits.memlock配置
人工智能·笔记
真诚的灰灰1 小时前
Bench2Drive:面向闭环端到端自动驾驶的多能力基准测试
人工智能·机器学习·自动驾驶
SZ07711 小时前
AI战略家:AI驱动的政府治理现代化:重构问题识别、决策与监督的范式
人工智能
Python大数据分析@1 小时前
如何用Deepseek制作流程图?
人工智能·流程图·ai编程
Tz一号1 小时前
前端 git规范-不同软件(GitHub、Sourcetree、WebStorm)、命令行合并方式下增加 --no-ff的方法
前端·git·github
Loadings1 小时前
MCP从理解到实现
前端·cursor·ai 编程
冬冬小圆帽1 小时前
防止手机验证码被刷:React + TypeScript 与 Node.js + Express 的全面防御策略
前端·后端·react.js·typescript