最小闭环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生态的构建可能成为大模型落地的重要一环。

相关推荐
刘晓倩8 分钟前
扣子Coze中的触发器实现流程自动化-实现每日新闻卡片式推送
人工智能·触发器·coze
徐礼昭|商派软件市场负责人26 分钟前
商派小程序商城(小程序/官网/APP···)的范式跃迁与增长再想象
人工智能·小程序·商城系统·商派oms
大志说编程37 分钟前
LangChain框架入门09:什么是RAG?
人工智能·langchain
ezl1fe37 分钟前
RAG 每日一技(十四):化繁为简,统揽全局——用LangChain构建高级RAG流程
人工智能·后端·算法
天才熊猫君43 分钟前
npm 和 pnpm 的一些理解
前端
飞飞飞仔44 分钟前
从 Cursor AI 到 Claude Code AI:我的辅助编程转型之路
前端
qb1 小时前
vue3.5.18源码:调试方式
前端·vue.js·架构
一碗白开水一1 小时前
【第6话:相机模型2】相机标定在自动驾驶中的作用、相机标定方法详解及代码说明
人工智能·数码相机·自动驾驶
The moon forgets1 小时前
Occ3D: A Large-Scale 3D Occupancy Prediction Benchmark for Autonomous Driving
人工智能·pytorch·深度学习·目标检测·3d
Spider_Man1 小时前
缓存策略大乱斗:让你的页面快到飞起!
前端·http·node.js