在 CommonJS 项目中加载 ESM-only 的 @mcp-use/client:动态 import() 实战指南
2026/9/24 16:15:30 网站建设 项目流程

在 CommonJS 项目中加载 ESM-only 的 @mcp-use/client:动态 import() 实战指南

【免费下载链接】mcp-useThe fullstack MCP framework to develop MCP Apps for ChatGPT / Claude & MCP Servers for AI Agents.项目地址: https://gitcode.com/gh_mirrors/mc/mcp-use

本篇指南围绕 mcp-use 仓库中 CommonJS 宿主加载示例 展开,讲解如何在.cjs/ CommonJS 环境中使用以 ESM 形式发布的@mcp-use/client包。你将掌握动态import()的标准写法、HTTP 与 stdio 两种 MCP Server 连接的启动方式,以及 Node 与浏览器环境下 OAuth 导出的差异,从而在传统 CJS 工程中顺畅接入 mcp-use v2 的 MCP 客户端。

为什么 @mcp-use/client 是 ESM-only

@mcp-use/client是 mcp-use v2 框架的官方 MCP 客户端,负责与 Model Context Protocol 服务器建立连接、管理会话与 OAuth 鉴权。它从设计上就只提供 ESM 构建产物,不提供 CommonJS(CJS)构建,因此不能用require("@mcp-use/client")直接加载。

这一点可以从包配置中得到直接印证。查看 packages/client/package.json:

  • 顶层声明"type": "module",整个包按 ESM 语义解析;
  • exports字段中每个条件的产物类型都只有"import"入口(./dist/index.js./dist/index-browser.js等),不存在"require"条件,Node 在解析require()时无法命中任何构建产物。

这是许多现代 npm 包的常见发布策略:ESM 便于静态分析、tree-shaking 和顶层 await,代价则是传统 CommonJS 宿主需要借助异步加载手段。Node 官方对 ESM 与 CJS 互操作的支持路径是:CommonJS 文件可以通过动态import()加载 ESM 模块,而require()仅能同步加载 CJS 模块,无法加载纯 ESM 包。

核心模式:用动态 import() 代替 require

在 CommonJS(.cjs"type": "commonjs".js)文件中加载@mcp-use/client的标准做法,是把import调用放在async函数内部,并解构出所需导出:

async function main() { const { MCPClient } = await import("@mcp-use/client"); const client = new MCPClient({ mcpServers: { demo: { url: "http://127.0.0.1:3102/mcp" }, }, }); const connection = await client.connect("demo"); console.log(await connection.listTools()); await client.close(); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

这个模式有三个关键点:

  1. await import(...)返回的是模块命名空间对象,从中解构MCPClient构造函数,用法与 ESM 中的静态import { MCPClient } from "@mcp-use/client"完全一致;
  2. 动态 import 是异步的,因此main()必须声明为async,并在入口处用.catch()兜底,避免未处理的 Promise 拒绝;
  3. 只加载一次:Node 会对 ESM 模块进行缓存,多次调用import()不会重复执行模块副作用,其./telemetry/configure-node.js等启动逻辑只会运行一次(该副作用在 packages/client/src/index.ts 中可见)。

完整可运行示例:commonjs_example.cjs

仓库在 examples/browser/commonjs/commonjs_example.cjs 提供了开箱即用的完整示例,展示了比最小模式更完整的生命周期:连接、列出工具、调用工具、关闭连接、错误处理。

async function runCommonJSExample() { const { MCPClient } = await import("@mcp-use/client"); console.log("=== CommonJS MCP Example ===\n"); const useStdio = process.env.USE_STDIO_EVERYTHING === "1"; const url = process.env.MCP_SERVER_URL ?? "http://127.0.0.1:3102/mcp"; const client = new MCPClient({ mcpServers: useStdio ? { everything: { command: "npx", args: ["-y", "@modelcontextprotocol/server-everything"], }, } : { demo: { url }, }, }); try { const name = useStdio ? "everything" : "demo"; const connection = await client.connect(name); console.log("✓ Connected", `(era=${connection.protocolEra ?? "?"})`); const tools = await connection.listTools(); console.log(`✓ Found ${tools.length} tools`); for (const tool of tools.slice(0, 5)) { console.log(` - ${tool.name}`); } if (tools.some((t) => t.name === "echo")) { const result = await connection.callTool("echo", { message: "cjs" }); console.log("✓ echo ->", JSON.stringify(result.content)); } console.log("\n=== CommonJS Example Completed Successfully ==="); } catch (error) { console.error("Error:", error.message); process.exitCode = 1; } finally { await client.close(); } } runCommonJSExample().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

该示例展示了几个值得复用的工程细节:

  • 环境变量驱动的连接配置MCP_SERVER_URL指定 HTTP 端点,USE_STDIO_EVERYTHING=1切换到 stdio 服务器,二者互斥且可覆盖默认值;
  • connection.protocolEra:mcp-use 客户端会自动协商传统会话式(legacy)与无会话(sessionless)两种 MCP 服务器时代(SDK 能力在 packages/client/src/index.ts 中描述为"自动协商 legacy sessionful 与 modern sessionless MCP servers"),此处打印出协商结果便于观测;
  • finally中关闭连接:无论成功失败都调用await client.close()释放资源,避免进程悬挂;
  • 错误码传递:失败时设置process.exitCode = 1,方便 CI 等自动化场景捕获失败。

运行方式:HTTP 与 stdio 两种服务器

方式一:连接本地 HTTP 演示服务器

仓库在 examples/_demo-servers 提供了两个最小演示服务器,分别模拟两代 Streamable HTTP 协议:

  • v1-http.ts:legacy 时代(2025 Streamable HTTP)服务器,PORT=3101 pnpm v1启动,提供echoadd两个工具;
  • v2-http.ts:mcp-use v2 无会话 Streamable HTTP 服务器,PORT=3102 pnpm v2启动,同样提供echoadd工具,且可通过LEGACY=reject强制拒绝旧协议请求。

examples/browser/commonjs/目录下分别执行:

# 先启动演示服务器:cd ../_demo-servers && PORT=3102 pnpm v2 MCP_SERVER_URL=http://127.0.0.1:3101/mcp node commonjs_example.cjs MCP_SERVER_URL=http://127.0.0.1:3102/mcp node commonjs_example.cjs

两条命令分别验证客户端与 v1、v2 两种协议时代的服务器握手——这正是connection.protocolEra发挥作用的地方。运行成功后应依次看到连接成功、工具数量与名称列表,以及echo工具调用返回的结果。

方式二:通过 stdio 连接 server-everything

如果不想先启动 HTTP 服务器,可以直接用 stdio 方式拉起 npm 生态中的@modelcontextprotocol/server-everything

USE_STDIO_EVERYTHING=1 node commonjs_example.cjs

此时示例内部会改用npx -y @modelcontextprotocol/server-everything作为子进程命令(见commonjs_example.cjs中的 stdio 配置分支),客户端通过标准输入输出与该进程通信,无需任何网络端口。这在本地快速验证、或者 CI 无端口环境下非常实用。

方式三:从 packages/client 目录直接运行

如果你已在本地构建过@mcp-use/client(执行过pnpm build),也可以从包目录直接运行示例:

node examples/browser/commonjs/commonjs_example.cjs

此时包解析会命中 packages/client/package.json 中exports["."].node.import指向的./dist/index.js,即 Node 专用入口。

关键注意事项

绝不要 require("@mcp-use/client")

如前所述,包没有 CJS 构建,require()会直接抛错(ERR_REQUIRE_ESM或模块解析失败)。判断某个包能否被require的快速方法:检查其package.jsonexports是否包含"require"条件——@mcp-use/client只有"import",明确不支持同步加载。

Node 与浏览器的 OAuth 导出不同

OAuth 相关辅助函数分环境导出:

  • Node 入口exports["."].node,即./dist/index.js)导出NodeOAuthClientProvider,以及createOAuthProvidercompleteOAuthFlowisOAuthInteractionRequiredisUnauthorized等(见 packages/client/src/auth/node.ts 与 packages/client/src/index.ts)。它支持完整的发现、动态客户端注册(DCR)、PKCE 回环回调与令牌持久化,仓库在 examples/node/auth/oauth-flow.ts 提供了无需外部身份提供商的端到端演示;
  • 浏览器 OAuth 提供方不会从 Node 入口导出:浏览器场景应使用浏览器构建(exports["."].browser/default指向./dist/index-browser.js)中对应的 OAuth 提供方,Node 构建中不存在该导出。

因此,如果你在 CommonJS 的 Node 宿主中需要 OAuth,应使用NodeOAuthClientProvider;需要浏览器侧 OAuth 时,请切换到浏览器入口并采用对应的浏览器 OAuth 提供方。

版本与运行环境前提

以当前仓库为准:@mcp-use/client版本为 2.3.2(见 packages/client/package.json),声明"engines": { "node": ">=22.22.2" },依赖@modelcontextprotocol/client@modelcontextprotocol/core@modelcontextprotocol/ext-apps(均为 2.0.0)。动态import()从 Node 12.17+ 即可使用,但请以包的 engines 声明为准选择运行环境;reactzod@e2b/code-interpreter为可选 peer 依赖,不使用对应能力时无需安装。

小结

在 CommonJS 宿主中使用 ESM-only 的@mcp-use/client,核心只有一条规则:require换成 async 函数内的动态import()。配合MCP_SERVER_URL/USE_STDIO_EVERYTHING环境变量即可灵活切换 HTTP 与 stdio 服务器;Node 与浏览器入口在 OAuth 导出上的差异则决定了鉴权代码应该写在哪个构建产物之上。将 commonjs_example.cjs 作为模板,你的 CJS 工程即可无缝接入 mcp-use v2 的完整 MCP 客户端能力。

【免费下载链接】mcp-useThe fullstack MCP framework to develop MCP Apps for ChatGPT / Claude & MCP Servers for AI Agents.项目地址: https://gitcode.com/gh_mirrors/mc/mcp-use

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询