webmcp-nexus-sdk 0.1.10 → 0.1.12

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/dist/index.cjs CHANGED
@@ -27,6 +27,25 @@ module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/registry.ts
29
29
  var modelContextPatched = false;
30
+ function patchDispatchEventForCoalescing(mc) {
31
+ if (mc.__toolchangeCoalesced) return;
32
+ const originalDispatch = mc.dispatchEvent.bind(mc);
33
+ let pending = false;
34
+ mc.dispatchEvent = (event) => {
35
+ if (event.type === "toolchange") {
36
+ if (!pending) {
37
+ pending = true;
38
+ queueMicrotask(() => {
39
+ originalDispatch(new Event("toolchange"));
40
+ pending = false;
41
+ });
42
+ }
43
+ return true;
44
+ }
45
+ return originalDispatch(event);
46
+ };
47
+ mc.__toolchangeCoalesced = true;
48
+ }
30
49
  function patchModelContextEventSupport() {
31
50
  if (modelContextPatched) {
32
51
  return;
@@ -39,6 +58,7 @@ function patchModelContextEventSupport() {
39
58
  const hasListTools = typeof mc.listTools === "function";
40
59
  const hasCallTool = typeof mc.callTool === "function";
41
60
  if (hasEventTarget && hasListTools && hasCallTool) {
61
+ patchDispatchEventForCoalescing(mc);
42
62
  modelContextPatched = true;
43
63
  return;
44
64
  }
@@ -143,6 +163,7 @@ function patchModelContextEventSupport() {
143
163
  }
144
164
  };
145
165
  }
166
+ patchDispatchEventForCoalescing(mc);
146
167
  modelContextPatched = true;
147
168
  }
148
169
  var registry = /* @__PURE__ */ new Map();
@@ -260,10 +281,9 @@ function unregisterScopedTool(name, owner) {
260
281
  if (record.owners.size > 0) return false;
261
282
  if (!record.controller.signal.aborted) {
262
283
  record.controller.abort();
263
- notifyToolsChanged({ immediate: true });
264
284
  } else {
265
285
  activeTools.delete(name);
266
- notifyToolsChanged({ immediate: true });
286
+ notifyToolsChanged();
267
287
  }
268
288
  return true;
269
289
  } catch {
@@ -273,11 +293,13 @@ function unregisterScopedTool(name, owner) {
273
293
  var pushToolsTimer = null;
274
294
  function pushToolsToWidget() {
275
295
  try {
276
- const tools = getToolsForWidget();
277
296
  if (typeof document === "undefined") return;
278
297
  const iframes = document.querySelectorAll("iframe");
298
+ if (iframes.length === 0) return;
299
+ const tools = getToolsForWidget();
279
300
  for (let i = 0; i < iframes.length; i++) {
280
301
  const iframe = iframes[i];
302
+ if (iframe.hasAttribute("data-webmcp-relay")) continue;
281
303
  try {
282
304
  if (iframe.contentWindow) {
283
305
  iframe.contentWindow.postMessage(
@@ -328,24 +350,13 @@ function schedulePushToolsToWidget() {
328
350
  pushToolsToWidget();
329
351
  }, 100);
330
352
  }
331
- function pushToolsToWidgetImmediately() {
332
- if (pushToolsTimer) {
333
- clearTimeout(pushToolsTimer);
334
- pushToolsTimer = null;
335
- }
336
- pushToolsToWidget();
337
- }
338
- function notifyToolsChanged(options = {}) {
353
+ function notifyToolsChanged() {
339
354
  if (typeof navigator === "undefined" || !("modelContext" in navigator)) return;
340
355
  try {
341
- navigator.modelContext.dispatchEvent(new Event("toolschanged"));
356
+ navigator.modelContext.dispatchEvent(new Event("toolchange"));
342
357
  } catch {
343
358
  }
344
- if (options.immediate) {
345
- pushToolsToWidgetImmediately();
346
- } else {
347
- schedulePushToolsToWidget();
348
- }
359
+ schedulePushToolsToWidget();
349
360
  }
350
361
 
351
362
  // src/polyfill.ts
@@ -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 * 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 — nothing to patch\n if (hasEventTarget && hasListTools && hasCallTool) {\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 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 notifyToolsChanged({ immediate: true });\n } else {\n activeTools.delete(name);\n notifyToolsChanged({ immediate: true });\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 const tools = getToolsForWidget();\n\n if (typeof document === 'undefined') return;\n\n // Find all iframes and send updated tool list\n // The widget iframe listens for 'webmcp.tools.changed' messages\n const iframes = document.querySelectorAll('iframe');\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\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\nfunction pushToolsToWidgetImmediately(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n pushToolsToWidget();\n}\n\n/**\n * 通知 MCP relay 工具列表已变化。\n * 发射 W3C 规范事件名 (toolschanged),toolchange 由 registerTool/unregisterTool 包装器负责。\n */\nexport function notifyToolsChanged(options: { immediate?: boolean } = {}): 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('toolschanged'));\n } catch {\n // Ignore dispatch errors\n }\n\n // Direct push to widget iframe as fallback for Chrome native environment\n // where registerToolsChangedCallback doesn't fire on unregisterTool\n if (options.immediate) {\n pushToolsToWidgetImmediately();\n } else {\n schedulePushToolsToWidget();\n }\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGA,IAAI,sBAAsB;AAgBnB,SAAS,gCAAsC;AACpD,MAAI,qBAAqB;AACvB;AAAA,EACF;AACA,MACE,OAAO,cAAc,eACrB,EAAE,kBAAkB,cACpB,CAAC,UAAU,cACX;AACA;AAAA,EACF;AAGA,QAAM,KAAK,UAAU;AACrB,QAAM,iBAAiB,OAAO,GAAG,qBAAqB;AACtD,QAAM,eAAe,OAAO,GAAG,cAAc;AAC7C,QAAM,cAAc,OAAO,GAAG,aAAa;AAG3C,MAAI,kBAAkB,gBAAgB,aAAa;AACjD,0BAAsB;AACtB;AAAA,EACF;AAGA,MAAI,CAAC,gBAAgB;AACnB,UAAM,YAAY,oBAAI,IAAgC;AAEtD,OAAG,mBAAmB,CAAC,MAAc,aAA4B;AAC/D,UAAI,CAAC,UAAU,IAAI,IAAI,EAAG,WAAU,IAAI,MAAM,oBAAI,IAAI,CAAC;AACvD,gBAAU,IAAI,IAAI,EAAG,IAAI,QAAQ;AAAA,IACnC;AAEA,OAAG,sBAAsB,CAAC,MAAc,aAA4B;AAClE,gBAAU,IAAI,IAAI,GAAG,OAAO,QAAQ;AAAA,IACtC;AAEA,OAAG,gBAAgB,CAAC,UAA0B;AAC5C,YAAM,MAAM,UAAU,IAAI,MAAM,IAAI;AACpC,UAAI,KAAK;AACP,YAAI,QAAQ,QAAM;AAChB,cAAI;AACF,eAAG,KAAK,IAAI,KAAK;AAAA,UACnB,QAAQ;AAAA,UAER;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAKA,MAAI,CAAC,gBAAgB,CAAC,aAAa;AAEjC,UAAM,UAAW,UAAkB;AACnC,QAAI,SAAS;AACX,UACE,OAAO,QAAQ,cAAc,cAC7B,CAAC,QAAQ,+BACT;AACA,cAAM,2BAA2B,QAAQ,UAAU,KAAK,OAAO;AAE/D,gBAAQ,YAAY,IAAI,SAAgB;AACtC,gBAAM,cAAc,yBAAyB,GAAG,IAAI;AACpD,iBAAO,MAAM,QAAQ,WAAW,IAC5B,YAAY,OAAO,CAAC,SAAc;AAChC,gBAAI,CAAC,iBAAiB,IAAI,KAAK,IAAI,EAAG,QAAO;AAC7C,mBAAO,YAAY,IAAI,KAAK,IAAI;AAAA,UAClC,CAAC,IACD;AAAA,QACN;AACA,gBAAQ,gCAAgC;AAAA,MAC1C;AACA,UAAI,CAAC,gBAAgB,OAAO,QAAQ,cAAc,YAAY;AAE5D,WAAG,YAAY,MAAM;AACnB,cAAI;AAEF,mBAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,OAAY;AAAA,cAC1C,MAAM,EAAE;AAAA,cACR,aAAa,EAAE,eAAe;AAAA,cAC9B,aACE,OAAO,EAAE,gBAAgB,YAAY,EAAE,YAAY,SAAS,IACxD,KAAK,MAAM,EAAE,WAAW,IACxB,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,YACzC,EAAE;AAAA,UACJ,QAAQ;AACN,mBAAO,CAAC;AAAA,UACV;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,eAAe,OAAO,QAAQ,gBAAgB,YAAY;AAC7D,WAAG,WAAW,OAAO,WAAkE;AACrF,gBAAM,SAAS,MAAM,QAAQ;AAAA,YAC3B,OAAO;AAAA,YACP,KAAK,UAAU,OAAO,aAAa,CAAC,CAAC;AAAA,UACvC;AACA,cAAI,WAAW,MAAM;AACnB,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,2CAA2C,CAAC;AAAA,YAC9E;AAAA,UACF;AACA,cAAI;AACF,mBAAO,KAAK,MAAM,MAAM;AAAA,UAC1B,QAAQ;AACN,kBAAM,IAAI,MAAM,+BAA+B,OAAO,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,UAC/E;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAMA,MAAI,OAAO,GAAG,iBAAiB,YAAY;AACzC,UAAM,uBAAuB,GAAG,aAAa,KAAK,EAAE;AAEpD,OAAG,eAAe,CAAC,MAAW,YAAkB;AAC9C,UAAI;AACJ,UAAI;AACF,6BAAqB,MAAM,OAAO;AAAA,MACpC,SAAS,OAAO;AACd,4BAAoB;AAAA,MACtB;AACA,UAAI;AACF,WAAG,cAAc,IAAI,MAAM,YAAY,CAAC;AAAA,MAC1C,QAAQ;AAAA,MAER;AACA,UAAI,mBAAmB;AACrB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,GAAG,mBAAmB,YAAY;AAC3C,UAAM,yBAAyB,GAAG,eAAe,KAAK,EAAE;AACxD,OAAG,iBAAiB,CAAC,SAAiB;AACpC,UAAI;AACF,+BAAuB,IAAI;AAAA,MAC7B,QAAQ;AAAA,MAER;AACA,UAAI;AACF,WAAG,cAAc,IAAI,MAAM,YAAY,CAAC;AAAA,MAC1C,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,wBAAsB;AACxB;AAwBA,IAAM,WAAW,oBAAI,IAAyB;AAC9C,IAAM,cAAc,oBAAI,IAA8B;AACtD,IAAM,mBAAmB,oBAAI,IAAY;AACzC,IAAI,kBAAkB;AAEtB,SAAS,YAAY,OAA0B;AAC7C,SAAO,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO;AACxC;AAQO,SAAS,uBAIb;AACD,SAAO,MAAM,KAAK,YAAY,OAAO,CAAC,EAAE,IAAI,aAAW;AAAA,IACrD,MAAM,OAAO;AAAA,IACb,aAAa,OAAO,OAAO;AAAA,IAC3B,aAAa,OAAO,OAAO,eAAe,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,EAC7E,EAAE;AACJ;AAMO,SAAS,cAAc,MAAc,OAAkB,SAAuB;AACnF,QAAM,UAAU,SAAS,IAAI,IAAI,KAAK,CAAC;AACvC,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,mBAAmB,QAAQ,KAAK,OAAK,EAAE,UAAU,SAAS,EAAE,YAAY,OAAO;AACrF,QAAI,CAAC,kBAAkB;AACrB,YAAM,WAAW,QAAQ,CAAC;AAC1B,cAAQ;AAAA,QACN,kBAAkB,IAAI,qCAAqC,SAAS,KAAK,IAAI,SAAS,OAAO,2BACnE,KAAK,IAAI,OAAO;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,UAAQ,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AACrC,WAAS,IAAI,MAAM,OAAO;AAC5B;AAuCO,SAAS,mBAAmB,YAA8B,OAAwB;AACvF,MAAI;AACF,UAAM,WAAW,YAAY,KAAK;AAClC,sBAAkB;AAClB,qBAAiB,IAAI,WAAW,IAAI;AACpC,UAAM,WAAW,YAAY,IAAI,WAAW,IAAI;AAChD,QAAI,UAAU;AACZ,eAAS,OAAO,IAAI,UAAU,KAAK;AACnC,eAAS,QAAQ,IAAI,UAAU,UAAU;AACzC;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,SAAS,oBAAI,IAAuB,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC;AAC7D,UAAM,SAA2B;AAAA,MAC/B,MAAM,WAAW;AAAA,MACjB,QAAQ;AAAA,MACR,SAAS,oBAAI,IAAI,CAAC,CAAC,UAAU,UAAU,CAAC,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,IACpB;AAEA,gBAAY,IAAI,WAAW,MAAM,MAAM;AAEvC,UAAM,KACJ,OAAO,cAAc;AAAA;AAAA,MAEhB,UAAU;AAAA,QACX;AAEN,eAAW,OAAO;AAAA,MAChB;AAAA,MACA,MAAM;AACJ,YAAI;AAOF,cACE,MACA,OAAO,GAAG,mBAAmB,cAC7B,CAAC,GAAG,oBACJ;AACA,eAAG,eAAe,WAAW,IAAI;AAAA,UACnC;AAAA,QACF,QAAQ;AAAA,QAER;AACA,YAAI;AACF,sBAAY,OAAO,WAAW,IAAI;AAClC,6BAAmB;AAAA,QACrB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACf;AAEA,QAAI,MAAM,OAAO,GAAG,iBAAiB,YAAY;AAC/C,YAAM,mBAAmB;AAAA,QACvB,GAAG;AAAA,QACH,SAAS,OAAO,UAAmC;AACjD,gBAAM,eAAe,YAAY,IAAI,WAAW,IAAI;AACpD,iBAAO,cAAc,OAAO,QAAQ,KAAK;AAAA,QAC3C;AAAA,MACF;AACA,UAAI;AACF,WAAG,aAAa,kBAAkB,EAAE,QAAQ,WAAW,OAAO,CAAC;AAC/D,eAAO,mBAAmB;AAAA,MAC5B,QAAQ;AACN,YAAI;AACF,cAAI,OAAO,GAAG,mBAAmB,YAAY;AAC3C,eAAG,eAAe,WAAW,IAAI;AACjC,eAAG,aAAa,kBAAkB,EAAE,QAAQ,WAAW,OAAO,CAAC;AAC/D,mBAAO,mBAAmB;AAAA,UAC5B;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,qBAAqB,MAAc,OAA2B;AAC5E,MAAI;AACF,UAAM,SAAS,YAAY,IAAI,IAAI;AACnC,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,YAAY,KAAK;AAClC,UAAM,gBAAgB,OAAO,QAAQ,IAAI,QAAQ;AACjD,WAAO,OAAO,OAAO,QAAQ;AAC7B,WAAO,QAAQ,OAAO,QAAQ;AAC9B,QAAI,iBAAiB,OAAO,WAAW,eAAe;AACpD,YAAM,aAAa,OAAO,QAAQ,OAAO,EAAE,KAAK,EAAE;AAClD,UAAI,YAAY;AACd,eAAO,SAAS;AAAA,MAClB;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,EAAG,QAAO;AAEnC,QAAI,CAAC,OAAO,WAAW,OAAO,SAAS;AACrC,aAAO,WAAW,MAAM;AACxB,yBAAmB,EAAE,WAAW,KAAK,CAAC;AAAA,IACxC,OAAO;AACL,kBAAY,OAAO,IAAI;AACvB,yBAAmB,EAAE,WAAW,KAAK,CAAC;AAAA,IACxC;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,IAAI,iBAAuD;AAO3D,SAAS,oBAA0B;AACjC,MAAI;AACF,UAAM,QAAQ,kBAAkB;AAEhC,QAAI,OAAO,aAAa,YAAa;AAIrC,UAAM,UAAU,SAAS,iBAAiB,QAAQ;AAClD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI;AACF,YAAI,OAAO,eAAe;AACxB,iBAAO,cAAc;AAAA,YACnB;AAAA,cACE,MAAM;AAAA,cACN;AAAA,YACF;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,oBAAuF;AAC9F,MAAI,iBAAiB;AACnB,WAAO,qBAAqB;AAAA,EAC9B;AACA,MAAI;AAEF,UAAM,KAAK,OAAO,cAAc,cAAe,UAAU,eAAuB;AAChF,QAAI,MAAM,OAAO,GAAG,cAAc,YAAY;AAE5C,YAAM,QAAQ,GAAG,UAAU,EAAE,IAAI,CAAC,UAAe;AAAA,QAC/C,MAAM,KAAK;AAAA,QACX,aAAa,KAAK,eAAe;AAAA,QACjC,aACE,OAAO,KAAK,gBAAgB,YAAY,KAAK,YAAY,SAAS,IAC9D,cAAc,KAAK,WAAW,IAC9B,KAAK,eAAe,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,MAC7D,EAAE;AACF,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,qBAAqB;AAC9B;AAEA,SAAS,cAAc,OAAuB;AAC5C,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,EAC1C;AACF;AAOA,SAAS,4BAAkC;AACzC,MAAI,gBAAgB;AAClB,iBAAa,cAAc;AAAA,EAC7B;AACA,mBAAiB,WAAW,MAAM;AAChC,qBAAiB;AACjB,sBAAkB;AAAA,EACpB,GAAG,GAAG;AACR;AAEA,SAAS,+BAAqC;AAC5C,MAAI,gBAAgB;AAClB,iBAAa,cAAc;AAC3B,qBAAiB;AAAA,EACnB;AACA,oBAAkB;AACpB;AAMO,SAAS,mBAAmB,UAAmC,CAAC,GAAS;AAC9E,MAAI,OAAO,cAAc,eAAe,EAAE,kBAAkB,WAAY;AACxE,MAAI;AAEF,IAAC,UAAU,aAAqB,cAAc,IAAI,MAAM,cAAc,CAAC;AAAA,EACzE,QAAQ;AAAA,EAER;AAIA,MAAI,QAAQ,WAAW;AACrB,iCAA6B;AAAA,EAC/B,OAAO;AACL,8BAA0B;AAAA,EAC5B;AACF;;;AC7gBA,6BAAgE;AAEhE,IAAI,YAAY;AAYT,SAAS,6BAAmC;AACjD,MAAI,UAAW;AACf,cAAY;AACZ,MAAI,OAAO,cAAc,YAAa;AACtC,MAAI,kBAAkB,UAAW;AACjC,MAAI;AACF,yDAAyB,EAAE,oBAAoB,KAAK,CAAC;AAAA,EACvD,QAAQ;AAAA,EAER;AACF;;;ACAO,SAAS,uBAAuB,UAA4C;AACjF,6BAA2B;AAC3B,MAAI,OAAO,cAAc,eAAe,EAAE,kBAAkB,YAAY;AACtE;AAAA,EACF;AAEA,MAAI;AACF,kCAA8B;AAE9B,QAAI,kBAAkB;AAEtB,eAAW,WAAW,UAAU;AAC9B,iBAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,OAAO,GAAG;AAEhD,YAAI,OAAO,OAAO,WAAY;AAG9B,cAAM,SAAU,GAAyB;AACzC,YAAI,CAAC,OAAQ;AAEb,sBAAc,MAAM,UAAU,KAAK;AACnC;AAAA,UACE;AAAA,YACE;AAAA,YACA,aAAa,OAAO;AAAA,YACpB,aAAa,OAAO;AAAA,YACpB,SAAS,OAAO,UAAmB,GAAG,KAAK;AAAA,YAC3C,aAAa,EAAE,cAAc,OAAO,YAAY,MAAM;AAAA,UACxD;AAAA,UACA,EAAE,OAAO,UAAU,SAAS,MAAM;AAAA,QACpC;AACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,kBAAkB,GAAG;AACvB,yBAAmB;AAAA,IACrB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;ACjEA,mBAA4C;AAD5C;AAWA,IAAI,eAAe;AACnB,SAAS,kBAA0B;AACjC,SAAO,aAAa,EAAE,YAAY;AACpC;AAKA,IAAI,aAAa;AACjB,IAAM,SAAS,OAAO,gBAAgB,cAAc,YAAY,MAAM;AACtE,IAAI,UAAU,OAAO,OAAO,OAAO,YAAY;AAC7C,SAAO,GAAG,oBAAoB,MAAM;AAClC;AAAA,EACF,CAAC;AACH;AAoBO,SAAS,kBAAkB,UAA4C;AAE5E,QAAM,SAAmC,CAAC;AAC1C,aAAW,WAAW,UAAU;AAC9B,eAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,OAAO,GAAG;AAChD,UAAI,OAAO,OAAO,YAAY;AAC5B,eAAO,IAAI,IAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,eAAW,qBAAO,MAAM;AAC9B,WAAS,UAAU;AAGnB,QAAM,iBAAa,qBAAe,EAAE;AACpC,MAAI,CAAC,WAAW,SAAS;AACvB,eAAW,UAAU,gBAAgB;AAAA,EACvC;AAGA,QAAM,WAAW,OAAO,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG;AAGpD,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,uBAAS,UAAU;AACjE,8BAAU,MAAM;AACd,UAAM,MAAM,OAAO,gBAAgB,cAAc,YAAY,MAAM;AACnE,QAAI,OAAO,OAAO,IAAI,OAAO,YAAY;AACvC,YAAM,UAAU,MAAM,mBAAmB,OAAK,IAAI,CAAC;AACnD,UAAI,GAAG,oBAAoB,OAAO;AAClC,aAAO,MAAM;AACX,YAAI,OAAO,IAAI,QAAQ,YAAY;AACjC,cAAI,IAAI,oBAAoB,OAAO;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,8BAAU,MAAM;AACd,+BAA2B;AAC3B,QAAI,OAAO,cAAc,eAAe,EAAE,kBAAkB,YAAY;AACtE;AAAA,IACF;AAEA,kCAA8B;AAE9B,UAAM,kBAA4B,CAAC;AAEnC,eAAW,CAAC,MAAM,EAAE,KAAK,OAAO,QAAQ,SAAS,OAAO,GAAG;AACzD,YAAM,SAAU,GAAyB;AACzC,UAAI,CAAC,OAAQ;AAEb;AAAA,QACE;AAAA,UACE;AAAA,UACA,aAAa,OAAO;AAAA,UACpB,aAAa,OAAO;AAAA;AAAA,UAEpB,SAAS,OAAO,UAAmB,SAAS,QAAQ,IAAI,EAAE,KAAK;AAAA,UAC/D,aAAa,EAAE,cAAc,OAAO,YAAY,MAAM;AAAA,QACxD;AAAA,QACA,EAAE,OAAO,aAAa,SAAS,WAAW,QAAQ;AAAA,MACpD;AAEA,sBAAgB,KAAK,IAAI;AAAA,IAC3B;AAEA,QAAI,gBAAgB,SAAS,GAAG;AAC9B,yBAAmB;AAAA,IACrB;AAGA,WAAO,MAAM;AACX,UAAI,kBAAkB;AACtB,iBAAW,QAAQ,iBAAiB;AAClC,cAAM,mBAAmB,qBAAqB,MAAM;AAAA,UAClD,OAAO;AAAA,UACP,SAAS,WAAW;AAAA,QACtB,CAAC;AACD,YAAI,kBAAkB;AACpB,4BAAkB;AAAA,QACpB;AAAA,MACF;AACA,UAAI,iBAAiB;AACnB,2BAAmB;AAAA,MACrB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAU,eAAe,CAAC;AAChC;","names":[]}
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":[]}
package/dist/index.js CHANGED
@@ -1,5 +1,24 @@
1
1
  // src/registry.ts
2
2
  var modelContextPatched = false;
3
+ function patchDispatchEventForCoalescing(mc) {
4
+ if (mc.__toolchangeCoalesced) return;
5
+ const originalDispatch = mc.dispatchEvent.bind(mc);
6
+ let pending = false;
7
+ mc.dispatchEvent = (event) => {
8
+ if (event.type === "toolchange") {
9
+ if (!pending) {
10
+ pending = true;
11
+ queueMicrotask(() => {
12
+ originalDispatch(new Event("toolchange"));
13
+ pending = false;
14
+ });
15
+ }
16
+ return true;
17
+ }
18
+ return originalDispatch(event);
19
+ };
20
+ mc.__toolchangeCoalesced = true;
21
+ }
3
22
  function patchModelContextEventSupport() {
4
23
  if (modelContextPatched) {
5
24
  return;
@@ -12,6 +31,7 @@ function patchModelContextEventSupport() {
12
31
  const hasListTools = typeof mc.listTools === "function";
13
32
  const hasCallTool = typeof mc.callTool === "function";
14
33
  if (hasEventTarget && hasListTools && hasCallTool) {
34
+ patchDispatchEventForCoalescing(mc);
15
35
  modelContextPatched = true;
16
36
  return;
17
37
  }
@@ -116,6 +136,7 @@ function patchModelContextEventSupport() {
116
136
  }
117
137
  };
118
138
  }
139
+ patchDispatchEventForCoalescing(mc);
119
140
  modelContextPatched = true;
120
141
  }
121
142
  var registry = /* @__PURE__ */ new Map();
@@ -233,10 +254,9 @@ function unregisterScopedTool(name, owner) {
233
254
  if (record.owners.size > 0) return false;
234
255
  if (!record.controller.signal.aborted) {
235
256
  record.controller.abort();
236
- notifyToolsChanged({ immediate: true });
237
257
  } else {
238
258
  activeTools.delete(name);
239
- notifyToolsChanged({ immediate: true });
259
+ notifyToolsChanged();
240
260
  }
241
261
  return true;
242
262
  } catch {
@@ -246,11 +266,13 @@ function unregisterScopedTool(name, owner) {
246
266
  var pushToolsTimer = null;
247
267
  function pushToolsToWidget() {
248
268
  try {
249
- const tools = getToolsForWidget();
250
269
  if (typeof document === "undefined") return;
251
270
  const iframes = document.querySelectorAll("iframe");
271
+ if (iframes.length === 0) return;
272
+ const tools = getToolsForWidget();
252
273
  for (let i = 0; i < iframes.length; i++) {
253
274
  const iframe = iframes[i];
275
+ if (iframe.hasAttribute("data-webmcp-relay")) continue;
254
276
  try {
255
277
  if (iframe.contentWindow) {
256
278
  iframe.contentWindow.postMessage(
@@ -301,24 +323,13 @@ function schedulePushToolsToWidget() {
301
323
  pushToolsToWidget();
302
324
  }, 100);
303
325
  }
304
- function pushToolsToWidgetImmediately() {
305
- if (pushToolsTimer) {
306
- clearTimeout(pushToolsTimer);
307
- pushToolsTimer = null;
308
- }
309
- pushToolsToWidget();
310
- }
311
- function notifyToolsChanged(options = {}) {
326
+ function notifyToolsChanged() {
312
327
  if (typeof navigator === "undefined" || !("modelContext" in navigator)) return;
313
328
  try {
314
- navigator.modelContext.dispatchEvent(new Event("toolschanged"));
329
+ navigator.modelContext.dispatchEvent(new Event("toolchange"));
315
330
  } catch {
316
331
  }
317
- if (options.immediate) {
318
- pushToolsToWidgetImmediately();
319
- } else {
320
- schedulePushToolsToWidget();
321
- }
332
+ schedulePushToolsToWidget();
322
333
  }
323
334
 
324
335
  // src/polyfill.ts
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 * 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 — nothing to patch\n if (hasEventTarget && hasListTools && hasCallTool) {\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 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 notifyToolsChanged({ immediate: true });\n } else {\n activeTools.delete(name);\n notifyToolsChanged({ immediate: true });\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 const tools = getToolsForWidget();\n\n if (typeof document === 'undefined') return;\n\n // Find all iframes and send updated tool list\n // The widget iframe listens for 'webmcp.tools.changed' messages\n const iframes = document.querySelectorAll('iframe');\n for (let i = 0; i < iframes.length; i++) {\n const iframe = iframes[i];\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\nfunction pushToolsToWidgetImmediately(): void {\n if (pushToolsTimer) {\n clearTimeout(pushToolsTimer);\n pushToolsTimer = null;\n }\n pushToolsToWidget();\n}\n\n/**\n * 通知 MCP relay 工具列表已变化。\n * 发射 W3C 规范事件名 (toolschanged),toolchange 由 registerTool/unregisterTool 包装器负责。\n */\nexport function notifyToolsChanged(options: { immediate?: boolean } = {}): 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('toolschanged'));\n } catch {\n // Ignore dispatch errors\n }\n\n // Direct push to widget iframe as fallback for Chrome native environment\n // where registerToolsChangedCallback doesn't fire on unregisterTool\n if (options.immediate) {\n pushToolsToWidgetImmediately();\n } else {\n schedulePushToolsToWidget();\n }\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"],"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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmcp-nexus-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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": [