webmcp-nexus-sdk 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,12 +30,13 @@
30
30
 
31
31
  `webmcp-nexus-sdk` 是 [WebMCP Nexus](https://github.com/alibaba/webmcp-nexus) 项目的运行时 SDK,围绕 [W3C WebMCP 标准提案](https://webmcp.org) 提供生产可用的 React 集成方案。
32
32
 
33
- 它只导出两个 API —— `registerGlobalTools` 和 `useWebMcpTools` —— 即可覆盖**全局**、**路由**、**组件**三种生命周期,让任何普通的 TypeScript 函数无需包装即可被 MCP 客户端(Claude Desktop、Cursor、VS Code 等)直接调用。
33
+ 它只导出三个 API —— `registerGlobalTools`、`useWebMcpTools` 和 `withWebMcpTools` —— 即可覆盖**全局**、**路由**、**组件**三种生命周期,让任何普通的 TypeScript 函数无需包装即可被 MCP 客户端(Claude Desktop、Cursor、VS Code 等)直接调用。
34
34
 
35
35
  ## 核心特性
36
36
 
37
- - 🪶 **极简 API** —— 仅 2 个函数即可完成所有注册场景。
37
+ - 🪶 **极简 API** —— 仅 3 个函数即可完成所有注册场景。
38
38
  - 🧩 **三级作用域** —— 全局工具、路由级工具、组件级工具自动随生命周期挂载 / 注销。
39
+ - 🏛️ **Class 组件支持** —— `withWebMcpTools` HOC 让 class 组件方法与函数组件 Hook 享受同等的自动注册体验。
39
40
  - 🌐 **跨浏览器兼容** —— Chrome 146+ 使用原生 `navigator.modelContext`;其他环境在首次注册时自动启用内置的 [`@mcp-b/webmcp-polyfill`](https://www.npmjs.com/package/@mcp-b/webmcp-polyfill),业务代码完全无感。
40
41
  - 🔁 **HMR 友好** —— 开发阶段修改函数签名后,工具 schema 会自动重新注册。
41
42
  - 🛡️ **冲突感知** —— 内置 scope ownership registry,多个 scope 注册同名工具时仅警告不阻断,注销严格隔离。
@@ -45,7 +46,7 @@
45
46
 
46
47
  | 维度 | 业内常见做法 | webmcp-nexus-sdk |
47
48
  | ---------- | ------------------------------------ | ---------------------------------------------------- |
48
- | API 表面 | 装饰器 / 包装函数 / 显式 schema 配置 | **2 个 API** 覆盖全部场景 |
49
+ | API 表面 | 装饰器 / 包装函数 / 显式 schema 配置 | **3 个 API** 覆盖全部场景 |
49
50
  | 函数侵入度 | `defineApi` / `createTool` 等包装 | **零侵入**——函数保持原样,原有调用方完全无感 |
50
51
  | 生命周期 | 仅支持全局注册,需手动维护 | 全局 / 路由 / 组件 **三级作用域**,组件卸载自动注销 |
51
52
  | 浏览器兼容 | 调用方自行判断 + 兜底 | SDK 内置 polyfill **惰性加载** |
@@ -122,6 +123,27 @@ export default function TasksPage() {
122
123
 
123
124
  组件卸载时同名工具会自动从 `modelContext` 注销,**避免 Agent 在错误的页面调用错误的工具**。
124
125
 
126
+ ### 4. Class 组件注册
127
+
128
+ ```tsx
129
+ import { withWebMcpTools } from 'webmcp-nexus-sdk';
130
+
131
+ class MyPanel extends React.Component {
132
+ /** 在面板中搜索 @readonly */
133
+ searchInPanel(params: { query: string }) { /* ... */ }
134
+
135
+ /** 清除搜索 */
136
+ clearSearch = (params: { confirm?: boolean }) => { /* ... */ };
137
+
138
+ render() { return <div />; }
139
+ }
140
+
141
+ export default withWebMcpTools(MyPanel);
142
+ // 或仅注册指定方法:withWebMcpTools(MyPanel, ['searchInPanel'])
143
+ ```
144
+
145
+ `withWebMcpTools` 必须作为最内层 HOC 直接包裹 class 组件。
146
+
125
147
  ## API 速览
126
148
 
127
149
  ### `registerGlobalTools(tools)`
@@ -150,6 +172,28 @@ function MyDialog() {
150
172
  }
151
173
  ```
152
174
 
175
+ ### `withWebMcpTools(Component, methodNames?)`
176
+
177
+ 高阶组件,将 class 组件的方法注册为 WebMCP 工具 —— mount 时注册、unmount 时注销。
178
+
179
+ ```tsx
180
+ import { withWebMcpTools } from 'webmcp-nexus-sdk';
181
+
182
+ class OrderPanel extends React.Component {
183
+ /** 搜索订单 @readonly */
184
+ searchOrders(params: { keyword: string }) { /* ... */ }
185
+
186
+ /** 导出订单 */
187
+ exportOrders = (params: { format: 'csv' | 'json' }) => { /* ... */ };
188
+
189
+ render() { return <div />; }
190
+ }
191
+
192
+ export default withWebMcpTools(OrderPanel);
193
+ ```
194
+
195
+ > **约束**:`withWebMcpTools` 必须作为最内层 HOC 直接包裹 class 组件。
196
+
153
197
  > 完整 API 文档与最佳实践参见 [仓库 README](https://github.com/alibaba/webmcp-nexus#readme)。
154
198
 
155
199
  ## 生态包
@@ -158,7 +202,7 @@ WebMCP Nexus 是一个 monorepo 工程化方案,下表是发布到 npm 的所
158
202
 
159
203
  | 包 | 用途 |
160
204
  | ------------------------------------------------------------------------------------------ | --------------------------------------------- |
161
- | **`webmcp-nexus-sdk`** (本包) | 运行时 SDK,提供 2 个核心 API |
205
+ | **`webmcp-nexus-sdk`** (本包) | 运行时 SDK,提供 3 个核心 API |
162
206
  | [`webmcp-nexus-core`](https://www.npmjs.com/package/webmcp-nexus-core) | 构建时核心:TS 类型抽取 + JSON Schema 生成 |
163
207
  | [`vite-plugin-webmcp-nexus`](https://www.npmjs.com/package/vite-plugin-webmcp-nexus) | Vite 构建插件 |
164
208
  | [`webpack-plugin-webmcp-nexus`](https://www.npmjs.com/package/webpack-plugin-webmcp-nexus) | Webpack 构建插件 |
package/dist/index.cjs CHANGED
@@ -21,12 +21,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  registerGlobalTools: () => registerGlobalTools,
24
- useWebMcpTools: () => useWebMcpTools
24
+ useWebMcpTools: () => useWebMcpTools,
25
+ withWebMcpTools: () => withWebMcpTools
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
 
28
29
  // src/registry.ts
29
30
  var modelContextPatched = false;
31
+ function isCallToolResult(value) {
32
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
33
+ const obj = value;
34
+ if (!Array.isArray(obj.content)) return false;
35
+ if (obj.content.length === 0) return true;
36
+ const first = obj.content[0];
37
+ return first !== null && typeof first === "object" && "type" in first && (first.type === "text" || first.type === "image" || first.type === "resource");
38
+ }
39
+ function normalizeToCallToolResult(value) {
40
+ if (isCallToolResult(value)) {
41
+ return value;
42
+ }
43
+ let text;
44
+ if (typeof value === "string") {
45
+ text = value;
46
+ } else {
47
+ try {
48
+ const serialized = JSON.stringify(value);
49
+ text = serialized === void 0 ? String(value) : serialized;
50
+ } catch {
51
+ text = String(value);
52
+ }
53
+ }
54
+ return {
55
+ content: [{ type: "text", text }],
56
+ isError: false
57
+ };
58
+ }
30
59
  function patchDispatchEventForCoalescing(mc) {
31
60
  if (mc.__toolchangeCoalesced) return;
32
61
  const originalDispatch = mc.dispatchEvent.bind(mc);
@@ -124,9 +153,10 @@ function patchModelContextEventSupport() {
124
153
  };
125
154
  }
126
155
  try {
127
- return JSON.parse(result);
156
+ const parsed = JSON.parse(result);
157
+ return normalizeToCallToolResult(parsed);
128
158
  } catch {
129
- throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);
159
+ throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 500)}`);
130
160
  }
131
161
  };
132
162
  }
@@ -494,9 +524,75 @@ function useWebMcpTools(...toolMaps) {
494
524
  };
495
525
  }, [toolKeys, localHmrVersion]);
496
526
  }
527
+
528
+ // src/withWebMcpTools.tsx
529
+ var import_react2 = require("react");
530
+ var import_jsx_runtime = require("react/jsx-runtime");
531
+ var import_meta2 = {};
532
+ var LIFECYCLE_METHODS = /* @__PURE__ */ new Set([
533
+ "constructor",
534
+ "render",
535
+ "componentDidMount",
536
+ "componentDidUpdate",
537
+ "componentWillUnmount",
538
+ "shouldComponentUpdate",
539
+ "getSnapshotBeforeUpdate",
540
+ "componentDidCatch"
541
+ ]);
542
+ function withWebMcpTools(WrappedComponent, methodNames) {
543
+ const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
544
+ function WebMcpToolsWrapper(props) {
545
+ const instanceRef = (0, import_react2.useRef)(null);
546
+ const [hmrVersion2, setHmrVersion] = (0, import_react2.useState)(0);
547
+ (0, import_react2.useEffect)(() => {
548
+ const hot = typeof import_meta2 !== "undefined" ? import_meta2.hot : void 0;
549
+ if (hot && typeof hot.on === "function") {
550
+ const handler = () => setHmrVersion((v) => v + 1);
551
+ hot.on("vite:afterUpdate", handler);
552
+ return () => {
553
+ if (typeof hot.off === "function") {
554
+ hot.off("vite:afterUpdate", handler);
555
+ }
556
+ };
557
+ }
558
+ }, []);
559
+ const toolMap = (0, import_react2.useMemo)(() => {
560
+ const map = {};
561
+ const candidates = methodNames ? new Set(methodNames) : null;
562
+ const proto = WrappedComponent.prototype;
563
+ for (const name of Object.getOwnPropertyNames(proto)) {
564
+ if (LIFECYCLE_METHODS.has(name)) continue;
565
+ if (candidates && !candidates.has(name)) continue;
566
+ const method = proto[name];
567
+ if (typeof method !== "function") continue;
568
+ const schema = method.__webmcpSchema;
569
+ if (!schema) continue;
570
+ const wrapper = (input) => instanceRef.current?.[name](input);
571
+ wrapper.__webmcpSchema = schema;
572
+ map[name] = wrapper;
573
+ }
574
+ const fieldSchemas = WrappedComponent.__webmcpFieldSchemas;
575
+ if (fieldSchemas) {
576
+ for (const [name, schema] of Object.entries(fieldSchemas)) {
577
+ if (candidates && !candidates.has(name)) continue;
578
+ if (map[name]) continue;
579
+ const wrapper = (input) => instanceRef.current?.[name](input);
580
+ wrapper.__webmcpSchema = schema;
581
+ map[name] = wrapper;
582
+ }
583
+ }
584
+ return map;
585
+ }, [hmrVersion2]);
586
+ useWebMcpTools(toolMap);
587
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WrappedComponent, { ref: instanceRef, ...props });
588
+ }
589
+ WebMcpToolsWrapper.displayName = `withWebMcpTools(${displayName})`;
590
+ return WebMcpToolsWrapper;
591
+ }
497
592
  // Annotate the CommonJS export names for ESM import in node:
498
593
  0 && (module.exports = {
499
594
  registerGlobalTools,
500
- useWebMcpTools
595
+ useWebMcpTools,
596
+ withWebMcpTools
501
597
  });
502
598
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/registry.ts","../src/polyfill.ts","../src/registerGlobalTools.ts","../src/useWebMcpTools.ts"],"sourcesContent":["// packages/webmcp-sdk/src/index.ts\nexport { registerGlobalTools } from './registerGlobalTools';\nexport { useWebMcpTools } from './useWebMcpTools';\nexport type { WebMcpToolFn, WebMcpToolSchema, WebMcpAnnotatedFn, WebMcpToolConfig } from './types';\n","// packages/webmcp-sdk/src/registry.ts\nimport type { WebMcpToolConfig } from './types';\n\nlet modelContextPatched = false;\n\n/**\n * 在 dispatchEvent 层面合并 toolchange 事件。\n * 无论来源(SDK 包装器、polyfill 内部、第三方代码),同一微任务内只触发 1 次。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction patchDispatchEventForCoalescing(mc: any): void {\n if (mc.__toolchangeCoalesced) return;\n const originalDispatch: (event: Event) => boolean =\n mc.dispatchEvent.bind(mc);\n let pending = false;\n mc.dispatchEvent = (event: Event): boolean => {\n if (event.type === 'toolchange') {\n if (!pending) {\n pending = true;\n queueMicrotask(() => {\n originalDispatch(new Event('toolchange'));\n pending = false;\n });\n }\n return true;\n }\n return originalDispatch(event);\n };\n mc.__toolchangeCoalesced = true;\n}\n\n/**\n * Patches navigator.modelContext to ensure embed.js can discover tools.\n *\n * Handles three scenarios:\n * 1. Chrome 146+ native / @mcp-b/webmcp-polyfill: modelContext lacks\n * listTools/callTool/EventTarget; modelContextTesting shim provides\n * listTools/executeTool\n * → Bridge listTools/callTool from modelContextTesting, add EventTarget,\n * wrap registerTool/unregisterTool to fire toolchange\n * 2. Older polyfills (e.g. @mcp-b/global before EventTarget support):\n * modelContext has listTools/callTool but lacks EventTarget\n * → Add EventTarget, wrap registerTool/unregisterTool to fire toolchange\n * 3. Full MCP-B environment: modelContext has everything → skip\n */\nexport function patchModelContextEventSupport(): void {\n if (modelContextPatched) {\n return;\n }\n if (\n typeof navigator === 'undefined' ||\n !('modelContext' in navigator) ||\n !navigator.modelContext\n ) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = navigator.modelContext as any;\n const hasEventTarget = typeof mc.addEventListener === 'function';\n const hasListTools = typeof mc.listTools === 'function';\n const hasCallTool = typeof mc.callTool === 'function';\n\n // Scenario 3: fully featured — only apply toolchange coalescing\n if (hasEventTarget && hasListTools && hasCallTool) {\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n return;\n }\n\n // Add EventTarget support if missing (Chrome native & polyfill)\n if (!hasEventTarget) {\n const listeners = new Map<string, Set<EventListener>>();\n\n mc.addEventListener = (type: string, callback: EventListener) => {\n if (!listeners.has(type)) listeners.set(type, new Set());\n listeners.get(type)!.add(callback);\n };\n\n mc.removeEventListener = (type: string, callback: EventListener) => {\n listeners.get(type)?.delete(callback);\n };\n\n mc.dispatchEvent = (event: Event): boolean => {\n const set = listeners.get(event.type);\n if (set) {\n set.forEach(fn => {\n try {\n fn.call(mc, event);\n } catch {\n // Ignore listener errors\n }\n });\n }\n return true;\n };\n }\n\n // Bridge listTools/callTool from modelContextTesting if missing (Chrome native)\n // Chrome 146+ has these methods on modelContextTesting but not on modelContext.\n // embed.js requires them on modelContext for getExtendedModelContext() to succeed.\n if (!hasListTools || !hasCallTool) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const testing = (navigator as any).modelContextTesting;\n if (testing) {\n if (\n typeof testing.listTools === 'function' &&\n !testing.__webmcpNexusListToolsPatched\n ) {\n const originalTestingListTools = testing.listTools.bind(testing);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n testing.listTools = (...args: any[]) => {\n const nativeTools = originalTestingListTools(...args);\n return Array.isArray(nativeTools)\n ? nativeTools.filter((tool: any) => {\n if (!managedToolNames.has(tool.name)) return true;\n return activeTools.has(tool.name);\n })\n : nativeTools;\n };\n testing.__webmcpNexusListToolsPatched = true;\n }\n if (!hasListTools && typeof testing.listTools === 'function') {\n // Convert testing format (inputSchema as JSON string) to extended format (inputSchema as object)\n mc.listTools = () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return testing.listTools().map((t: any) => ({\n name: t.name,\n description: t.description || '',\n inputSchema:\n typeof t.inputSchema === 'string' && t.inputSchema.length > 0\n ? JSON.parse(t.inputSchema)\n : { type: 'object', properties: {} },\n }));\n } catch {\n return [];\n }\n };\n }\n if (!hasCallTool && typeof testing.executeTool === 'function') {\n mc.callTool = async (params: { name: string; arguments?: Record<string, unknown> }) => {\n const result = await testing.executeTool(\n params.name,\n JSON.stringify(params.arguments || {}),\n );\n if (result === null) {\n return {\n isError: true,\n content: [{ type: 'text', text: 'Tool execution interrupted by navigation' }],\n };\n }\n try {\n return JSON.parse(result);\n } catch {\n throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);\n }\n };\n }\n }\n }\n\n // Wrap registerTool/unregisterTool to auto-fire 'toolchange'\n // For Chrome native: registerTool fires toolchange on modelContextTesting (not modelContext),\n // but embed.js subscribes on modelContext after our patch — so we need to bridge the event.\n // For polyfill: BrowserMcpServer doesn't fire any events, so wrapping is required.\n if (typeof mc.registerTool === 'function') {\n const originalRegisterTool = mc.registerTool.bind(mc);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mc.registerTool = (tool: any, options?: any) => {\n let registrationError: unknown;\n try {\n originalRegisterTool(tool, options);\n } catch (error) {\n registrationError = error;\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n if (registrationError) {\n throw registrationError;\n }\n };\n }\n\n if (typeof mc.unregisterTool === 'function') {\n const originalUnregisterTool = mc.unregisterTool.bind(mc);\n mc.unregisterTool = (name: string) => {\n try {\n originalUnregisterTool(name);\n } catch {\n // Swallow unregister errors\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n };\n }\n\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n}\n\ntype ToolScope = 'global' | 'route' | 'component';\n\ninterface ToolEntry {\n name: string;\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ToolOwner {\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ActiveToolRecord {\n name: string;\n config: WebMcpToolConfig;\n configs: Map<string, WebMcpToolConfig>;\n controller: AbortController;\n owners: Map<string, ToolOwner>;\n nativeRegistered: boolean;\n}\n\nconst registry = new Map<string, ToolEntry[]>();\nconst activeTools = new Map<string, ActiveToolRecord>();\nconst managedToolNames = new Set<string>();\nlet hasManagedTools = false;\n\nfunction getOwnerKey(owner: ToolOwner): string {\n return `${owner.scope}:${owner.scopeId}`;\n}\n\n/** 获取当前活跃工具名列表(仅用于测试和内部同步) */\nexport function getActiveToolNames(): string[] {\n return Array.from(activeTools.keys());\n}\n\n/** 获取当前活跃工具配置列表(供 pushToolsToWidget 在 listTools 缺失时兜底) */\nexport function getActiveToolConfigs(): Array<{\n name: string;\n description: string;\n inputSchema: object;\n}> {\n return Array.from(activeTools.values()).map(record => ({\n name: record.name,\n description: record.config.description,\n inputSchema: record.config.inputSchema || { type: 'object', properties: {} },\n }));\n}\n\n/**\n * 在内部注册表中记录工具的所有权信息。\n * 如果同名工具已被其他 scope 注册,输出警告但仍允许注册。\n */\nexport function registerEntry(name: string, scope: ToolScope, scopeId: string): void {\n const entries = registry.get(name) || [];\n if (entries.length > 0) {\n const isSameScopeAndId = entries.some(e => e.scope === scope && e.scopeId === scopeId);\n if (!isSameScopeAndId) {\n const existing = entries[0];\n console.warn(\n `[webmcp] Tool \"${name}\" is already registered by scope \"${existing.scope}:${existing.scopeId}\". ` +\n `Re-registering from \"${scope}:${scopeId}\". This may cause unexpected behavior.`,\n );\n }\n }\n entries.push({ name, scope, scopeId });\n registry.set(name, entries);\n}\n\n/**\n * 从内部注册表中移除指定 scope 的工具所有权记录。\n * @returns true 表示可以安全调用 unregisterTool(最后一个持有者已移除);\n * false 表示还有其他 scope 持有该工具,不应注销。\n */\nexport function unregisterEntry(name: string, scope: ToolScope, scopeId: string): boolean {\n const entries = registry.get(name);\n if (!entries) return false;\n const idx = entries.findIndex(e => e.scope === scope && e.scopeId === scopeId);\n if (idx === -1) return false;\n entries.splice(idx, 1);\n if (entries.length === 0) {\n registry.delete(name);\n return true; // 可以安全注销\n }\n return false; // 其他 scope 仍持有该工具\n}\n\n/**\n * 清空注册表(仅用于测试)\n */\nexport function clearRegistry(): void {\n registry.clear();\n activeTools.clear();\n managedToolNames.clear();\n hasManagedTools = false;\n modelContextPatched = false;\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n}\n\n/**\n * 注册带 owner 的工具。\n * 同名工具只会原生注册一次;多个组件/作用域通过 owners 聚合生命周期。\n */\nexport function registerScopedTool(toolConfig: WebMcpToolConfig, owner: ToolOwner): void {\n try {\n const ownerKey = getOwnerKey(owner);\n hasManagedTools = true;\n managedToolNames.add(toolConfig.name);\n const existing = activeTools.get(toolConfig.name);\n if (existing) {\n existing.owners.set(ownerKey, owner);\n existing.configs.set(ownerKey, toolConfig);\n return;\n }\n\n const controller = new AbortController();\n const owners = new Map<string, ToolOwner>([[ownerKey, owner]]);\n const record: ActiveToolRecord = {\n name: toolConfig.name,\n config: toolConfig,\n configs: new Map([[ownerKey, toolConfig]]),\n controller,\n owners,\n nativeRegistered: false,\n };\n\n activeTools.set(toolConfig.name, record);\n\n const mc =\n typeof navigator !== 'undefined'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any)\n : undefined;\n\n controller.signal.addEventListener(\n 'abort',\n () => {\n try {\n // L2 fallback: 仅当 modelContext 不是带原生 signal 处理的实现时才调用 unregisterTool。\n // - Chrome 148+ 原生:移除了 unregisterTool,typeof === 'function' 为 false,自动跳过\n // - Chrome 146/147 原生:registerTool 不识别 signal,必须靠 unregisterTool 注销\n // - @mcp-b/webmcp-polyfill 2.x:registerTool 内部已 hook signal abort 自动删除 tool,\n // 再调 unregisterTool 既是双重操作,又会触发 polyfill 的 deprecation 警告。\n // 通过 __isWebMCPPolyfill 标记跳过这条路径。\n if (\n mc &&\n typeof mc.unregisterTool === 'function' &&\n !mc.__isWebMCPPolyfill\n ) {\n mc.unregisterTool(toolConfig.name);\n }\n } catch {\n // Ignore legacy fallback errors\n }\n try {\n activeTools.delete(toolConfig.name);\n notifyToolsChanged();\n } catch {\n // Ignore notification errors\n }\n },\n { once: true },\n );\n\n if (mc && typeof mc.registerTool === 'function') {\n const nativeToolConfig = {\n ...toolConfig,\n execute: async (input: Record<string, unknown>) => {\n const latestRecord = activeTools.get(toolConfig.name);\n return latestRecord?.config.execute(input);\n },\n };\n try {\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n } catch {\n try {\n if (typeof mc.unregisterTool === 'function') {\n mc.unregisterTool(toolConfig.name);\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n }\n } catch {\n // Keep the internal mirror so widget fallback still reflects SDK ownership.\n }\n }\n }\n } catch {\n // SDK public paths should never surface browser API errors.\n }\n}\n\n/**\n * 移除工具 owner。只有最后一个 owner 移除时才 abort 原生注册。\n * @returns true 表示工具列表发生了实际注销;false 表示仍有其他 owner。\n */\nexport function unregisterScopedTool(name: string, owner: ToolOwner): boolean {\n try {\n const record = activeTools.get(name);\n if (!record) {\n return false;\n }\n\n const ownerKey = getOwnerKey(owner);\n const removedConfig = record.configs.get(ownerKey);\n record.owners.delete(ownerKey);\n record.configs.delete(ownerKey);\n if (removedConfig && record.config === removedConfig) {\n const nextConfig = record.configs.values().next().value as WebMcpToolConfig | undefined;\n if (nextConfig) {\n record.config = nextConfig;\n }\n }\n if (record.owners.size > 0) return false;\n\n if (!record.controller.signal.aborted) {\n record.controller.abort();\n } else {\n activeTools.delete(name);\n notifyToolsChanged();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Debounce timer for pushing tools to widget iframe.\n */\nlet pushToolsTimer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Directly push the current tool list to any widget iframes via postMessage.\n * This bypasses Chrome's registerToolsChangedCallback mechanism which doesn't\n * fire on unregisterTool, ensuring the relay always has the correct tool list.\n */\nfunction pushToolsToWidget(): void {\n try {\n if (typeof document === 'undefined') return;\n\n const iframes = document.querySelectorAll('iframe');\n if (iframes.length === 0) return;\n\n const tools = getToolsForWidget();\n\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\n // embed.js(@mcp-b/webmcp-local-relay)已通过 toolchange 监听自行推送\n // relay iframe,跳过以避免重复 webmcp.tools.changed 消息\n if (iframe.hasAttribute('data-webmcp-relay')) continue;\n try {\n if (iframe.contentWindow) {\n iframe.contentWindow.postMessage(\n {\n type: 'webmcp.tools.changed',\n tools,\n },\n '*',\n );\n }\n } catch {\n // Cross-origin or other iframe access error, skip\n }\n }\n } catch {\n // Ignore errors\n }\n}\n\nfunction getToolsForWidget(): Array<{ name: string; description: string; inputSchema: object }> {\n if (hasManagedTools) {\n return getActiveToolConfigs();\n }\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = typeof navigator !== 'undefined' ? (navigator.modelContext as any) : undefined;\n if (mc && typeof mc.listTools === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tools = mc.listTools().map((tool: any) => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema:\n typeof tool.inputSchema === 'string' && tool.inputSchema.length > 0\n ? safeJsonParse(tool.inputSchema)\n : tool.inputSchema || { type: 'object', properties: {} },\n }));\n return tools;\n }\n } catch {\n // Fall through to activeTools mirror\n }\n return getActiveToolConfigs();\n}\n\nfunction safeJsonParse(value: string): object {\n try {\n return JSON.parse(value);\n } catch {\n return { type: 'object', properties: {} };\n }\n}\n\n/**\n * Schedule a debounced push of tools to widget iframes.\n * Uses 100ms delay to coalesce rapid register/unregister cycles\n * (e.g., during SPA navigation when old component unmounts and new one mounts).\n */\nfunction schedulePushToolsToWidget(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n }\n pushToolsTimer = setTimeout(() => {\n pushToolsTimer = null;\n pushToolsToWidget();\n }, 100);\n}\n\n/**\n * 通知工具列表已变化,发射标准 toolchange 事件并推送至 widget iframe。\n */\nexport function notifyToolsChanged(): void {\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) return;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any).dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n\n schedulePushToolsToWidget();\n}\n\n/**\n * 安全注册工具,处理重复名称的情况。\n * 如果 registerTool 因重复名称抛错,则先 unregister 再重试。\n */\nexport function safeRegisterTool(toolConfig: WebMcpToolConfig): void {\n registerScopedTool(toolConfig, { scope: 'global', scopeId: 'safe-register' });\n}\n","// packages/webmcp-sdk/src/polyfill.ts\nimport { initializeWebMCPPolyfill, cleanupWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';\n\nlet attempted = false;\n\n/**\n * 惰性、幂等地确保 navigator.modelContext 存在。\n *\n * - 原生(Chrome 146+)/ 已被其他 polyfill 安装:直接返回,不动 navigator\n * - 非浏览器环境(SSR):直接返回\n * - 缺失 modelContext:调 initializeWebMCPPolyfill 装上严格 W3C 核心 + modelContextTesting shim\n *\n * 调用方应在判定 'modelContext' in navigator 之前调用本函数。整体包 try/catch,\n * polyfill 加载或初始化失败不向调用方传播异常——SDK 后续逻辑会按\"无 modelContext\"路径继续 no-op。\n */\nexport function ensureModelContextPolyfill(): void {\n if (attempted) return;\n attempted = true;\n if (typeof navigator === 'undefined') return;\n if ('modelContext' in navigator) return;\n try {\n initializeWebMCPPolyfill({ installTestingShim: true });\n } catch {\n // polyfill 初始化失败兜底\n }\n}\n\n/**\n * 仅供单元测试使用:重置模块级 attempted 标志并卸载 polyfill。\n * 生产代码不应调用本函数。\n */\nexport function __resetPolyfillStateForTest(): void {\n attempted = false;\n try {\n cleanupWebMCPPolyfill();\n } catch {\n // cleanup 失败兜底\n }\n}\n","// packages/webmcp-sdk/src/registerGlobalTools.ts\nimport {\n registerEntry,\n registerScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/**\n * 全局注册 WebMCP 工具。应用启动时调用一次。\n * 支持可变参数,兼容 import * as module 批量导入。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * import * as userApi from './api/user';\n * import * as productApi from './api/product';\n * registerGlobalTools(userApi, productApi);\n *\n * @example\n * registerGlobalTools({ getUser, searchUsers }, { searchProducts });\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function registerGlobalTools(...toolMaps: Record<string, Function>[]): void {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n try {\n patchModelContextEventSupport();\n\n let registeredCount = 0;\n\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n // 跳过非函数值(如 TypeScript 类型导出在运行时可能不存在)\n if (typeof fn !== 'function') continue;\n\n // __webmcpSchema 由 Vite 插件在构建时注入\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerEntry(name, 'global', 'app');\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n execute: async (input: unknown) => fn(input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'global', scopeId: 'app' },\n );\n registeredCount++;\n }\n }\n\n if (registeredCount > 0) {\n notifyToolsChanged();\n }\n } catch {\n // 全局兜底:SDK 入口不向调用方传播浏览器 API 异常\n }\n}\n","// packages/webmcp-sdk/src/useWebMcpTools.ts\nimport { useEffect, useRef, useState } from 'react';\nimport {\n registerScopedTool,\n unregisterScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\nlet scopeCounter = 0;\nfunction generateScopeId(): string {\n return `component-${++scopeCounter}`;\n}\n\n// HMR support: track a global version counter that increments on hot updates.\n// Function body updates are already handled via toolsRef.current indirect calls.\n// This counter ensures schema changes (__webmcpSchema) trigger re-registration.\nlet hmrVersion = 0;\nconst hmrHot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\nif (hmrHot && typeof hmrHot.on === 'function') {\n hmrHot.on('vite:afterUpdate', () => {\n hmrVersion++;\n });\n}\n\n/**\n * React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。\n * mount 时注册,unmount 时自动注销。\n *\n * 使用 useRef 持有最新函数引用,避免闭包陷阱。\n * 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,\n * 当工具集合变化时重新注册,函数体变化不触发重新注册。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * // 组件级注册\n * useWebMcpTools({ searchInPanel, clearSearch });\n *\n * @example\n * // 路由级注册(配合 React Router 使用)\n * useWebMcpTools({ setUserFilter });\n */\nexport function useWebMcpTools(...toolMaps: Record<string, Function>[]): void {\n // 合并所有 toolMap 为一个对象\n const merged: Record<string, Function> = {};\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n if (typeof fn === 'function') {\n merged[name] = fn;\n }\n }\n }\n\n // 用 ref 持有最新的函数引用,避免闭包陷阱\n const toolsRef = useRef(merged);\n toolsRef.current = merged;\n\n // 生成唯一 scopeId,确保同一组件实例的 scopeId 一致\n const scopeIdRef = useRef<string>('');\n if (!scopeIdRef.current) {\n scopeIdRef.current = generateScopeId();\n }\n\n // 工具名集合作为依赖 — 工具集合变化时重新注册\n const toolKeys = Object.keys(merged).sort().join(',');\n\n // DEV: listen for HMR updates to force re-registration when schema changes\n const [localHmrVersion, setLocalHmrVersion] = useState(hmrVersion);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setLocalHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n useEffect(() => {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n patchModelContextEventSupport();\n\n const registeredNames: string[] = [];\n\n for (const [name, fn] of Object.entries(toolsRef.current)) {\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n // 通过 ref 间接调用,保证始终执行最新版本的函数\n execute: async (input: unknown) => toolsRef.current[name](input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'component', scopeId: scopeIdRef.current },\n );\n\n registeredNames.push(name);\n }\n\n if (registeredNames.length > 0) {\n notifyToolsChanged();\n }\n\n // 组件卸载时释放 owner;最后一个 owner 才会触发原生 abort。\n return () => {\n let unregisteredAny = false;\n for (const name of registeredNames) {\n const shouldUnregister = unregisterScopedTool(name, {\n scope: 'component',\n scopeId: scopeIdRef.current,\n });\n if (shouldUnregister) {\n unregisteredAny = true;\n }\n }\n if (unregisteredAny) {\n notifyToolsChanged();\n }\n };\n }, [toolKeys, localHmrVersion]); // 工具集合变化或 HMR 更新时重新注册\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/registry.ts","../src/polyfill.ts","../src/registerGlobalTools.ts","../src/useWebMcpTools.ts","../src/withWebMcpTools.tsx"],"sourcesContent":["// packages/webmcp-sdk/src/index.ts\nexport { registerGlobalTools } from './registerGlobalTools';\nexport { useWebMcpTools } from './useWebMcpTools';\nexport { withWebMcpTools } from './withWebMcpTools';\nexport type { WebMcpToolFn, WebMcpToolSchema, WebMcpAnnotatedFn, WebMcpToolConfig } from './types';\n","// packages/webmcp-sdk/src/registry.ts\nimport type { WebMcpToolConfig } from './types';\n\nlet modelContextPatched = false;\n\n/**\n * 严格判断值是否为合法的 MCP CallToolResult。\n * 检查 content 数组存在且首元素含合法 type 字段,防止业务对象误判。\n */\nexport function isCallToolResult(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const obj = value as Record<string, unknown>;\n if (!Array.isArray(obj.content)) return false;\n if (obj.content.length === 0) return true;\n const first = obj.content[0];\n return (\n first !== null &&\n typeof first === 'object' &&\n 'type' in first &&\n ((first as Record<string, unknown>).type === 'text' ||\n (first as Record<string, unknown>).type === 'image' ||\n (first as Record<string, unknown>).type === 'resource')\n );\n}\n\n/**\n * 将工具原始返回值规范化为 MCP CallToolResult 格式。\n * 若已是合法 CallToolResult 则直接返回,否则将完整内容包装为 text content。\n * 注意:不对结果做任何截断,完整保留工具返回的全部数据。\n */\nexport function normalizeToCallToolResult(value: unknown): Record<string, unknown> {\n if (isCallToolResult(value)) {\n return value as Record<string, unknown>;\n }\n let text: string;\n if (typeof value === 'string') {\n text = value;\n } else {\n try {\n const serialized = JSON.stringify(value);\n text = serialized === undefined ? String(value) : serialized;\n } catch {\n text = String(value);\n }\n }\n return {\n content: [{ type: 'text', text }],\n isError: false,\n };\n}\n\n/**\n * 在 dispatchEvent 层面合并 toolchange 事件。\n * 无论来源(SDK 包装器、polyfill 内部、第三方代码),同一微任务内只触发 1 次。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction patchDispatchEventForCoalescing(mc: any): void {\n if (mc.__toolchangeCoalesced) return;\n const originalDispatch: (event: Event) => boolean =\n mc.dispatchEvent.bind(mc);\n let pending = false;\n mc.dispatchEvent = (event: Event): boolean => {\n if (event.type === 'toolchange') {\n if (!pending) {\n pending = true;\n queueMicrotask(() => {\n originalDispatch(new Event('toolchange'));\n pending = false;\n });\n }\n return true;\n }\n return originalDispatch(event);\n };\n mc.__toolchangeCoalesced = true;\n}\n\n/**\n * Patches navigator.modelContext to ensure embed.js can discover tools.\n *\n * Handles three scenarios:\n * 1. Chrome 146+ native / @mcp-b/webmcp-polyfill: modelContext lacks\n * listTools/callTool/EventTarget; modelContextTesting shim provides\n * listTools/executeTool\n * → Bridge listTools/callTool from modelContextTesting, add EventTarget,\n * wrap registerTool/unregisterTool to fire toolchange\n * 2. Older polyfills (e.g. @mcp-b/global before EventTarget support):\n * modelContext has listTools/callTool but lacks EventTarget\n * → Add EventTarget, wrap registerTool/unregisterTool to fire toolchange\n * 3. Full MCP-B environment: modelContext has everything → skip\n */\nexport function patchModelContextEventSupport(): void {\n if (modelContextPatched) {\n return;\n }\n if (\n typeof navigator === 'undefined' ||\n !('modelContext' in navigator) ||\n !navigator.modelContext\n ) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = navigator.modelContext as any;\n const hasEventTarget = typeof mc.addEventListener === 'function';\n const hasListTools = typeof mc.listTools === 'function';\n const hasCallTool = typeof mc.callTool === 'function';\n\n // Scenario 3: fully featured — only apply toolchange coalescing\n if (hasEventTarget && hasListTools && hasCallTool) {\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n return;\n }\n\n // Add EventTarget support if missing (Chrome native & polyfill)\n if (!hasEventTarget) {\n const listeners = new Map<string, Set<EventListener>>();\n\n mc.addEventListener = (type: string, callback: EventListener) => {\n if (!listeners.has(type)) listeners.set(type, new Set());\n listeners.get(type)!.add(callback);\n };\n\n mc.removeEventListener = (type: string, callback: EventListener) => {\n listeners.get(type)?.delete(callback);\n };\n\n mc.dispatchEvent = (event: Event): boolean => {\n const set = listeners.get(event.type);\n if (set) {\n set.forEach(fn => {\n try {\n fn.call(mc, event);\n } catch {\n // Ignore listener errors\n }\n });\n }\n return true;\n };\n }\n\n // Bridge listTools/callTool from modelContextTesting if missing (Chrome native)\n // Chrome 146+ has these methods on modelContextTesting but not on modelContext.\n // embed.js requires them on modelContext for getExtendedModelContext() to succeed.\n if (!hasListTools || !hasCallTool) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const testing = (navigator as any).modelContextTesting;\n if (testing) {\n if (\n typeof testing.listTools === 'function' &&\n !testing.__webmcpNexusListToolsPatched\n ) {\n const originalTestingListTools = testing.listTools.bind(testing);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n testing.listTools = (...args: any[]) => {\n const nativeTools = originalTestingListTools(...args);\n return Array.isArray(nativeTools)\n ? nativeTools.filter((tool: any) => {\n if (!managedToolNames.has(tool.name)) return true;\n return activeTools.has(tool.name);\n })\n : nativeTools;\n };\n testing.__webmcpNexusListToolsPatched = true;\n }\n if (!hasListTools && typeof testing.listTools === 'function') {\n // Convert testing format (inputSchema as JSON string) to extended format (inputSchema as object)\n mc.listTools = () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return testing.listTools().map((t: any) => ({\n name: t.name,\n description: t.description || '',\n inputSchema:\n typeof t.inputSchema === 'string' && t.inputSchema.length > 0\n ? JSON.parse(t.inputSchema)\n : { type: 'object', properties: {} },\n }));\n } catch {\n return [];\n }\n };\n }\n if (!hasCallTool && typeof testing.executeTool === 'function') {\n mc.callTool = async (params: { name: string; arguments?: Record<string, unknown> }) => {\n const result = await testing.executeTool(\n params.name,\n JSON.stringify(params.arguments || {}),\n );\n if (result === null) {\n return {\n isError: true,\n content: [{ type: 'text', text: 'Tool execution interrupted by navigation' }],\n };\n }\n try {\n const parsed = JSON.parse(result);\n return normalizeToCallToolResult(parsed);\n } catch {\n throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 500)}`);\n }\n };\n }\n }\n }\n\n // Wrap registerTool/unregisterTool to auto-fire 'toolchange'\n // For Chrome native: registerTool fires toolchange on modelContextTesting (not modelContext),\n // but embed.js subscribes on modelContext after our patch — so we need to bridge the event.\n // For polyfill: BrowserMcpServer doesn't fire any events, so wrapping is required.\n if (typeof mc.registerTool === 'function') {\n const originalRegisterTool = mc.registerTool.bind(mc);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mc.registerTool = (tool: any, options?: any) => {\n let registrationError: unknown;\n try {\n originalRegisterTool(tool, options);\n } catch (error) {\n registrationError = error;\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n if (registrationError) {\n throw registrationError;\n }\n };\n }\n\n if (typeof mc.unregisterTool === 'function') {\n const originalUnregisterTool = mc.unregisterTool.bind(mc);\n mc.unregisterTool = (name: string) => {\n try {\n originalUnregisterTool(name);\n } catch {\n // Swallow unregister errors\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n };\n }\n\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n}\n\ntype ToolScope = 'global' | 'route' | 'component';\n\ninterface ToolEntry {\n name: string;\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ToolOwner {\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ActiveToolRecord {\n name: string;\n config: WebMcpToolConfig;\n configs: Map<string, WebMcpToolConfig>;\n controller: AbortController;\n owners: Map<string, ToolOwner>;\n nativeRegistered: boolean;\n}\n\nconst registry = new Map<string, ToolEntry[]>();\nconst activeTools = new Map<string, ActiveToolRecord>();\nconst managedToolNames = new Set<string>();\nlet hasManagedTools = false;\n\nfunction getOwnerKey(owner: ToolOwner): string {\n return `${owner.scope}:${owner.scopeId}`;\n}\n\n/** 获取当前活跃工具名列表(仅用于测试和内部同步) */\nexport function getActiveToolNames(): string[] {\n return Array.from(activeTools.keys());\n}\n\n/** 获取当前活跃工具配置列表(供 pushToolsToWidget 在 listTools 缺失时兜底) */\nexport function getActiveToolConfigs(): Array<{\n name: string;\n description: string;\n inputSchema: object;\n}> {\n return Array.from(activeTools.values()).map(record => ({\n name: record.name,\n description: record.config.description,\n inputSchema: record.config.inputSchema || { type: 'object', properties: {} },\n }));\n}\n\n/**\n * 在内部注册表中记录工具的所有权信息。\n * 如果同名工具已被其他 scope 注册,输出警告但仍允许注册。\n */\nexport function registerEntry(name: string, scope: ToolScope, scopeId: string): void {\n const entries = registry.get(name) || [];\n if (entries.length > 0) {\n const isSameScopeAndId = entries.some(e => e.scope === scope && e.scopeId === scopeId);\n if (!isSameScopeAndId) {\n const existing = entries[0];\n console.warn(\n `[webmcp] Tool \"${name}\" is already registered by scope \"${existing.scope}:${existing.scopeId}\". ` +\n `Re-registering from \"${scope}:${scopeId}\". This may cause unexpected behavior.`,\n );\n }\n }\n entries.push({ name, scope, scopeId });\n registry.set(name, entries);\n}\n\n/**\n * 从内部注册表中移除指定 scope 的工具所有权记录。\n * @returns true 表示可以安全调用 unregisterTool(最后一个持有者已移除);\n * false 表示还有其他 scope 持有该工具,不应注销。\n */\nexport function unregisterEntry(name: string, scope: ToolScope, scopeId: string): boolean {\n const entries = registry.get(name);\n if (!entries) return false;\n const idx = entries.findIndex(e => e.scope === scope && e.scopeId === scopeId);\n if (idx === -1) return false;\n entries.splice(idx, 1);\n if (entries.length === 0) {\n registry.delete(name);\n return true; // 可以安全注销\n }\n return false; // 其他 scope 仍持有该工具\n}\n\n/**\n * 清空注册表(仅用于测试)\n */\nexport function clearRegistry(): void {\n registry.clear();\n activeTools.clear();\n managedToolNames.clear();\n hasManagedTools = false;\n modelContextPatched = false;\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n}\n\n/**\n * 注册带 owner 的工具。\n * 同名工具只会原生注册一次;多个组件/作用域通过 owners 聚合生命周期。\n */\nexport function registerScopedTool(toolConfig: WebMcpToolConfig, owner: ToolOwner): void {\n try {\n const ownerKey = getOwnerKey(owner);\n hasManagedTools = true;\n managedToolNames.add(toolConfig.name);\n const existing = activeTools.get(toolConfig.name);\n if (existing) {\n existing.owners.set(ownerKey, owner);\n existing.configs.set(ownerKey, toolConfig);\n return;\n }\n\n const controller = new AbortController();\n const owners = new Map<string, ToolOwner>([[ownerKey, owner]]);\n const record: ActiveToolRecord = {\n name: toolConfig.name,\n config: toolConfig,\n configs: new Map([[ownerKey, toolConfig]]),\n controller,\n owners,\n nativeRegistered: false,\n };\n\n activeTools.set(toolConfig.name, record);\n\n const mc =\n typeof navigator !== 'undefined'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any)\n : undefined;\n\n controller.signal.addEventListener(\n 'abort',\n () => {\n try {\n // L2 fallback: 仅当 modelContext 不是带原生 signal 处理的实现时才调用 unregisterTool。\n // - Chrome 148+ 原生:移除了 unregisterTool,typeof === 'function' 为 false,自动跳过\n // - Chrome 146/147 原生:registerTool 不识别 signal,必须靠 unregisterTool 注销\n // - @mcp-b/webmcp-polyfill 2.x:registerTool 内部已 hook signal abort 自动删除 tool,\n // 再调 unregisterTool 既是双重操作,又会触发 polyfill 的 deprecation 警告。\n // 通过 __isWebMCPPolyfill 标记跳过这条路径。\n if (\n mc &&\n typeof mc.unregisterTool === 'function' &&\n !mc.__isWebMCPPolyfill\n ) {\n mc.unregisterTool(toolConfig.name);\n }\n } catch {\n // Ignore legacy fallback errors\n }\n try {\n activeTools.delete(toolConfig.name);\n notifyToolsChanged();\n } catch {\n // Ignore notification errors\n }\n },\n { once: true },\n );\n\n if (mc && typeof mc.registerTool === 'function') {\n const nativeToolConfig = {\n ...toolConfig,\n execute: async (input: Record<string, unknown>) => {\n const latestRecord = activeTools.get(toolConfig.name);\n return latestRecord?.config.execute(input);\n },\n };\n try {\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n } catch {\n try {\n if (typeof mc.unregisterTool === 'function') {\n mc.unregisterTool(toolConfig.name);\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n }\n } catch {\n // Keep the internal mirror so widget fallback still reflects SDK ownership.\n }\n }\n }\n } catch {\n // SDK public paths should never surface browser API errors.\n }\n}\n\n/**\n * 移除工具 owner。只有最后一个 owner 移除时才 abort 原生注册。\n * @returns true 表示工具列表发生了实际注销;false 表示仍有其他 owner。\n */\nexport function unregisterScopedTool(name: string, owner: ToolOwner): boolean {\n try {\n const record = activeTools.get(name);\n if (!record) {\n return false;\n }\n\n const ownerKey = getOwnerKey(owner);\n const removedConfig = record.configs.get(ownerKey);\n record.owners.delete(ownerKey);\n record.configs.delete(ownerKey);\n if (removedConfig && record.config === removedConfig) {\n const nextConfig = record.configs.values().next().value as WebMcpToolConfig | undefined;\n if (nextConfig) {\n record.config = nextConfig;\n }\n }\n if (record.owners.size > 0) return false;\n\n if (!record.controller.signal.aborted) {\n record.controller.abort();\n } else {\n activeTools.delete(name);\n notifyToolsChanged();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Debounce timer for pushing tools to widget iframe.\n */\nlet pushToolsTimer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Directly push the current tool list to any widget iframes via postMessage.\n * This bypasses Chrome's registerToolsChangedCallback mechanism which doesn't\n * fire on unregisterTool, ensuring the relay always has the correct tool list.\n */\nfunction pushToolsToWidget(): void {\n try {\n if (typeof document === 'undefined') return;\n\n const iframes = document.querySelectorAll('iframe');\n if (iframes.length === 0) return;\n\n const tools = getToolsForWidget();\n\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\n // embed.js(@mcp-b/webmcp-local-relay)已通过 toolchange 监听自行推送\n // relay iframe,跳过以避免重复 webmcp.tools.changed 消息\n if (iframe.hasAttribute('data-webmcp-relay')) continue;\n try {\n if (iframe.contentWindow) {\n iframe.contentWindow.postMessage(\n {\n type: 'webmcp.tools.changed',\n tools,\n },\n '*',\n );\n }\n } catch {\n // Cross-origin or other iframe access error, skip\n }\n }\n } catch {\n // Ignore errors\n }\n}\n\nfunction getToolsForWidget(): Array<{ name: string; description: string; inputSchema: object }> {\n if (hasManagedTools) {\n return getActiveToolConfigs();\n }\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = typeof navigator !== 'undefined' ? (navigator.modelContext as any) : undefined;\n if (mc && typeof mc.listTools === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tools = mc.listTools().map((tool: any) => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema:\n typeof tool.inputSchema === 'string' && tool.inputSchema.length > 0\n ? safeJsonParse(tool.inputSchema)\n : tool.inputSchema || { type: 'object', properties: {} },\n }));\n return tools;\n }\n } catch {\n // Fall through to activeTools mirror\n }\n return getActiveToolConfigs();\n}\n\nfunction safeJsonParse(value: string): object {\n try {\n return JSON.parse(value);\n } catch {\n return { type: 'object', properties: {} };\n }\n}\n\n/**\n * Schedule a debounced push of tools to widget iframes.\n * Uses 100ms delay to coalesce rapid register/unregister cycles\n * (e.g., during SPA navigation when old component unmounts and new one mounts).\n */\nfunction schedulePushToolsToWidget(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n }\n pushToolsTimer = setTimeout(() => {\n pushToolsTimer = null;\n pushToolsToWidget();\n }, 100);\n}\n\n/**\n * 通知工具列表已变化,发射标准 toolchange 事件并推送至 widget iframe。\n */\nexport function notifyToolsChanged(): void {\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) return;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any).dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n\n schedulePushToolsToWidget();\n}\n\n/**\n * 安全注册工具,处理重复名称的情况。\n * 如果 registerTool 因重复名称抛错,则先 unregister 再重试。\n */\nexport function safeRegisterTool(toolConfig: WebMcpToolConfig): void {\n registerScopedTool(toolConfig, { scope: 'global', scopeId: 'safe-register' });\n}\n","// packages/webmcp-sdk/src/polyfill.ts\nimport { initializeWebMCPPolyfill, cleanupWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';\n\nlet attempted = false;\n\n/**\n * 惰性、幂等地确保 navigator.modelContext 存在。\n *\n * - 原生(Chrome 146+)/ 已被其他 polyfill 安装:直接返回,不动 navigator\n * - 非浏览器环境(SSR):直接返回\n * - 缺失 modelContext:调 initializeWebMCPPolyfill 装上严格 W3C 核心 + modelContextTesting shim\n *\n * 调用方应在判定 'modelContext' in navigator 之前调用本函数。整体包 try/catch,\n * polyfill 加载或初始化失败不向调用方传播异常——SDK 后续逻辑会按\"无 modelContext\"路径继续 no-op。\n */\nexport function ensureModelContextPolyfill(): void {\n if (attempted) return;\n attempted = true;\n if (typeof navigator === 'undefined') return;\n if ('modelContext' in navigator) return;\n try {\n initializeWebMCPPolyfill({ installTestingShim: true });\n } catch {\n // polyfill 初始化失败兜底\n }\n}\n\n/**\n * 仅供单元测试使用:重置模块级 attempted 标志并卸载 polyfill。\n * 生产代码不应调用本函数。\n */\nexport function __resetPolyfillStateForTest(): void {\n attempted = false;\n try {\n cleanupWebMCPPolyfill();\n } catch {\n // cleanup 失败兜底\n }\n}\n","// packages/webmcp-sdk/src/registerGlobalTools.ts\nimport {\n registerEntry,\n registerScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/**\n * 全局注册 WebMCP 工具。应用启动时调用一次。\n * 支持可变参数,兼容 import * as module 批量导入。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * import * as userApi from './api/user';\n * import * as productApi from './api/product';\n * registerGlobalTools(userApi, productApi);\n *\n * @example\n * registerGlobalTools({ getUser, searchUsers }, { searchProducts });\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function registerGlobalTools(...toolMaps: Record<string, Function>[]): void {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n try {\n patchModelContextEventSupport();\n\n let registeredCount = 0;\n\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n // 跳过非函数值(如 TypeScript 类型导出在运行时可能不存在)\n if (typeof fn !== 'function') continue;\n\n // __webmcpSchema 由 Vite 插件在构建时注入\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerEntry(name, 'global', 'app');\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n execute: async (input: unknown) => fn(input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'global', scopeId: 'app' },\n );\n registeredCount++;\n }\n }\n\n if (registeredCount > 0) {\n notifyToolsChanged();\n }\n } catch {\n // 全局兜底:SDK 入口不向调用方传播浏览器 API 异常\n }\n}\n","// packages/webmcp-sdk/src/useWebMcpTools.ts\nimport { useEffect, useRef, useState } from 'react';\nimport {\n registerScopedTool,\n unregisterScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\nlet scopeCounter = 0;\nfunction generateScopeId(): string {\n return `component-${++scopeCounter}`;\n}\n\n// HMR support: track a global version counter that increments on hot updates.\n// Function body updates are already handled via toolsRef.current indirect calls.\n// This counter ensures schema changes (__webmcpSchema) trigger re-registration.\nlet hmrVersion = 0;\nconst hmrHot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\nif (hmrHot && typeof hmrHot.on === 'function') {\n hmrHot.on('vite:afterUpdate', () => {\n hmrVersion++;\n });\n}\n\n/**\n * React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。\n * mount 时注册,unmount 时自动注销。\n *\n * 使用 useRef 持有最新函数引用,避免闭包陷阱。\n * 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,\n * 当工具集合变化时重新注册,函数体变化不触发重新注册。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * // 组件级注册\n * useWebMcpTools({ searchInPanel, clearSearch });\n *\n * @example\n * // 路由级注册(配合 React Router 使用)\n * useWebMcpTools({ setUserFilter });\n */\nexport function useWebMcpTools(...toolMaps: Record<string, Function>[]): void {\n // 合并所有 toolMap 为一个对象\n const merged: Record<string, Function> = {};\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n if (typeof fn === 'function') {\n merged[name] = fn;\n }\n }\n }\n\n // 用 ref 持有最新的函数引用,避免闭包陷阱\n const toolsRef = useRef(merged);\n toolsRef.current = merged;\n\n // 生成唯一 scopeId,确保同一组件实例的 scopeId 一致\n const scopeIdRef = useRef<string>('');\n if (!scopeIdRef.current) {\n scopeIdRef.current = generateScopeId();\n }\n\n // 工具名集合作为依赖 — 工具集合变化时重新注册\n const toolKeys = Object.keys(merged).sort().join(',');\n\n // DEV: listen for HMR updates to force re-registration when schema changes\n const [localHmrVersion, setLocalHmrVersion] = useState(hmrVersion);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setLocalHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n useEffect(() => {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n patchModelContextEventSupport();\n\n const registeredNames: string[] = [];\n\n for (const [name, fn] of Object.entries(toolsRef.current)) {\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n // 通过 ref 间接调用,保证始终执行最新版本的函数\n execute: async (input: unknown) => toolsRef.current[name](input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'component', scopeId: scopeIdRef.current },\n );\n\n registeredNames.push(name);\n }\n\n if (registeredNames.length > 0) {\n notifyToolsChanged();\n }\n\n // 组件卸载时释放 owner;最后一个 owner 才会触发原生 abort。\n return () => {\n let unregisteredAny = false;\n for (const name of registeredNames) {\n const shouldUnregister = unregisterScopedTool(name, {\n scope: 'component',\n scopeId: scopeIdRef.current,\n });\n if (shouldUnregister) {\n unregisteredAny = true;\n }\n }\n if (unregisteredAny) {\n notifyToolsChanged();\n }\n };\n }, [toolKeys, localHmrVersion]); // 工具集合变化或 HMR 更新时重新注册\n}\n","// packages/webmcp-sdk/src/withWebMcpTools.tsx\nimport React, { useRef, useMemo, useState, useEffect } from 'react';\nimport { useWebMcpTools } from './useWebMcpTools';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/** React 生命周期方法黑名单 */\nconst LIFECYCLE_METHODS = new Set([\n 'constructor',\n 'render',\n 'componentDidMount',\n 'componentDidUpdate',\n 'componentWillUnmount',\n 'shouldComponentUpdate',\n 'getSnapshotBeforeUpdate',\n 'componentDidCatch',\n]);\n\n/**\n * 高阶组件:将 class 组件的方法注册为 WebMCP 工具。\n * mount 时注册,unmount 时自动注销。\n *\n * 支持两种方法形式:\n * - 原型方法:`methodName(params: T) { ... }` — schema 挂在 prototype 上\n * - Class field 箭头函数:`methodName = (params: T) => { ... }` — schema 挂在静态属性上\n *\n * @param WrappedComponent - React class 组件\n * @param methodNames - 可选,显式指定要注册的方法名列表\n *\n * @example\n * class MyPanel extends React.Component {\n * /\\** 搜索面板 @readonly *\\/\n * searchInPanel(params: { query: string }) { ... }\n *\n * /\\** 清除搜索 *\\/\n * clearSearch = (params: { confirm?: boolean }) => { ... };\n *\n * render() { return <div />; }\n * }\n * export default withWebMcpTools(MyPanel);\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withWebMcpTools<P extends Record<string, any>>(\n WrappedComponent: React.ComponentClass<P>,\n methodNames?: string[],\n): React.ComponentType<P> {\n const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';\n\n function WebMcpToolsWrapper(props: P) {\n const instanceRef = useRef<InstanceType<typeof WrappedComponent> | null>(null);\n\n // HMR 版本追踪:schema 更新时重建 toolMap\n const [hmrVersion, setHmrVersion] = useState(0);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const toolMap = useMemo((): Record<string, any> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const map: Record<string, any> = {};\n const candidates = methodNames ? new Set(methodNames) : null;\n const proto = WrappedComponent.prototype;\n\n // 来源 1:原型方法(__webmcpSchema 挂在 prototype 方法上)\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (LIFECYCLE_METHODS.has(name)) continue;\n if (candidates && !candidates.has(name)) continue;\n const method = proto[name];\n if (typeof method !== 'function') continue;\n const schema = (method as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapper: any = (input: unknown) => (instanceRef.current as any)?.[name](input);\n wrapper.__webmcpSchema = schema;\n map[name] = wrapper;\n }\n\n // 来源 2:class field 箭头函数(schema 挂在静态属性 __webmcpFieldSchemas 上)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldSchemas = (WrappedComponent as any).__webmcpFieldSchemas as\n | Record<string, { description: string; inputSchema: object; readOnly?: boolean }>\n | undefined;\n if (fieldSchemas) {\n for (const [name, schema] of Object.entries(fieldSchemas)) {\n if (candidates && !candidates.has(name)) continue;\n if (map[name]) continue; // 原型方法优先\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapper: any = (input: unknown) => (instanceRef.current as any)?.[name](input);\n wrapper.__webmcpSchema = schema;\n map[name] = wrapper;\n }\n }\n\n return map;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hmrVersion]);\n\n useWebMcpTools(toolMap);\n\n return <WrappedComponent ref={instanceRef} {...props} />;\n }\n\n WebMcpToolsWrapper.displayName = `withWebMcpTools(${displayName})`;\n return WebMcpToolsWrapper;\n}\n"],"mappings":"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","names":["import_react","import_meta","hmrVersion"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  /**
2
4
  * 全局注册 WebMCP 工具。应用启动时调用一次。
3
5
  * 支持可变参数,兼容 import * as module 批量导入。
@@ -34,6 +36,31 @@ declare function registerGlobalTools(...toolMaps: Record<string, Function>[]): v
34
36
  */
35
37
  declare function useWebMcpTools(...toolMaps: Record<string, Function>[]): void;
36
38
 
39
+ /**
40
+ * 高阶组件:将 class 组件的方法注册为 WebMCP 工具。
41
+ * mount 时注册,unmount 时自动注销。
42
+ *
43
+ * 支持两种方法形式:
44
+ * - 原型方法:`methodName(params: T) { ... }` — schema 挂在 prototype 上
45
+ * - Class field 箭头函数:`methodName = (params: T) => { ... }` — schema 挂在静态属性上
46
+ *
47
+ * @param WrappedComponent - React class 组件
48
+ * @param methodNames - 可选,显式指定要注册的方法名列表
49
+ *
50
+ * @example
51
+ * class MyPanel extends React.Component {
52
+ * /\** 搜索面板 @readonly *\/
53
+ * searchInPanel(params: { query: string }) { ... }
54
+ *
55
+ * /\** 清除搜索 *\/
56
+ * clearSearch = (params: { confirm?: boolean }) => { ... };
57
+ *
58
+ * render() { return <div />; }
59
+ * }
60
+ * export default withWebMcpTools(MyPanel);
61
+ */
62
+ declare function withWebMcpTools<P extends Record<string, any>>(WrappedComponent: React.ComponentClass<P>, methodNames?: string[]): React.ComponentType<P>;
63
+
37
64
  /** 工具 schema 元数据(由 Vite 插件构建时注入到函数的 __webmcpSchema 属性) */
38
65
  interface WebMcpToolSchema {
39
66
  /** 工具描述(来自 JSDoc) */
@@ -74,4 +101,4 @@ interface WebMcpToolConfig {
74
101
  };
75
102
  }
76
103
 
77
- export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools };
104
+ export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools, withWebMcpTools };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  /**
2
4
  * 全局注册 WebMCP 工具。应用启动时调用一次。
3
5
  * 支持可变参数,兼容 import * as module 批量导入。
@@ -34,6 +36,31 @@ declare function registerGlobalTools(...toolMaps: Record<string, Function>[]): v
34
36
  */
35
37
  declare function useWebMcpTools(...toolMaps: Record<string, Function>[]): void;
36
38
 
39
+ /**
40
+ * 高阶组件:将 class 组件的方法注册为 WebMCP 工具。
41
+ * mount 时注册,unmount 时自动注销。
42
+ *
43
+ * 支持两种方法形式:
44
+ * - 原型方法:`methodName(params: T) { ... }` — schema 挂在 prototype 上
45
+ * - Class field 箭头函数:`methodName = (params: T) => { ... }` — schema 挂在静态属性上
46
+ *
47
+ * @param WrappedComponent - React class 组件
48
+ * @param methodNames - 可选,显式指定要注册的方法名列表
49
+ *
50
+ * @example
51
+ * class MyPanel extends React.Component {
52
+ * /\** 搜索面板 @readonly *\/
53
+ * searchInPanel(params: { query: string }) { ... }
54
+ *
55
+ * /\** 清除搜索 *\/
56
+ * clearSearch = (params: { confirm?: boolean }) => { ... };
57
+ *
58
+ * render() { return <div />; }
59
+ * }
60
+ * export default withWebMcpTools(MyPanel);
61
+ */
62
+ declare function withWebMcpTools<P extends Record<string, any>>(WrappedComponent: React.ComponentClass<P>, methodNames?: string[]): React.ComponentType<P>;
63
+
37
64
  /** 工具 schema 元数据(由 Vite 插件构建时注入到函数的 __webmcpSchema 属性) */
38
65
  interface WebMcpToolSchema {
39
66
  /** 工具描述(来自 JSDoc) */
@@ -74,4 +101,4 @@ interface WebMcpToolConfig {
74
101
  };
75
102
  }
76
103
 
77
- export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools };
104
+ export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools, withWebMcpTools };
package/dist/index.js CHANGED
@@ -1,5 +1,33 @@
1
1
  // src/registry.ts
2
2
  var modelContextPatched = false;
3
+ function isCallToolResult(value) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5
+ const obj = value;
6
+ if (!Array.isArray(obj.content)) return false;
7
+ if (obj.content.length === 0) return true;
8
+ const first = obj.content[0];
9
+ return first !== null && typeof first === "object" && "type" in first && (first.type === "text" || first.type === "image" || first.type === "resource");
10
+ }
11
+ function normalizeToCallToolResult(value) {
12
+ if (isCallToolResult(value)) {
13
+ return value;
14
+ }
15
+ let text;
16
+ if (typeof value === "string") {
17
+ text = value;
18
+ } else {
19
+ try {
20
+ const serialized = JSON.stringify(value);
21
+ text = serialized === void 0 ? String(value) : serialized;
22
+ } catch {
23
+ text = String(value);
24
+ }
25
+ }
26
+ return {
27
+ content: [{ type: "text", text }],
28
+ isError: false
29
+ };
30
+ }
3
31
  function patchDispatchEventForCoalescing(mc) {
4
32
  if (mc.__toolchangeCoalesced) return;
5
33
  const originalDispatch = mc.dispatchEvent.bind(mc);
@@ -97,9 +125,10 @@ function patchModelContextEventSupport() {
97
125
  };
98
126
  }
99
127
  try {
100
- return JSON.parse(result);
128
+ const parsed = JSON.parse(result);
129
+ return normalizeToCallToolResult(parsed);
101
130
  } catch {
102
- throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);
131
+ throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 500)}`);
103
132
  }
104
133
  };
105
134
  }
@@ -466,8 +495,73 @@ function useWebMcpTools(...toolMaps) {
466
495
  };
467
496
  }, [toolKeys, localHmrVersion]);
468
497
  }
498
+
499
+ // src/withWebMcpTools.tsx
500
+ import { useRef as useRef2, useMemo, useState as useState2, useEffect as useEffect2 } from "react";
501
+ import { jsx } from "react/jsx-runtime";
502
+ var LIFECYCLE_METHODS = /* @__PURE__ */ new Set([
503
+ "constructor",
504
+ "render",
505
+ "componentDidMount",
506
+ "componentDidUpdate",
507
+ "componentWillUnmount",
508
+ "shouldComponentUpdate",
509
+ "getSnapshotBeforeUpdate",
510
+ "componentDidCatch"
511
+ ]);
512
+ function withWebMcpTools(WrappedComponent, methodNames) {
513
+ const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
514
+ function WebMcpToolsWrapper(props) {
515
+ const instanceRef = useRef2(null);
516
+ const [hmrVersion2, setHmrVersion] = useState2(0);
517
+ useEffect2(() => {
518
+ const hot = typeof import.meta !== "undefined" ? import.meta.hot : void 0;
519
+ if (hot && typeof hot.on === "function") {
520
+ const handler = () => setHmrVersion((v) => v + 1);
521
+ hot.on("vite:afterUpdate", handler);
522
+ return () => {
523
+ if (typeof hot.off === "function") {
524
+ hot.off("vite:afterUpdate", handler);
525
+ }
526
+ };
527
+ }
528
+ }, []);
529
+ const toolMap = useMemo(() => {
530
+ const map = {};
531
+ const candidates = methodNames ? new Set(methodNames) : null;
532
+ const proto = WrappedComponent.prototype;
533
+ for (const name of Object.getOwnPropertyNames(proto)) {
534
+ if (LIFECYCLE_METHODS.has(name)) continue;
535
+ if (candidates && !candidates.has(name)) continue;
536
+ const method = proto[name];
537
+ if (typeof method !== "function") continue;
538
+ const schema = method.__webmcpSchema;
539
+ if (!schema) continue;
540
+ const wrapper = (input) => instanceRef.current?.[name](input);
541
+ wrapper.__webmcpSchema = schema;
542
+ map[name] = wrapper;
543
+ }
544
+ const fieldSchemas = WrappedComponent.__webmcpFieldSchemas;
545
+ if (fieldSchemas) {
546
+ for (const [name, schema] of Object.entries(fieldSchemas)) {
547
+ if (candidates && !candidates.has(name)) continue;
548
+ if (map[name]) continue;
549
+ const wrapper = (input) => instanceRef.current?.[name](input);
550
+ wrapper.__webmcpSchema = schema;
551
+ map[name] = wrapper;
552
+ }
553
+ }
554
+ return map;
555
+ }, [hmrVersion2]);
556
+ useWebMcpTools(toolMap);
557
+ return /* @__PURE__ */ jsx(WrappedComponent, { ref: instanceRef, ...props });
558
+ }
559
+ WebMcpToolsWrapper.displayName = `withWebMcpTools(${displayName})`;
560
+ return WebMcpToolsWrapper;
561
+ }
469
562
  export {
470
563
  registerGlobalTools,
471
- useWebMcpTools
564
+ useWebMcpTools,
565
+ withWebMcpTools
472
566
  };
473
567
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts","../src/polyfill.ts","../src/registerGlobalTools.ts","../src/useWebMcpTools.ts"],"sourcesContent":["// packages/webmcp-sdk/src/registry.ts\nimport type { WebMcpToolConfig } from './types';\n\nlet modelContextPatched = false;\n\n/**\n * 在 dispatchEvent 层面合并 toolchange 事件。\n * 无论来源(SDK 包装器、polyfill 内部、第三方代码),同一微任务内只触发 1 次。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction patchDispatchEventForCoalescing(mc: any): void {\n if (mc.__toolchangeCoalesced) return;\n const originalDispatch: (event: Event) => boolean =\n mc.dispatchEvent.bind(mc);\n let pending = false;\n mc.dispatchEvent = (event: Event): boolean => {\n if (event.type === 'toolchange') {\n if (!pending) {\n pending = true;\n queueMicrotask(() => {\n originalDispatch(new Event('toolchange'));\n pending = false;\n });\n }\n return true;\n }\n return originalDispatch(event);\n };\n mc.__toolchangeCoalesced = true;\n}\n\n/**\n * Patches navigator.modelContext to ensure embed.js can discover tools.\n *\n * Handles three scenarios:\n * 1. Chrome 146+ native / @mcp-b/webmcp-polyfill: modelContext lacks\n * listTools/callTool/EventTarget; modelContextTesting shim provides\n * listTools/executeTool\n * → Bridge listTools/callTool from modelContextTesting, add EventTarget,\n * wrap registerTool/unregisterTool to fire toolchange\n * 2. Older polyfills (e.g. @mcp-b/global before EventTarget support):\n * modelContext has listTools/callTool but lacks EventTarget\n * → Add EventTarget, wrap registerTool/unregisterTool to fire toolchange\n * 3. Full MCP-B environment: modelContext has everything → skip\n */\nexport function patchModelContextEventSupport(): void {\n if (modelContextPatched) {\n return;\n }\n if (\n typeof navigator === 'undefined' ||\n !('modelContext' in navigator) ||\n !navigator.modelContext\n ) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = navigator.modelContext as any;\n const hasEventTarget = typeof mc.addEventListener === 'function';\n const hasListTools = typeof mc.listTools === 'function';\n const hasCallTool = typeof mc.callTool === 'function';\n\n // Scenario 3: fully featured — only apply toolchange coalescing\n if (hasEventTarget && hasListTools && hasCallTool) {\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n return;\n }\n\n // Add EventTarget support if missing (Chrome native & polyfill)\n if (!hasEventTarget) {\n const listeners = new Map<string, Set<EventListener>>();\n\n mc.addEventListener = (type: string, callback: EventListener) => {\n if (!listeners.has(type)) listeners.set(type, new Set());\n listeners.get(type)!.add(callback);\n };\n\n mc.removeEventListener = (type: string, callback: EventListener) => {\n listeners.get(type)?.delete(callback);\n };\n\n mc.dispatchEvent = (event: Event): boolean => {\n const set = listeners.get(event.type);\n if (set) {\n set.forEach(fn => {\n try {\n fn.call(mc, event);\n } catch {\n // Ignore listener errors\n }\n });\n }\n return true;\n };\n }\n\n // Bridge listTools/callTool from modelContextTesting if missing (Chrome native)\n // Chrome 146+ has these methods on modelContextTesting but not on modelContext.\n // embed.js requires them on modelContext for getExtendedModelContext() to succeed.\n if (!hasListTools || !hasCallTool) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const testing = (navigator as any).modelContextTesting;\n if (testing) {\n if (\n typeof testing.listTools === 'function' &&\n !testing.__webmcpNexusListToolsPatched\n ) {\n const originalTestingListTools = testing.listTools.bind(testing);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n testing.listTools = (...args: any[]) => {\n const nativeTools = originalTestingListTools(...args);\n return Array.isArray(nativeTools)\n ? nativeTools.filter((tool: any) => {\n if (!managedToolNames.has(tool.name)) return true;\n return activeTools.has(tool.name);\n })\n : nativeTools;\n };\n testing.__webmcpNexusListToolsPatched = true;\n }\n if (!hasListTools && typeof testing.listTools === 'function') {\n // Convert testing format (inputSchema as JSON string) to extended format (inputSchema as object)\n mc.listTools = () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return testing.listTools().map((t: any) => ({\n name: t.name,\n description: t.description || '',\n inputSchema:\n typeof t.inputSchema === 'string' && t.inputSchema.length > 0\n ? JSON.parse(t.inputSchema)\n : { type: 'object', properties: {} },\n }));\n } catch {\n return [];\n }\n };\n }\n if (!hasCallTool && typeof testing.executeTool === 'function') {\n mc.callTool = async (params: { name: string; arguments?: Record<string, unknown> }) => {\n const result = await testing.executeTool(\n params.name,\n JSON.stringify(params.arguments || {}),\n );\n if (result === null) {\n return {\n isError: true,\n content: [{ type: 'text', text: 'Tool execution interrupted by navigation' }],\n };\n }\n try {\n return JSON.parse(result);\n } catch {\n throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);\n }\n };\n }\n }\n }\n\n // Wrap registerTool/unregisterTool to auto-fire 'toolchange'\n // For Chrome native: registerTool fires toolchange on modelContextTesting (not modelContext),\n // but embed.js subscribes on modelContext after our patch — so we need to bridge the event.\n // For polyfill: BrowserMcpServer doesn't fire any events, so wrapping is required.\n if (typeof mc.registerTool === 'function') {\n const originalRegisterTool = mc.registerTool.bind(mc);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mc.registerTool = (tool: any, options?: any) => {\n let registrationError: unknown;\n try {\n originalRegisterTool(tool, options);\n } catch (error) {\n registrationError = error;\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n if (registrationError) {\n throw registrationError;\n }\n };\n }\n\n if (typeof mc.unregisterTool === 'function') {\n const originalUnregisterTool = mc.unregisterTool.bind(mc);\n mc.unregisterTool = (name: string) => {\n try {\n originalUnregisterTool(name);\n } catch {\n // Swallow unregister errors\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n };\n }\n\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n}\n\ntype ToolScope = 'global' | 'route' | 'component';\n\ninterface ToolEntry {\n name: string;\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ToolOwner {\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ActiveToolRecord {\n name: string;\n config: WebMcpToolConfig;\n configs: Map<string, WebMcpToolConfig>;\n controller: AbortController;\n owners: Map<string, ToolOwner>;\n nativeRegistered: boolean;\n}\n\nconst registry = new Map<string, ToolEntry[]>();\nconst activeTools = new Map<string, ActiveToolRecord>();\nconst managedToolNames = new Set<string>();\nlet hasManagedTools = false;\n\nfunction getOwnerKey(owner: ToolOwner): string {\n return `${owner.scope}:${owner.scopeId}`;\n}\n\n/** 获取当前活跃工具名列表(仅用于测试和内部同步) */\nexport function getActiveToolNames(): string[] {\n return Array.from(activeTools.keys());\n}\n\n/** 获取当前活跃工具配置列表(供 pushToolsToWidget 在 listTools 缺失时兜底) */\nexport function getActiveToolConfigs(): Array<{\n name: string;\n description: string;\n inputSchema: object;\n}> {\n return Array.from(activeTools.values()).map(record => ({\n name: record.name,\n description: record.config.description,\n inputSchema: record.config.inputSchema || { type: 'object', properties: {} },\n }));\n}\n\n/**\n * 在内部注册表中记录工具的所有权信息。\n * 如果同名工具已被其他 scope 注册,输出警告但仍允许注册。\n */\nexport function registerEntry(name: string, scope: ToolScope, scopeId: string): void {\n const entries = registry.get(name) || [];\n if (entries.length > 0) {\n const isSameScopeAndId = entries.some(e => e.scope === scope && e.scopeId === scopeId);\n if (!isSameScopeAndId) {\n const existing = entries[0];\n console.warn(\n `[webmcp] Tool \"${name}\" is already registered by scope \"${existing.scope}:${existing.scopeId}\". ` +\n `Re-registering from \"${scope}:${scopeId}\". This may cause unexpected behavior.`,\n );\n }\n }\n entries.push({ name, scope, scopeId });\n registry.set(name, entries);\n}\n\n/**\n * 从内部注册表中移除指定 scope 的工具所有权记录。\n * @returns true 表示可以安全调用 unregisterTool(最后一个持有者已移除);\n * false 表示还有其他 scope 持有该工具,不应注销。\n */\nexport function unregisterEntry(name: string, scope: ToolScope, scopeId: string): boolean {\n const entries = registry.get(name);\n if (!entries) return false;\n const idx = entries.findIndex(e => e.scope === scope && e.scopeId === scopeId);\n if (idx === -1) return false;\n entries.splice(idx, 1);\n if (entries.length === 0) {\n registry.delete(name);\n return true; // 可以安全注销\n }\n return false; // 其他 scope 仍持有该工具\n}\n\n/**\n * 清空注册表(仅用于测试)\n */\nexport function clearRegistry(): void {\n registry.clear();\n activeTools.clear();\n managedToolNames.clear();\n hasManagedTools = false;\n modelContextPatched = false;\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n}\n\n/**\n * 注册带 owner 的工具。\n * 同名工具只会原生注册一次;多个组件/作用域通过 owners 聚合生命周期。\n */\nexport function registerScopedTool(toolConfig: WebMcpToolConfig, owner: ToolOwner): void {\n try {\n const ownerKey = getOwnerKey(owner);\n hasManagedTools = true;\n managedToolNames.add(toolConfig.name);\n const existing = activeTools.get(toolConfig.name);\n if (existing) {\n existing.owners.set(ownerKey, owner);\n existing.configs.set(ownerKey, toolConfig);\n return;\n }\n\n const controller = new AbortController();\n const owners = new Map<string, ToolOwner>([[ownerKey, owner]]);\n const record: ActiveToolRecord = {\n name: toolConfig.name,\n config: toolConfig,\n configs: new Map([[ownerKey, toolConfig]]),\n controller,\n owners,\n nativeRegistered: false,\n };\n\n activeTools.set(toolConfig.name, record);\n\n const mc =\n typeof navigator !== 'undefined'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any)\n : undefined;\n\n controller.signal.addEventListener(\n 'abort',\n () => {\n try {\n // L2 fallback: 仅当 modelContext 不是带原生 signal 处理的实现时才调用 unregisterTool。\n // - Chrome 148+ 原生:移除了 unregisterTool,typeof === 'function' 为 false,自动跳过\n // - Chrome 146/147 原生:registerTool 不识别 signal,必须靠 unregisterTool 注销\n // - @mcp-b/webmcp-polyfill 2.x:registerTool 内部已 hook signal abort 自动删除 tool,\n // 再调 unregisterTool 既是双重操作,又会触发 polyfill 的 deprecation 警告。\n // 通过 __isWebMCPPolyfill 标记跳过这条路径。\n if (\n mc &&\n typeof mc.unregisterTool === 'function' &&\n !mc.__isWebMCPPolyfill\n ) {\n mc.unregisterTool(toolConfig.name);\n }\n } catch {\n // Ignore legacy fallback errors\n }\n try {\n activeTools.delete(toolConfig.name);\n notifyToolsChanged();\n } catch {\n // Ignore notification errors\n }\n },\n { once: true },\n );\n\n if (mc && typeof mc.registerTool === 'function') {\n const nativeToolConfig = {\n ...toolConfig,\n execute: async (input: Record<string, unknown>) => {\n const latestRecord = activeTools.get(toolConfig.name);\n return latestRecord?.config.execute(input);\n },\n };\n try {\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n } catch {\n try {\n if (typeof mc.unregisterTool === 'function') {\n mc.unregisterTool(toolConfig.name);\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n }\n } catch {\n // Keep the internal mirror so widget fallback still reflects SDK ownership.\n }\n }\n }\n } catch {\n // SDK public paths should never surface browser API errors.\n }\n}\n\n/**\n * 移除工具 owner。只有最后一个 owner 移除时才 abort 原生注册。\n * @returns true 表示工具列表发生了实际注销;false 表示仍有其他 owner。\n */\nexport function unregisterScopedTool(name: string, owner: ToolOwner): boolean {\n try {\n const record = activeTools.get(name);\n if (!record) {\n return false;\n }\n\n const ownerKey = getOwnerKey(owner);\n const removedConfig = record.configs.get(ownerKey);\n record.owners.delete(ownerKey);\n record.configs.delete(ownerKey);\n if (removedConfig && record.config === removedConfig) {\n const nextConfig = record.configs.values().next().value as WebMcpToolConfig | undefined;\n if (nextConfig) {\n record.config = nextConfig;\n }\n }\n if (record.owners.size > 0) return false;\n\n if (!record.controller.signal.aborted) {\n record.controller.abort();\n } else {\n activeTools.delete(name);\n notifyToolsChanged();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Debounce timer for pushing tools to widget iframe.\n */\nlet pushToolsTimer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Directly push the current tool list to any widget iframes via postMessage.\n * This bypasses Chrome's registerToolsChangedCallback mechanism which doesn't\n * fire on unregisterTool, ensuring the relay always has the correct tool list.\n */\nfunction pushToolsToWidget(): void {\n try {\n if (typeof document === 'undefined') return;\n\n const iframes = document.querySelectorAll('iframe');\n if (iframes.length === 0) return;\n\n const tools = getToolsForWidget();\n\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\n // embed.js(@mcp-b/webmcp-local-relay)已通过 toolchange 监听自行推送\n // relay iframe,跳过以避免重复 webmcp.tools.changed 消息\n if (iframe.hasAttribute('data-webmcp-relay')) continue;\n try {\n if (iframe.contentWindow) {\n iframe.contentWindow.postMessage(\n {\n type: 'webmcp.tools.changed',\n tools,\n },\n '*',\n );\n }\n } catch {\n // Cross-origin or other iframe access error, skip\n }\n }\n } catch {\n // Ignore errors\n }\n}\n\nfunction getToolsForWidget(): Array<{ name: string; description: string; inputSchema: object }> {\n if (hasManagedTools) {\n return getActiveToolConfigs();\n }\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = typeof navigator !== 'undefined' ? (navigator.modelContext as any) : undefined;\n if (mc && typeof mc.listTools === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tools = mc.listTools().map((tool: any) => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema:\n typeof tool.inputSchema === 'string' && tool.inputSchema.length > 0\n ? safeJsonParse(tool.inputSchema)\n : tool.inputSchema || { type: 'object', properties: {} },\n }));\n return tools;\n }\n } catch {\n // Fall through to activeTools mirror\n }\n return getActiveToolConfigs();\n}\n\nfunction safeJsonParse(value: string): object {\n try {\n return JSON.parse(value);\n } catch {\n return { type: 'object', properties: {} };\n }\n}\n\n/**\n * Schedule a debounced push of tools to widget iframes.\n * Uses 100ms delay to coalesce rapid register/unregister cycles\n * (e.g., during SPA navigation when old component unmounts and new one mounts).\n */\nfunction schedulePushToolsToWidget(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n }\n pushToolsTimer = setTimeout(() => {\n pushToolsTimer = null;\n pushToolsToWidget();\n }, 100);\n}\n\n/**\n * 通知工具列表已变化,发射标准 toolchange 事件并推送至 widget iframe。\n */\nexport function notifyToolsChanged(): void {\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) return;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any).dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n\n schedulePushToolsToWidget();\n}\n\n/**\n * 安全注册工具,处理重复名称的情况。\n * 如果 registerTool 因重复名称抛错,则先 unregister 再重试。\n */\nexport function safeRegisterTool(toolConfig: WebMcpToolConfig): void {\n registerScopedTool(toolConfig, { scope: 'global', scopeId: 'safe-register' });\n}\n","// packages/webmcp-sdk/src/polyfill.ts\nimport { initializeWebMCPPolyfill, cleanupWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';\n\nlet attempted = false;\n\n/**\n * 惰性、幂等地确保 navigator.modelContext 存在。\n *\n * - 原生(Chrome 146+)/ 已被其他 polyfill 安装:直接返回,不动 navigator\n * - 非浏览器环境(SSR):直接返回\n * - 缺失 modelContext:调 initializeWebMCPPolyfill 装上严格 W3C 核心 + modelContextTesting shim\n *\n * 调用方应在判定 'modelContext' in navigator 之前调用本函数。整体包 try/catch,\n * polyfill 加载或初始化失败不向调用方传播异常——SDK 后续逻辑会按\"无 modelContext\"路径继续 no-op。\n */\nexport function ensureModelContextPolyfill(): void {\n if (attempted) return;\n attempted = true;\n if (typeof navigator === 'undefined') return;\n if ('modelContext' in navigator) return;\n try {\n initializeWebMCPPolyfill({ installTestingShim: true });\n } catch {\n // polyfill 初始化失败兜底\n }\n}\n\n/**\n * 仅供单元测试使用:重置模块级 attempted 标志并卸载 polyfill。\n * 生产代码不应调用本函数。\n */\nexport function __resetPolyfillStateForTest(): void {\n attempted = false;\n try {\n cleanupWebMCPPolyfill();\n } catch {\n // cleanup 失败兜底\n }\n}\n","// packages/webmcp-sdk/src/registerGlobalTools.ts\nimport {\n registerEntry,\n registerScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/**\n * 全局注册 WebMCP 工具。应用启动时调用一次。\n * 支持可变参数,兼容 import * as module 批量导入。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * import * as userApi from './api/user';\n * import * as productApi from './api/product';\n * registerGlobalTools(userApi, productApi);\n *\n * @example\n * registerGlobalTools({ getUser, searchUsers }, { searchProducts });\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function registerGlobalTools(...toolMaps: Record<string, Function>[]): void {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n try {\n patchModelContextEventSupport();\n\n let registeredCount = 0;\n\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n // 跳过非函数值(如 TypeScript 类型导出在运行时可能不存在)\n if (typeof fn !== 'function') continue;\n\n // __webmcpSchema 由 Vite 插件在构建时注入\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerEntry(name, 'global', 'app');\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n execute: async (input: unknown) => fn(input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'global', scopeId: 'app' },\n );\n registeredCount++;\n }\n }\n\n if (registeredCount > 0) {\n notifyToolsChanged();\n }\n } catch {\n // 全局兜底:SDK 入口不向调用方传播浏览器 API 异常\n }\n}\n","// packages/webmcp-sdk/src/useWebMcpTools.ts\nimport { useEffect, useRef, useState } from 'react';\nimport {\n registerScopedTool,\n unregisterScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\nlet scopeCounter = 0;\nfunction generateScopeId(): string {\n return `component-${++scopeCounter}`;\n}\n\n// HMR support: track a global version counter that increments on hot updates.\n// Function body updates are already handled via toolsRef.current indirect calls.\n// This counter ensures schema changes (__webmcpSchema) trigger re-registration.\nlet hmrVersion = 0;\nconst hmrHot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\nif (hmrHot && typeof hmrHot.on === 'function') {\n hmrHot.on('vite:afterUpdate', () => {\n hmrVersion++;\n });\n}\n\n/**\n * React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。\n * mount 时注册,unmount 时自动注销。\n *\n * 使用 useRef 持有最新函数引用,避免闭包陷阱。\n * 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,\n * 当工具集合变化时重新注册,函数体变化不触发重新注册。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * // 组件级注册\n * useWebMcpTools({ searchInPanel, clearSearch });\n *\n * @example\n * // 路由级注册(配合 React Router 使用)\n * useWebMcpTools({ setUserFilter });\n */\nexport function useWebMcpTools(...toolMaps: Record<string, Function>[]): void {\n // 合并所有 toolMap 为一个对象\n const merged: Record<string, Function> = {};\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n if (typeof fn === 'function') {\n merged[name] = fn;\n }\n }\n }\n\n // 用 ref 持有最新的函数引用,避免闭包陷阱\n const toolsRef = useRef(merged);\n toolsRef.current = merged;\n\n // 生成唯一 scopeId,确保同一组件实例的 scopeId 一致\n const scopeIdRef = useRef<string>('');\n if (!scopeIdRef.current) {\n scopeIdRef.current = generateScopeId();\n }\n\n // 工具名集合作为依赖 — 工具集合变化时重新注册\n const toolKeys = Object.keys(merged).sort().join(',');\n\n // DEV: listen for HMR updates to force re-registration when schema changes\n const [localHmrVersion, setLocalHmrVersion] = useState(hmrVersion);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setLocalHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n useEffect(() => {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n patchModelContextEventSupport();\n\n const registeredNames: string[] = [];\n\n for (const [name, fn] of Object.entries(toolsRef.current)) {\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n // 通过 ref 间接调用,保证始终执行最新版本的函数\n execute: async (input: unknown) => toolsRef.current[name](input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'component', scopeId: scopeIdRef.current },\n );\n\n registeredNames.push(name);\n }\n\n if (registeredNames.length > 0) {\n notifyToolsChanged();\n }\n\n // 组件卸载时释放 owner;最后一个 owner 才会触发原生 abort。\n return () => {\n let unregisteredAny = false;\n for (const name of registeredNames) {\n const shouldUnregister = unregisterScopedTool(name, {\n scope: 'component',\n scopeId: scopeIdRef.current,\n });\n if (shouldUnregister) {\n unregisteredAny = true;\n }\n }\n if (unregisteredAny) {\n notifyToolsChanged();\n }\n };\n }, [toolKeys, localHmrVersion]); // 工具集合变化或 HMR 更新时重新注册\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/registry.ts","../src/polyfill.ts","../src/registerGlobalTools.ts","../src/useWebMcpTools.ts","../src/withWebMcpTools.tsx"],"sourcesContent":["// packages/webmcp-sdk/src/registry.ts\nimport type { WebMcpToolConfig } from './types';\n\nlet modelContextPatched = false;\n\n/**\n * 严格判断值是否为合法的 MCP CallToolResult。\n * 检查 content 数组存在且首元素含合法 type 字段,防止业务对象误判。\n */\nexport function isCallToolResult(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const obj = value as Record<string, unknown>;\n if (!Array.isArray(obj.content)) return false;\n if (obj.content.length === 0) return true;\n const first = obj.content[0];\n return (\n first !== null &&\n typeof first === 'object' &&\n 'type' in first &&\n ((first as Record<string, unknown>).type === 'text' ||\n (first as Record<string, unknown>).type === 'image' ||\n (first as Record<string, unknown>).type === 'resource')\n );\n}\n\n/**\n * 将工具原始返回值规范化为 MCP CallToolResult 格式。\n * 若已是合法 CallToolResult 则直接返回,否则将完整内容包装为 text content。\n * 注意:不对结果做任何截断,完整保留工具返回的全部数据。\n */\nexport function normalizeToCallToolResult(value: unknown): Record<string, unknown> {\n if (isCallToolResult(value)) {\n return value as Record<string, unknown>;\n }\n let text: string;\n if (typeof value === 'string') {\n text = value;\n } else {\n try {\n const serialized = JSON.stringify(value);\n text = serialized === undefined ? String(value) : serialized;\n } catch {\n text = String(value);\n }\n }\n return {\n content: [{ type: 'text', text }],\n isError: false,\n };\n}\n\n/**\n * 在 dispatchEvent 层面合并 toolchange 事件。\n * 无论来源(SDK 包装器、polyfill 内部、第三方代码),同一微任务内只触发 1 次。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction patchDispatchEventForCoalescing(mc: any): void {\n if (mc.__toolchangeCoalesced) return;\n const originalDispatch: (event: Event) => boolean =\n mc.dispatchEvent.bind(mc);\n let pending = false;\n mc.dispatchEvent = (event: Event): boolean => {\n if (event.type === 'toolchange') {\n if (!pending) {\n pending = true;\n queueMicrotask(() => {\n originalDispatch(new Event('toolchange'));\n pending = false;\n });\n }\n return true;\n }\n return originalDispatch(event);\n };\n mc.__toolchangeCoalesced = true;\n}\n\n/**\n * Patches navigator.modelContext to ensure embed.js can discover tools.\n *\n * Handles three scenarios:\n * 1. Chrome 146+ native / @mcp-b/webmcp-polyfill: modelContext lacks\n * listTools/callTool/EventTarget; modelContextTesting shim provides\n * listTools/executeTool\n * → Bridge listTools/callTool from modelContextTesting, add EventTarget,\n * wrap registerTool/unregisterTool to fire toolchange\n * 2. Older polyfills (e.g. @mcp-b/global before EventTarget support):\n * modelContext has listTools/callTool but lacks EventTarget\n * → Add EventTarget, wrap registerTool/unregisterTool to fire toolchange\n * 3. Full MCP-B environment: modelContext has everything → skip\n */\nexport function patchModelContextEventSupport(): void {\n if (modelContextPatched) {\n return;\n }\n if (\n typeof navigator === 'undefined' ||\n !('modelContext' in navigator) ||\n !navigator.modelContext\n ) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = navigator.modelContext as any;\n const hasEventTarget = typeof mc.addEventListener === 'function';\n const hasListTools = typeof mc.listTools === 'function';\n const hasCallTool = typeof mc.callTool === 'function';\n\n // Scenario 3: fully featured — only apply toolchange coalescing\n if (hasEventTarget && hasListTools && hasCallTool) {\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n return;\n }\n\n // Add EventTarget support if missing (Chrome native & polyfill)\n if (!hasEventTarget) {\n const listeners = new Map<string, Set<EventListener>>();\n\n mc.addEventListener = (type: string, callback: EventListener) => {\n if (!listeners.has(type)) listeners.set(type, new Set());\n listeners.get(type)!.add(callback);\n };\n\n mc.removeEventListener = (type: string, callback: EventListener) => {\n listeners.get(type)?.delete(callback);\n };\n\n mc.dispatchEvent = (event: Event): boolean => {\n const set = listeners.get(event.type);\n if (set) {\n set.forEach(fn => {\n try {\n fn.call(mc, event);\n } catch {\n // Ignore listener errors\n }\n });\n }\n return true;\n };\n }\n\n // Bridge listTools/callTool from modelContextTesting if missing (Chrome native)\n // Chrome 146+ has these methods on modelContextTesting but not on modelContext.\n // embed.js requires them on modelContext for getExtendedModelContext() to succeed.\n if (!hasListTools || !hasCallTool) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const testing = (navigator as any).modelContextTesting;\n if (testing) {\n if (\n typeof testing.listTools === 'function' &&\n !testing.__webmcpNexusListToolsPatched\n ) {\n const originalTestingListTools = testing.listTools.bind(testing);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n testing.listTools = (...args: any[]) => {\n const nativeTools = originalTestingListTools(...args);\n return Array.isArray(nativeTools)\n ? nativeTools.filter((tool: any) => {\n if (!managedToolNames.has(tool.name)) return true;\n return activeTools.has(tool.name);\n })\n : nativeTools;\n };\n testing.__webmcpNexusListToolsPatched = true;\n }\n if (!hasListTools && typeof testing.listTools === 'function') {\n // Convert testing format (inputSchema as JSON string) to extended format (inputSchema as object)\n mc.listTools = () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return testing.listTools().map((t: any) => ({\n name: t.name,\n description: t.description || '',\n inputSchema:\n typeof t.inputSchema === 'string' && t.inputSchema.length > 0\n ? JSON.parse(t.inputSchema)\n : { type: 'object', properties: {} },\n }));\n } catch {\n return [];\n }\n };\n }\n if (!hasCallTool && typeof testing.executeTool === 'function') {\n mc.callTool = async (params: { name: string; arguments?: Record<string, unknown> }) => {\n const result = await testing.executeTool(\n params.name,\n JSON.stringify(params.arguments || {}),\n );\n if (result === null) {\n return {\n isError: true,\n content: [{ type: 'text', text: 'Tool execution interrupted by navigation' }],\n };\n }\n try {\n const parsed = JSON.parse(result);\n return normalizeToCallToolResult(parsed);\n } catch {\n throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 500)}`);\n }\n };\n }\n }\n }\n\n // Wrap registerTool/unregisterTool to auto-fire 'toolchange'\n // For Chrome native: registerTool fires toolchange on modelContextTesting (not modelContext),\n // but embed.js subscribes on modelContext after our patch — so we need to bridge the event.\n // For polyfill: BrowserMcpServer doesn't fire any events, so wrapping is required.\n if (typeof mc.registerTool === 'function') {\n const originalRegisterTool = mc.registerTool.bind(mc);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mc.registerTool = (tool: any, options?: any) => {\n let registrationError: unknown;\n try {\n originalRegisterTool(tool, options);\n } catch (error) {\n registrationError = error;\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n if (registrationError) {\n throw registrationError;\n }\n };\n }\n\n if (typeof mc.unregisterTool === 'function') {\n const originalUnregisterTool = mc.unregisterTool.bind(mc);\n mc.unregisterTool = (name: string) => {\n try {\n originalUnregisterTool(name);\n } catch {\n // Swallow unregister errors\n }\n try {\n mc.dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n };\n }\n\n patchDispatchEventForCoalescing(mc);\n modelContextPatched = true;\n}\n\ntype ToolScope = 'global' | 'route' | 'component';\n\ninterface ToolEntry {\n name: string;\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ToolOwner {\n scope: ToolScope;\n scopeId: string;\n}\n\ninterface ActiveToolRecord {\n name: string;\n config: WebMcpToolConfig;\n configs: Map<string, WebMcpToolConfig>;\n controller: AbortController;\n owners: Map<string, ToolOwner>;\n nativeRegistered: boolean;\n}\n\nconst registry = new Map<string, ToolEntry[]>();\nconst activeTools = new Map<string, ActiveToolRecord>();\nconst managedToolNames = new Set<string>();\nlet hasManagedTools = false;\n\nfunction getOwnerKey(owner: ToolOwner): string {\n return `${owner.scope}:${owner.scopeId}`;\n}\n\n/** 获取当前活跃工具名列表(仅用于测试和内部同步) */\nexport function getActiveToolNames(): string[] {\n return Array.from(activeTools.keys());\n}\n\n/** 获取当前活跃工具配置列表(供 pushToolsToWidget 在 listTools 缺失时兜底) */\nexport function getActiveToolConfigs(): Array<{\n name: string;\n description: string;\n inputSchema: object;\n}> {\n return Array.from(activeTools.values()).map(record => ({\n name: record.name,\n description: record.config.description,\n inputSchema: record.config.inputSchema || { type: 'object', properties: {} },\n }));\n}\n\n/**\n * 在内部注册表中记录工具的所有权信息。\n * 如果同名工具已被其他 scope 注册,输出警告但仍允许注册。\n */\nexport function registerEntry(name: string, scope: ToolScope, scopeId: string): void {\n const entries = registry.get(name) || [];\n if (entries.length > 0) {\n const isSameScopeAndId = entries.some(e => e.scope === scope && e.scopeId === scopeId);\n if (!isSameScopeAndId) {\n const existing = entries[0];\n console.warn(\n `[webmcp] Tool \"${name}\" is already registered by scope \"${existing.scope}:${existing.scopeId}\". ` +\n `Re-registering from \"${scope}:${scopeId}\". This may cause unexpected behavior.`,\n );\n }\n }\n entries.push({ name, scope, scopeId });\n registry.set(name, entries);\n}\n\n/**\n * 从内部注册表中移除指定 scope 的工具所有权记录。\n * @returns true 表示可以安全调用 unregisterTool(最后一个持有者已移除);\n * false 表示还有其他 scope 持有该工具,不应注销。\n */\nexport function unregisterEntry(name: string, scope: ToolScope, scopeId: string): boolean {\n const entries = registry.get(name);\n if (!entries) return false;\n const idx = entries.findIndex(e => e.scope === scope && e.scopeId === scopeId);\n if (idx === -1) return false;\n entries.splice(idx, 1);\n if (entries.length === 0) {\n registry.delete(name);\n return true; // 可以安全注销\n }\n return false; // 其他 scope 仍持有该工具\n}\n\n/**\n * 清空注册表(仅用于测试)\n */\nexport function clearRegistry(): void {\n registry.clear();\n activeTools.clear();\n managedToolNames.clear();\n hasManagedTools = false;\n modelContextPatched = false;\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n}\n\n/**\n * 注册带 owner 的工具。\n * 同名工具只会原生注册一次;多个组件/作用域通过 owners 聚合生命周期。\n */\nexport function registerScopedTool(toolConfig: WebMcpToolConfig, owner: ToolOwner): void {\n try {\n const ownerKey = getOwnerKey(owner);\n hasManagedTools = true;\n managedToolNames.add(toolConfig.name);\n const existing = activeTools.get(toolConfig.name);\n if (existing) {\n existing.owners.set(ownerKey, owner);\n existing.configs.set(ownerKey, toolConfig);\n return;\n }\n\n const controller = new AbortController();\n const owners = new Map<string, ToolOwner>([[ownerKey, owner]]);\n const record: ActiveToolRecord = {\n name: toolConfig.name,\n config: toolConfig,\n configs: new Map([[ownerKey, toolConfig]]),\n controller,\n owners,\n nativeRegistered: false,\n };\n\n activeTools.set(toolConfig.name, record);\n\n const mc =\n typeof navigator !== 'undefined'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any)\n : undefined;\n\n controller.signal.addEventListener(\n 'abort',\n () => {\n try {\n // L2 fallback: 仅当 modelContext 不是带原生 signal 处理的实现时才调用 unregisterTool。\n // - Chrome 148+ 原生:移除了 unregisterTool,typeof === 'function' 为 false,自动跳过\n // - Chrome 146/147 原生:registerTool 不识别 signal,必须靠 unregisterTool 注销\n // - @mcp-b/webmcp-polyfill 2.x:registerTool 内部已 hook signal abort 自动删除 tool,\n // 再调 unregisterTool 既是双重操作,又会触发 polyfill 的 deprecation 警告。\n // 通过 __isWebMCPPolyfill 标记跳过这条路径。\n if (\n mc &&\n typeof mc.unregisterTool === 'function' &&\n !mc.__isWebMCPPolyfill\n ) {\n mc.unregisterTool(toolConfig.name);\n }\n } catch {\n // Ignore legacy fallback errors\n }\n try {\n activeTools.delete(toolConfig.name);\n notifyToolsChanged();\n } catch {\n // Ignore notification errors\n }\n },\n { once: true },\n );\n\n if (mc && typeof mc.registerTool === 'function') {\n const nativeToolConfig = {\n ...toolConfig,\n execute: async (input: Record<string, unknown>) => {\n const latestRecord = activeTools.get(toolConfig.name);\n return latestRecord?.config.execute(input);\n },\n };\n try {\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n } catch {\n try {\n if (typeof mc.unregisterTool === 'function') {\n mc.unregisterTool(toolConfig.name);\n mc.registerTool(nativeToolConfig, { signal: controller.signal });\n record.nativeRegistered = true;\n }\n } catch {\n // Keep the internal mirror so widget fallback still reflects SDK ownership.\n }\n }\n }\n } catch {\n // SDK public paths should never surface browser API errors.\n }\n}\n\n/**\n * 移除工具 owner。只有最后一个 owner 移除时才 abort 原生注册。\n * @returns true 表示工具列表发生了实际注销;false 表示仍有其他 owner。\n */\nexport function unregisterScopedTool(name: string, owner: ToolOwner): boolean {\n try {\n const record = activeTools.get(name);\n if (!record) {\n return false;\n }\n\n const ownerKey = getOwnerKey(owner);\n const removedConfig = record.configs.get(ownerKey);\n record.owners.delete(ownerKey);\n record.configs.delete(ownerKey);\n if (removedConfig && record.config === removedConfig) {\n const nextConfig = record.configs.values().next().value as WebMcpToolConfig | undefined;\n if (nextConfig) {\n record.config = nextConfig;\n }\n }\n if (record.owners.size > 0) return false;\n\n if (!record.controller.signal.aborted) {\n record.controller.abort();\n } else {\n activeTools.delete(name);\n notifyToolsChanged();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Debounce timer for pushing tools to widget iframe.\n */\nlet pushToolsTimer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Directly push the current tool list to any widget iframes via postMessage.\n * This bypasses Chrome's registerToolsChangedCallback mechanism which doesn't\n * fire on unregisterTool, ensuring the relay always has the correct tool list.\n */\nfunction pushToolsToWidget(): void {\n try {\n if (typeof document === 'undefined') return;\n\n const iframes = document.querySelectorAll('iframe');\n if (iframes.length === 0) return;\n\n const tools = getToolsForWidget();\n\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\n // embed.js(@mcp-b/webmcp-local-relay)已通过 toolchange 监听自行推送\n // relay iframe,跳过以避免重复 webmcp.tools.changed 消息\n if (iframe.hasAttribute('data-webmcp-relay')) continue;\n try {\n if (iframe.contentWindow) {\n iframe.contentWindow.postMessage(\n {\n type: 'webmcp.tools.changed',\n tools,\n },\n '*',\n );\n }\n } catch {\n // Cross-origin or other iframe access error, skip\n }\n }\n } catch {\n // Ignore errors\n }\n}\n\nfunction getToolsForWidget(): Array<{ name: string; description: string; inputSchema: object }> {\n if (hasManagedTools) {\n return getActiveToolConfigs();\n }\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mc = typeof navigator !== 'undefined' ? (navigator.modelContext as any) : undefined;\n if (mc && typeof mc.listTools === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tools = mc.listTools().map((tool: any) => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema:\n typeof tool.inputSchema === 'string' && tool.inputSchema.length > 0\n ? safeJsonParse(tool.inputSchema)\n : tool.inputSchema || { type: 'object', properties: {} },\n }));\n return tools;\n }\n } catch {\n // Fall through to activeTools mirror\n }\n return getActiveToolConfigs();\n}\n\nfunction safeJsonParse(value: string): object {\n try {\n return JSON.parse(value);\n } catch {\n return { type: 'object', properties: {} };\n }\n}\n\n/**\n * Schedule a debounced push of tools to widget iframes.\n * Uses 100ms delay to coalesce rapid register/unregister cycles\n * (e.g., during SPA navigation when old component unmounts and new one mounts).\n */\nfunction schedulePushToolsToWidget(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n }\n pushToolsTimer = setTimeout(() => {\n pushToolsTimer = null;\n pushToolsToWidget();\n }, 100);\n}\n\n/**\n * 通知工具列表已变化,发射标准 toolchange 事件并推送至 widget iframe。\n */\nexport function notifyToolsChanged(): void {\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) return;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (navigator.modelContext as any).dispatchEvent(new Event('toolchange'));\n } catch {\n // Ignore dispatch errors\n }\n\n schedulePushToolsToWidget();\n}\n\n/**\n * 安全注册工具,处理重复名称的情况。\n * 如果 registerTool 因重复名称抛错,则先 unregister 再重试。\n */\nexport function safeRegisterTool(toolConfig: WebMcpToolConfig): void {\n registerScopedTool(toolConfig, { scope: 'global', scopeId: 'safe-register' });\n}\n","// packages/webmcp-sdk/src/polyfill.ts\nimport { initializeWebMCPPolyfill, cleanupWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';\n\nlet attempted = false;\n\n/**\n * 惰性、幂等地确保 navigator.modelContext 存在。\n *\n * - 原生(Chrome 146+)/ 已被其他 polyfill 安装:直接返回,不动 navigator\n * - 非浏览器环境(SSR):直接返回\n * - 缺失 modelContext:调 initializeWebMCPPolyfill 装上严格 W3C 核心 + modelContextTesting shim\n *\n * 调用方应在判定 'modelContext' in navigator 之前调用本函数。整体包 try/catch,\n * polyfill 加载或初始化失败不向调用方传播异常——SDK 后续逻辑会按\"无 modelContext\"路径继续 no-op。\n */\nexport function ensureModelContextPolyfill(): void {\n if (attempted) return;\n attempted = true;\n if (typeof navigator === 'undefined') return;\n if ('modelContext' in navigator) return;\n try {\n initializeWebMCPPolyfill({ installTestingShim: true });\n } catch {\n // polyfill 初始化失败兜底\n }\n}\n\n/**\n * 仅供单元测试使用:重置模块级 attempted 标志并卸载 polyfill。\n * 生产代码不应调用本函数。\n */\nexport function __resetPolyfillStateForTest(): void {\n attempted = false;\n try {\n cleanupWebMCPPolyfill();\n } catch {\n // cleanup 失败兜底\n }\n}\n","// packages/webmcp-sdk/src/registerGlobalTools.ts\nimport {\n registerEntry,\n registerScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/**\n * 全局注册 WebMCP 工具。应用启动时调用一次。\n * 支持可变参数,兼容 import * as module 批量导入。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * import * as userApi from './api/user';\n * import * as productApi from './api/product';\n * registerGlobalTools(userApi, productApi);\n *\n * @example\n * registerGlobalTools({ getUser, searchUsers }, { searchProducts });\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function registerGlobalTools(...toolMaps: Record<string, Function>[]): void {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n try {\n patchModelContextEventSupport();\n\n let registeredCount = 0;\n\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n // 跳过非函数值(如 TypeScript 类型导出在运行时可能不存在)\n if (typeof fn !== 'function') continue;\n\n // __webmcpSchema 由 Vite 插件在构建时注入\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerEntry(name, 'global', 'app');\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n execute: async (input: unknown) => fn(input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'global', scopeId: 'app' },\n );\n registeredCount++;\n }\n }\n\n if (registeredCount > 0) {\n notifyToolsChanged();\n }\n } catch {\n // 全局兜底:SDK 入口不向调用方传播浏览器 API 异常\n }\n}\n","// packages/webmcp-sdk/src/useWebMcpTools.ts\nimport { useEffect, useRef, useState } from 'react';\nimport {\n registerScopedTool,\n unregisterScopedTool,\n notifyToolsChanged,\n patchModelContextEventSupport,\n} from './registry';\nimport { ensureModelContextPolyfill } from './polyfill';\nimport type { WebMcpAnnotatedFn } from './types';\n\nlet scopeCounter = 0;\nfunction generateScopeId(): string {\n return `component-${++scopeCounter}`;\n}\n\n// HMR support: track a global version counter that increments on hot updates.\n// Function body updates are already handled via toolsRef.current indirect calls.\n// This counter ensures schema changes (__webmcpSchema) trigger re-registration.\nlet hmrVersion = 0;\nconst hmrHot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\nif (hmrHot && typeof hmrHot.on === 'function') {\n hmrHot.on('vite:afterUpdate', () => {\n hmrVersion++;\n });\n}\n\n/**\n * React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。\n * mount 时注册,unmount 时自动注销。\n *\n * 使用 useRef 持有最新函数引用,避免闭包陷阱。\n * 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,\n * 当工具集合变化时重新注册,函数体变化不触发重新注册。\n *\n * @param toolMaps - 一个或多个 Record<string, Function> 对象\n *\n * @example\n * // 组件级注册\n * useWebMcpTools({ searchInPanel, clearSearch });\n *\n * @example\n * // 路由级注册(配合 React Router 使用)\n * useWebMcpTools({ setUserFilter });\n */\nexport function useWebMcpTools(...toolMaps: Record<string, Function>[]): void {\n // 合并所有 toolMap 为一个对象\n const merged: Record<string, Function> = {};\n for (const toolMap of toolMaps) {\n for (const [name, fn] of Object.entries(toolMap)) {\n if (typeof fn === 'function') {\n merged[name] = fn;\n }\n }\n }\n\n // 用 ref 持有最新的函数引用,避免闭包陷阱\n const toolsRef = useRef(merged);\n toolsRef.current = merged;\n\n // 生成唯一 scopeId,确保同一组件实例的 scopeId 一致\n const scopeIdRef = useRef<string>('');\n if (!scopeIdRef.current) {\n scopeIdRef.current = generateScopeId();\n }\n\n // 工具名集合作为依赖 — 工具集合变化时重新注册\n const toolKeys = Object.keys(merged).sort().join(',');\n\n // DEV: listen for HMR updates to force re-registration when schema changes\n const [localHmrVersion, setLocalHmrVersion] = useState(hmrVersion);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setLocalHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n useEffect(() => {\n ensureModelContextPolyfill();\n if (typeof navigator === 'undefined' || !('modelContext' in navigator)) {\n return;\n }\n\n patchModelContextEventSupport();\n\n const registeredNames: string[] = [];\n\n for (const [name, fn] of Object.entries(toolsRef.current)) {\n const schema = (fn as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n registerScopedTool(\n {\n name,\n description: schema.description,\n inputSchema: schema.inputSchema,\n // 通过 ref 间接调用,保证始终执行最新版本的函数\n execute: async (input: unknown) => toolsRef.current[name](input),\n annotations: { readOnlyHint: schema.readOnly ?? false },\n },\n { scope: 'component', scopeId: scopeIdRef.current },\n );\n\n registeredNames.push(name);\n }\n\n if (registeredNames.length > 0) {\n notifyToolsChanged();\n }\n\n // 组件卸载时释放 owner;最后一个 owner 才会触发原生 abort。\n return () => {\n let unregisteredAny = false;\n for (const name of registeredNames) {\n const shouldUnregister = unregisterScopedTool(name, {\n scope: 'component',\n scopeId: scopeIdRef.current,\n });\n if (shouldUnregister) {\n unregisteredAny = true;\n }\n }\n if (unregisteredAny) {\n notifyToolsChanged();\n }\n };\n }, [toolKeys, localHmrVersion]); // 工具集合变化或 HMR 更新时重新注册\n}\n","// packages/webmcp-sdk/src/withWebMcpTools.tsx\nimport React, { useRef, useMemo, useState, useEffect } from 'react';\nimport { useWebMcpTools } from './useWebMcpTools';\nimport type { WebMcpAnnotatedFn } from './types';\n\n/** React 生命周期方法黑名单 */\nconst LIFECYCLE_METHODS = new Set([\n 'constructor',\n 'render',\n 'componentDidMount',\n 'componentDidUpdate',\n 'componentWillUnmount',\n 'shouldComponentUpdate',\n 'getSnapshotBeforeUpdate',\n 'componentDidCatch',\n]);\n\n/**\n * 高阶组件:将 class 组件的方法注册为 WebMCP 工具。\n * mount 时注册,unmount 时自动注销。\n *\n * 支持两种方法形式:\n * - 原型方法:`methodName(params: T) { ... }` — schema 挂在 prototype 上\n * - Class field 箭头函数:`methodName = (params: T) => { ... }` — schema 挂在静态属性上\n *\n * @param WrappedComponent - React class 组件\n * @param methodNames - 可选,显式指定要注册的方法名列表\n *\n * @example\n * class MyPanel extends React.Component {\n * /\\** 搜索面板 @readonly *\\/\n * searchInPanel(params: { query: string }) { ... }\n *\n * /\\** 清除搜索 *\\/\n * clearSearch = (params: { confirm?: boolean }) => { ... };\n *\n * render() { return <div />; }\n * }\n * export default withWebMcpTools(MyPanel);\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function withWebMcpTools<P extends Record<string, any>>(\n WrappedComponent: React.ComponentClass<P>,\n methodNames?: string[],\n): React.ComponentType<P> {\n const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';\n\n function WebMcpToolsWrapper(props: P) {\n const instanceRef = useRef<InstanceType<typeof WrappedComponent> | null>(null);\n\n // HMR 版本追踪:schema 更新时重建 toolMap\n const [hmrVersion, setHmrVersion] = useState(0);\n useEffect(() => {\n const hot = typeof import.meta !== 'undefined' ? import.meta.hot : undefined;\n if (hot && typeof hot.on === 'function') {\n const handler = () => setHmrVersion(v => v + 1);\n hot.on('vite:afterUpdate', handler);\n return () => {\n if (typeof hot.off === 'function') {\n hot.off('vite:afterUpdate', handler);\n }\n };\n }\n }, []);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const toolMap = useMemo((): Record<string, any> => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const map: Record<string, any> = {};\n const candidates = methodNames ? new Set(methodNames) : null;\n const proto = WrappedComponent.prototype;\n\n // 来源 1:原型方法(__webmcpSchema 挂在 prototype 方法上)\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (LIFECYCLE_METHODS.has(name)) continue;\n if (candidates && !candidates.has(name)) continue;\n const method = proto[name];\n if (typeof method !== 'function') continue;\n const schema = (method as WebMcpAnnotatedFn).__webmcpSchema;\n if (!schema) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapper: any = (input: unknown) => (instanceRef.current as any)?.[name](input);\n wrapper.__webmcpSchema = schema;\n map[name] = wrapper;\n }\n\n // 来源 2:class field 箭头函数(schema 挂在静态属性 __webmcpFieldSchemas 上)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldSchemas = (WrappedComponent as any).__webmcpFieldSchemas as\n | Record<string, { description: string; inputSchema: object; readOnly?: boolean }>\n | undefined;\n if (fieldSchemas) {\n for (const [name, schema] of Object.entries(fieldSchemas)) {\n if (candidates && !candidates.has(name)) continue;\n if (map[name]) continue; // 原型方法优先\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapper: any = (input: unknown) => (instanceRef.current as any)?.[name](input);\n wrapper.__webmcpSchema = schema;\n map[name] = wrapper;\n }\n }\n\n return map;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hmrVersion]);\n\n useWebMcpTools(toolMap);\n\n return <WrappedComponent ref={instanceRef} {...props} />;\n }\n\n WebMcpToolsWrapper.displayName = `withWebMcpTools(${displayName})`;\n return WebMcpToolsWrapper;\n}\n"],"mappings":"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","names":["useRef","useState","useEffect","useRef","hmrVersion","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmcp-nexus-sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "SDK for WebMCP Nexus - register and expose browser-side tools for AI agents via Model Context Protocol",
5
5
  "license": "MIT",
6
6
  "keywords": [