webmcp-nexus-sdk 0.1.9
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/LICENSE +21 -0
- package/dist/index.cjs +491 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +462 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alibaba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
registerGlobalTools: () => registerGlobalTools,
|
|
24
|
+
useWebMcpTools: () => useWebMcpTools
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/registry.ts
|
|
29
|
+
var modelContextPatched = false;
|
|
30
|
+
function patchModelContextEventSupport() {
|
|
31
|
+
if (modelContextPatched) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator) || !navigator.modelContext) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const mc = navigator.modelContext;
|
|
38
|
+
const hasEventTarget = typeof mc.addEventListener === "function";
|
|
39
|
+
const hasListTools = typeof mc.listTools === "function";
|
|
40
|
+
const hasCallTool = typeof mc.callTool === "function";
|
|
41
|
+
if (hasEventTarget && hasListTools && hasCallTool) {
|
|
42
|
+
modelContextPatched = true;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!hasEventTarget) {
|
|
46
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
47
|
+
mc.addEventListener = (type, callback) => {
|
|
48
|
+
if (!listeners.has(type)) listeners.set(type, /* @__PURE__ */ new Set());
|
|
49
|
+
listeners.get(type).add(callback);
|
|
50
|
+
};
|
|
51
|
+
mc.removeEventListener = (type, callback) => {
|
|
52
|
+
listeners.get(type)?.delete(callback);
|
|
53
|
+
};
|
|
54
|
+
mc.dispatchEvent = (event) => {
|
|
55
|
+
const set = listeners.get(event.type);
|
|
56
|
+
if (set) {
|
|
57
|
+
set.forEach((fn) => {
|
|
58
|
+
try {
|
|
59
|
+
fn.call(mc, event);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (!hasListTools || !hasCallTool) {
|
|
68
|
+
const testing = navigator.modelContextTesting;
|
|
69
|
+
if (testing) {
|
|
70
|
+
if (typeof testing.listTools === "function" && !testing.__webmcpNexusListToolsPatched) {
|
|
71
|
+
const originalTestingListTools = testing.listTools.bind(testing);
|
|
72
|
+
testing.listTools = (...args) => {
|
|
73
|
+
const nativeTools = originalTestingListTools(...args);
|
|
74
|
+
return Array.isArray(nativeTools) ? nativeTools.filter((tool) => {
|
|
75
|
+
if (!managedToolNames.has(tool.name)) return true;
|
|
76
|
+
return activeTools.has(tool.name);
|
|
77
|
+
}) : nativeTools;
|
|
78
|
+
};
|
|
79
|
+
testing.__webmcpNexusListToolsPatched = true;
|
|
80
|
+
}
|
|
81
|
+
if (!hasListTools && typeof testing.listTools === "function") {
|
|
82
|
+
mc.listTools = () => {
|
|
83
|
+
try {
|
|
84
|
+
return testing.listTools().map((t) => ({
|
|
85
|
+
name: t.name,
|
|
86
|
+
description: t.description || "",
|
|
87
|
+
inputSchema: typeof t.inputSchema === "string" && t.inputSchema.length > 0 ? JSON.parse(t.inputSchema) : { type: "object", properties: {} }
|
|
88
|
+
}));
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!hasCallTool && typeof testing.executeTool === "function") {
|
|
95
|
+
mc.callTool = async (params) => {
|
|
96
|
+
const result = await testing.executeTool(
|
|
97
|
+
params.name,
|
|
98
|
+
JSON.stringify(params.arguments || {})
|
|
99
|
+
);
|
|
100
|
+
if (result === null) {
|
|
101
|
+
return {
|
|
102
|
+
isError: true,
|
|
103
|
+
content: [{ type: "text", text: "Tool execution interrupted by navigation" }]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(result);
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (typeof mc.registerTool === "function") {
|
|
116
|
+
const originalRegisterTool = mc.registerTool.bind(mc);
|
|
117
|
+
mc.registerTool = (tool, options) => {
|
|
118
|
+
let registrationError;
|
|
119
|
+
try {
|
|
120
|
+
originalRegisterTool(tool, options);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
registrationError = error;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
if (registrationError) {
|
|
129
|
+
throw registrationError;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (typeof mc.unregisterTool === "function") {
|
|
134
|
+
const originalUnregisterTool = mc.unregisterTool.bind(mc);
|
|
135
|
+
mc.unregisterTool = (name) => {
|
|
136
|
+
try {
|
|
137
|
+
originalUnregisterTool(name);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
modelContextPatched = true;
|
|
147
|
+
}
|
|
148
|
+
var registry = /* @__PURE__ */ new Map();
|
|
149
|
+
var activeTools = /* @__PURE__ */ new Map();
|
|
150
|
+
var managedToolNames = /* @__PURE__ */ new Set();
|
|
151
|
+
var hasManagedTools = false;
|
|
152
|
+
function getOwnerKey(owner) {
|
|
153
|
+
return `${owner.scope}:${owner.scopeId}`;
|
|
154
|
+
}
|
|
155
|
+
function getActiveToolConfigs() {
|
|
156
|
+
return Array.from(activeTools.values()).map((record) => ({
|
|
157
|
+
name: record.name,
|
|
158
|
+
description: record.config.description,
|
|
159
|
+
inputSchema: record.config.inputSchema || { type: "object", properties: {} }
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
function registerEntry(name, scope, scopeId) {
|
|
163
|
+
const entries = registry.get(name) || [];
|
|
164
|
+
if (entries.length > 0) {
|
|
165
|
+
const isSameScopeAndId = entries.some((e) => e.scope === scope && e.scopeId === scopeId);
|
|
166
|
+
if (!isSameScopeAndId) {
|
|
167
|
+
const existing = entries[0];
|
|
168
|
+
console.warn(
|
|
169
|
+
`[webmcp] Tool "${name}" is already registered by scope "${existing.scope}:${existing.scopeId}". Re-registering from "${scope}:${scopeId}". This may cause unexpected behavior.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
entries.push({ name, scope, scopeId });
|
|
174
|
+
registry.set(name, entries);
|
|
175
|
+
}
|
|
176
|
+
function registerScopedTool(toolConfig, owner) {
|
|
177
|
+
try {
|
|
178
|
+
const ownerKey = getOwnerKey(owner);
|
|
179
|
+
hasManagedTools = true;
|
|
180
|
+
managedToolNames.add(toolConfig.name);
|
|
181
|
+
const existing = activeTools.get(toolConfig.name);
|
|
182
|
+
if (existing) {
|
|
183
|
+
existing.owners.set(ownerKey, owner);
|
|
184
|
+
existing.configs.set(ownerKey, toolConfig);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
const owners = /* @__PURE__ */ new Map([[ownerKey, owner]]);
|
|
189
|
+
const record = {
|
|
190
|
+
name: toolConfig.name,
|
|
191
|
+
config: toolConfig,
|
|
192
|
+
configs: /* @__PURE__ */ new Map([[ownerKey, toolConfig]]),
|
|
193
|
+
controller,
|
|
194
|
+
owners,
|
|
195
|
+
nativeRegistered: false
|
|
196
|
+
};
|
|
197
|
+
activeTools.set(toolConfig.name, record);
|
|
198
|
+
const mc = typeof navigator !== "undefined" ? (
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
navigator.modelContext
|
|
201
|
+
) : void 0;
|
|
202
|
+
controller.signal.addEventListener(
|
|
203
|
+
"abort",
|
|
204
|
+
() => {
|
|
205
|
+
try {
|
|
206
|
+
if (mc && typeof mc.unregisterTool === "function" && !mc.__isWebMCPPolyfill) {
|
|
207
|
+
mc.unregisterTool(toolConfig.name);
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
activeTools.delete(toolConfig.name);
|
|
213
|
+
notifyToolsChanged();
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{ once: true }
|
|
218
|
+
);
|
|
219
|
+
if (mc && typeof mc.registerTool === "function") {
|
|
220
|
+
const nativeToolConfig = {
|
|
221
|
+
...toolConfig,
|
|
222
|
+
execute: async (input) => {
|
|
223
|
+
const latestRecord = activeTools.get(toolConfig.name);
|
|
224
|
+
return latestRecord?.config.execute(input);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
mc.registerTool(nativeToolConfig, { signal: controller.signal });
|
|
229
|
+
record.nativeRegistered = true;
|
|
230
|
+
} catch {
|
|
231
|
+
try {
|
|
232
|
+
if (typeof mc.unregisterTool === "function") {
|
|
233
|
+
mc.unregisterTool(toolConfig.name);
|
|
234
|
+
mc.registerTool(nativeToolConfig, { signal: controller.signal });
|
|
235
|
+
record.nativeRegistered = true;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function unregisterScopedTool(name, owner) {
|
|
245
|
+
try {
|
|
246
|
+
const record = activeTools.get(name);
|
|
247
|
+
if (!record) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const ownerKey = getOwnerKey(owner);
|
|
251
|
+
const removedConfig = record.configs.get(ownerKey);
|
|
252
|
+
record.owners.delete(ownerKey);
|
|
253
|
+
record.configs.delete(ownerKey);
|
|
254
|
+
if (removedConfig && record.config === removedConfig) {
|
|
255
|
+
const nextConfig = record.configs.values().next().value;
|
|
256
|
+
if (nextConfig) {
|
|
257
|
+
record.config = nextConfig;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (record.owners.size > 0) return false;
|
|
261
|
+
if (!record.controller.signal.aborted) {
|
|
262
|
+
record.controller.abort();
|
|
263
|
+
notifyToolsChanged({ immediate: true });
|
|
264
|
+
} else {
|
|
265
|
+
activeTools.delete(name);
|
|
266
|
+
notifyToolsChanged({ immediate: true });
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
var pushToolsTimer = null;
|
|
274
|
+
function pushToolsToWidget() {
|
|
275
|
+
try {
|
|
276
|
+
const tools = getToolsForWidget();
|
|
277
|
+
if (typeof document === "undefined") return;
|
|
278
|
+
const iframes = document.querySelectorAll("iframe");
|
|
279
|
+
for (let i = 0; i < iframes.length; i++) {
|
|
280
|
+
const iframe = iframes[i];
|
|
281
|
+
try {
|
|
282
|
+
if (iframe.contentWindow) {
|
|
283
|
+
iframe.contentWindow.postMessage(
|
|
284
|
+
{
|
|
285
|
+
type: "webmcp.tools.changed",
|
|
286
|
+
tools
|
|
287
|
+
},
|
|
288
|
+
"*"
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function getToolsForWidget() {
|
|
298
|
+
if (hasManagedTools) {
|
|
299
|
+
return getActiveToolConfigs();
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const mc = typeof navigator !== "undefined" ? navigator.modelContext : void 0;
|
|
303
|
+
if (mc && typeof mc.listTools === "function") {
|
|
304
|
+
const tools = mc.listTools().map((tool) => ({
|
|
305
|
+
name: tool.name,
|
|
306
|
+
description: tool.description || "",
|
|
307
|
+
inputSchema: typeof tool.inputSchema === "string" && tool.inputSchema.length > 0 ? safeJsonParse(tool.inputSchema) : tool.inputSchema || { type: "object", properties: {} }
|
|
308
|
+
}));
|
|
309
|
+
return tools;
|
|
310
|
+
}
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
return getActiveToolConfigs();
|
|
314
|
+
}
|
|
315
|
+
function safeJsonParse(value) {
|
|
316
|
+
try {
|
|
317
|
+
return JSON.parse(value);
|
|
318
|
+
} catch {
|
|
319
|
+
return { type: "object", properties: {} };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function schedulePushToolsToWidget() {
|
|
323
|
+
if (pushToolsTimer) {
|
|
324
|
+
clearTimeout(pushToolsTimer);
|
|
325
|
+
}
|
|
326
|
+
pushToolsTimer = setTimeout(() => {
|
|
327
|
+
pushToolsTimer = null;
|
|
328
|
+
pushToolsToWidget();
|
|
329
|
+
}, 100);
|
|
330
|
+
}
|
|
331
|
+
function pushToolsToWidgetImmediately() {
|
|
332
|
+
if (pushToolsTimer) {
|
|
333
|
+
clearTimeout(pushToolsTimer);
|
|
334
|
+
pushToolsTimer = null;
|
|
335
|
+
}
|
|
336
|
+
pushToolsToWidget();
|
|
337
|
+
}
|
|
338
|
+
function notifyToolsChanged(options = {}) {
|
|
339
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) return;
|
|
340
|
+
try {
|
|
341
|
+
navigator.modelContext.dispatchEvent(new Event("toolschanged"));
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
if (options.immediate) {
|
|
345
|
+
pushToolsToWidgetImmediately();
|
|
346
|
+
} else {
|
|
347
|
+
schedulePushToolsToWidget();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/polyfill.ts
|
|
352
|
+
var import_webmcp_polyfill = require("@mcp-b/webmcp-polyfill");
|
|
353
|
+
var attempted = false;
|
|
354
|
+
function ensureModelContextPolyfill() {
|
|
355
|
+
if (attempted) return;
|
|
356
|
+
attempted = true;
|
|
357
|
+
if (typeof navigator === "undefined") return;
|
|
358
|
+
if ("modelContext" in navigator) return;
|
|
359
|
+
try {
|
|
360
|
+
(0, import_webmcp_polyfill.initializeWebMCPPolyfill)({ installTestingShim: true });
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/registerGlobalTools.ts
|
|
366
|
+
function registerGlobalTools(...toolMaps) {
|
|
367
|
+
ensureModelContextPolyfill();
|
|
368
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
patchModelContextEventSupport();
|
|
373
|
+
let registeredCount = 0;
|
|
374
|
+
for (const toolMap of toolMaps) {
|
|
375
|
+
for (const [name, fn] of Object.entries(toolMap)) {
|
|
376
|
+
if (typeof fn !== "function") continue;
|
|
377
|
+
const schema = fn.__webmcpSchema;
|
|
378
|
+
if (!schema) continue;
|
|
379
|
+
registerEntry(name, "global", "app");
|
|
380
|
+
registerScopedTool(
|
|
381
|
+
{
|
|
382
|
+
name,
|
|
383
|
+
description: schema.description,
|
|
384
|
+
inputSchema: schema.inputSchema,
|
|
385
|
+
execute: async (input) => fn(input),
|
|
386
|
+
annotations: { readOnlyHint: schema.readOnly ?? false }
|
|
387
|
+
},
|
|
388
|
+
{ scope: "global", scopeId: "app" }
|
|
389
|
+
);
|
|
390
|
+
registeredCount++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (registeredCount > 0) {
|
|
394
|
+
notifyToolsChanged();
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/useWebMcpTools.ts
|
|
401
|
+
var import_react = require("react");
|
|
402
|
+
var import_meta = {};
|
|
403
|
+
var scopeCounter = 0;
|
|
404
|
+
function generateScopeId() {
|
|
405
|
+
return `component-${++scopeCounter}`;
|
|
406
|
+
}
|
|
407
|
+
var hmrVersion = 0;
|
|
408
|
+
var hmrHot = typeof import_meta !== "undefined" ? import_meta.hot : void 0;
|
|
409
|
+
if (hmrHot && typeof hmrHot.on === "function") {
|
|
410
|
+
hmrHot.on("vite:afterUpdate", () => {
|
|
411
|
+
hmrVersion++;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function useWebMcpTools(...toolMaps) {
|
|
415
|
+
const merged = {};
|
|
416
|
+
for (const toolMap of toolMaps) {
|
|
417
|
+
for (const [name, fn] of Object.entries(toolMap)) {
|
|
418
|
+
if (typeof fn === "function") {
|
|
419
|
+
merged[name] = fn;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const toolsRef = (0, import_react.useRef)(merged);
|
|
424
|
+
toolsRef.current = merged;
|
|
425
|
+
const scopeIdRef = (0, import_react.useRef)("");
|
|
426
|
+
if (!scopeIdRef.current) {
|
|
427
|
+
scopeIdRef.current = generateScopeId();
|
|
428
|
+
}
|
|
429
|
+
const toolKeys = Object.keys(merged).sort().join(",");
|
|
430
|
+
const [localHmrVersion, setLocalHmrVersion] = (0, import_react.useState)(hmrVersion);
|
|
431
|
+
(0, import_react.useEffect)(() => {
|
|
432
|
+
const hot = typeof import_meta !== "undefined" ? import_meta.hot : void 0;
|
|
433
|
+
if (hot && typeof hot.on === "function") {
|
|
434
|
+
const handler = () => setLocalHmrVersion((v) => v + 1);
|
|
435
|
+
hot.on("vite:afterUpdate", handler);
|
|
436
|
+
return () => {
|
|
437
|
+
if (typeof hot.off === "function") {
|
|
438
|
+
hot.off("vite:afterUpdate", handler);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}, []);
|
|
443
|
+
(0, import_react.useEffect)(() => {
|
|
444
|
+
ensureModelContextPolyfill();
|
|
445
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
patchModelContextEventSupport();
|
|
449
|
+
const registeredNames = [];
|
|
450
|
+
for (const [name, fn] of Object.entries(toolsRef.current)) {
|
|
451
|
+
const schema = fn.__webmcpSchema;
|
|
452
|
+
if (!schema) continue;
|
|
453
|
+
registerScopedTool(
|
|
454
|
+
{
|
|
455
|
+
name,
|
|
456
|
+
description: schema.description,
|
|
457
|
+
inputSchema: schema.inputSchema,
|
|
458
|
+
// 通过 ref 间接调用,保证始终执行最新版本的函数
|
|
459
|
+
execute: async (input) => toolsRef.current[name](input),
|
|
460
|
+
annotations: { readOnlyHint: schema.readOnly ?? false }
|
|
461
|
+
},
|
|
462
|
+
{ scope: "component", scopeId: scopeIdRef.current }
|
|
463
|
+
);
|
|
464
|
+
registeredNames.push(name);
|
|
465
|
+
}
|
|
466
|
+
if (registeredNames.length > 0) {
|
|
467
|
+
notifyToolsChanged();
|
|
468
|
+
}
|
|
469
|
+
return () => {
|
|
470
|
+
let unregisteredAny = false;
|
|
471
|
+
for (const name of registeredNames) {
|
|
472
|
+
const shouldUnregister = unregisterScopedTool(name, {
|
|
473
|
+
scope: "component",
|
|
474
|
+
scopeId: scopeIdRef.current
|
|
475
|
+
});
|
|
476
|
+
if (shouldUnregister) {
|
|
477
|
+
unregisteredAny = true;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (unregisteredAny) {
|
|
481
|
+
notifyToolsChanged();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}, [toolKeys, localHmrVersion]);
|
|
485
|
+
}
|
|
486
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
487
|
+
0 && (module.exports = {
|
|
488
|
+
registerGlobalTools,
|
|
489
|
+
useWebMcpTools
|
|
490
|
+
});
|
|
491
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局注册 WebMCP 工具。应用启动时调用一次。
|
|
3
|
+
* 支持可变参数,兼容 import * as module 批量导入。
|
|
4
|
+
*
|
|
5
|
+
* @param toolMaps - 一个或多个 Record<string, Function> 对象
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import * as userApi from './api/user';
|
|
9
|
+
* import * as productApi from './api/product';
|
|
10
|
+
* registerGlobalTools(userApi, productApi);
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* registerGlobalTools({ getUser, searchUsers }, { searchProducts });
|
|
14
|
+
*/
|
|
15
|
+
declare function registerGlobalTools(...toolMaps: Record<string, Function>[]): void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。
|
|
19
|
+
* mount 时注册,unmount 时自动注销。
|
|
20
|
+
*
|
|
21
|
+
* 使用 useRef 持有最新函数引用,避免闭包陷阱。
|
|
22
|
+
* 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,
|
|
23
|
+
* 当工具集合变化时重新注册,函数体变化不触发重新注册。
|
|
24
|
+
*
|
|
25
|
+
* @param toolMaps - 一个或多个 Record<string, Function> 对象
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // 组件级注册
|
|
29
|
+
* useWebMcpTools({ searchInPanel, clearSearch });
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // 路由级注册(配合 React Router 使用)
|
|
33
|
+
* useWebMcpTools({ setUserFilter });
|
|
34
|
+
*/
|
|
35
|
+
declare function useWebMcpTools(...toolMaps: Record<string, Function>[]): void;
|
|
36
|
+
|
|
37
|
+
/** 工具 schema 元数据(由 Vite 插件构建时注入到函数的 __webmcpSchema 属性) */
|
|
38
|
+
interface WebMcpToolSchema {
|
|
39
|
+
/** 工具描述(来自 JSDoc) */
|
|
40
|
+
description: string;
|
|
41
|
+
/** 输入参数的 JSON Schema(来自 TypeScript 类型推导) */
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object';
|
|
44
|
+
properties: Record<string, unknown>;
|
|
45
|
+
required?: string[];
|
|
46
|
+
};
|
|
47
|
+
/** 是否为只读工具(来自 @readonly JSDoc 标签) */
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 带有 __webmcpSchema 属性的函数类型。
|
|
52
|
+
* Vite 插件在构建时会将 schema 注入到函数的 __webmcpSchema 属性上。
|
|
53
|
+
*/
|
|
54
|
+
type WebMcpToolFn<TParams = unknown, TResult = unknown> = {
|
|
55
|
+
(params: TParams): Promise<TResult>;
|
|
56
|
+
__webmcpSchema?: WebMcpToolSchema;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* 带有可选 __webmcpSchema 属性的标注函数。
|
|
60
|
+
* 用于替代 `as any` 访问 __webmcpSchema。
|
|
61
|
+
*/
|
|
62
|
+
interface WebMcpAnnotatedFn {
|
|
63
|
+
(...args: unknown[]): unknown;
|
|
64
|
+
__webmcpSchema?: WebMcpToolSchema;
|
|
65
|
+
}
|
|
66
|
+
/** 传递给 navigator.modelContext.registerTool 的工具配置 */
|
|
67
|
+
interface WebMcpToolConfig {
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
inputSchema?: object;
|
|
71
|
+
execute: (input: Record<string, unknown>) => Promise<unknown>;
|
|
72
|
+
annotations?: {
|
|
73
|
+
readOnlyHint?: boolean;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局注册 WebMCP 工具。应用启动时调用一次。
|
|
3
|
+
* 支持可变参数,兼容 import * as module 批量导入。
|
|
4
|
+
*
|
|
5
|
+
* @param toolMaps - 一个或多个 Record<string, Function> 对象
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import * as userApi from './api/user';
|
|
9
|
+
* import * as productApi from './api/product';
|
|
10
|
+
* registerGlobalTools(userApi, productApi);
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* registerGlobalTools({ getUser, searchUsers }, { searchProducts });
|
|
14
|
+
*/
|
|
15
|
+
declare function registerGlobalTools(...toolMaps: Record<string, Function>[]): void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React Hook:将函数注册为 WebMCP 工具,绑定组件生命周期。
|
|
19
|
+
* mount 时注册,unmount 时自动注销。
|
|
20
|
+
*
|
|
21
|
+
* 使用 useRef 持有最新函数引用,避免闭包陷阱。
|
|
22
|
+
* 使用 toolKeys(工具名集合的字符串)作为 useEffect 依赖,
|
|
23
|
+
* 当工具集合变化时重新注册,函数体变化不触发重新注册。
|
|
24
|
+
*
|
|
25
|
+
* @param toolMaps - 一个或多个 Record<string, Function> 对象
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // 组件级注册
|
|
29
|
+
* useWebMcpTools({ searchInPanel, clearSearch });
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // 路由级注册(配合 React Router 使用)
|
|
33
|
+
* useWebMcpTools({ setUserFilter });
|
|
34
|
+
*/
|
|
35
|
+
declare function useWebMcpTools(...toolMaps: Record<string, Function>[]): void;
|
|
36
|
+
|
|
37
|
+
/** 工具 schema 元数据(由 Vite 插件构建时注入到函数的 __webmcpSchema 属性) */
|
|
38
|
+
interface WebMcpToolSchema {
|
|
39
|
+
/** 工具描述(来自 JSDoc) */
|
|
40
|
+
description: string;
|
|
41
|
+
/** 输入参数的 JSON Schema(来自 TypeScript 类型推导) */
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object';
|
|
44
|
+
properties: Record<string, unknown>;
|
|
45
|
+
required?: string[];
|
|
46
|
+
};
|
|
47
|
+
/** 是否为只读工具(来自 @readonly JSDoc 标签) */
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 带有 __webmcpSchema 属性的函数类型。
|
|
52
|
+
* Vite 插件在构建时会将 schema 注入到函数的 __webmcpSchema 属性上。
|
|
53
|
+
*/
|
|
54
|
+
type WebMcpToolFn<TParams = unknown, TResult = unknown> = {
|
|
55
|
+
(params: TParams): Promise<TResult>;
|
|
56
|
+
__webmcpSchema?: WebMcpToolSchema;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* 带有可选 __webmcpSchema 属性的标注函数。
|
|
60
|
+
* 用于替代 `as any` 访问 __webmcpSchema。
|
|
61
|
+
*/
|
|
62
|
+
interface WebMcpAnnotatedFn {
|
|
63
|
+
(...args: unknown[]): unknown;
|
|
64
|
+
__webmcpSchema?: WebMcpToolSchema;
|
|
65
|
+
}
|
|
66
|
+
/** 传递给 navigator.modelContext.registerTool 的工具配置 */
|
|
67
|
+
interface WebMcpToolConfig {
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
inputSchema?: object;
|
|
71
|
+
execute: (input: Record<string, unknown>) => Promise<unknown>;
|
|
72
|
+
annotations?: {
|
|
73
|
+
readOnlyHint?: boolean;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { type WebMcpAnnotatedFn, type WebMcpToolConfig, type WebMcpToolFn, type WebMcpToolSchema, registerGlobalTools, useWebMcpTools };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
var modelContextPatched = false;
|
|
3
|
+
function patchModelContextEventSupport() {
|
|
4
|
+
if (modelContextPatched) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator) || !navigator.modelContext) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const mc = navigator.modelContext;
|
|
11
|
+
const hasEventTarget = typeof mc.addEventListener === "function";
|
|
12
|
+
const hasListTools = typeof mc.listTools === "function";
|
|
13
|
+
const hasCallTool = typeof mc.callTool === "function";
|
|
14
|
+
if (hasEventTarget && hasListTools && hasCallTool) {
|
|
15
|
+
modelContextPatched = true;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!hasEventTarget) {
|
|
19
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
20
|
+
mc.addEventListener = (type, callback) => {
|
|
21
|
+
if (!listeners.has(type)) listeners.set(type, /* @__PURE__ */ new Set());
|
|
22
|
+
listeners.get(type).add(callback);
|
|
23
|
+
};
|
|
24
|
+
mc.removeEventListener = (type, callback) => {
|
|
25
|
+
listeners.get(type)?.delete(callback);
|
|
26
|
+
};
|
|
27
|
+
mc.dispatchEvent = (event) => {
|
|
28
|
+
const set = listeners.get(event.type);
|
|
29
|
+
if (set) {
|
|
30
|
+
set.forEach((fn) => {
|
|
31
|
+
try {
|
|
32
|
+
fn.call(mc, event);
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!hasListTools || !hasCallTool) {
|
|
41
|
+
const testing = navigator.modelContextTesting;
|
|
42
|
+
if (testing) {
|
|
43
|
+
if (typeof testing.listTools === "function" && !testing.__webmcpNexusListToolsPatched) {
|
|
44
|
+
const originalTestingListTools = testing.listTools.bind(testing);
|
|
45
|
+
testing.listTools = (...args) => {
|
|
46
|
+
const nativeTools = originalTestingListTools(...args);
|
|
47
|
+
return Array.isArray(nativeTools) ? nativeTools.filter((tool) => {
|
|
48
|
+
if (!managedToolNames.has(tool.name)) return true;
|
|
49
|
+
return activeTools.has(tool.name);
|
|
50
|
+
}) : nativeTools;
|
|
51
|
+
};
|
|
52
|
+
testing.__webmcpNexusListToolsPatched = true;
|
|
53
|
+
}
|
|
54
|
+
if (!hasListTools && typeof testing.listTools === "function") {
|
|
55
|
+
mc.listTools = () => {
|
|
56
|
+
try {
|
|
57
|
+
return testing.listTools().map((t) => ({
|
|
58
|
+
name: t.name,
|
|
59
|
+
description: t.description || "",
|
|
60
|
+
inputSchema: typeof t.inputSchema === "string" && t.inputSchema.length > 0 ? JSON.parse(t.inputSchema) : { type: "object", properties: {} }
|
|
61
|
+
}));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (!hasCallTool && typeof testing.executeTool === "function") {
|
|
68
|
+
mc.callTool = async (params) => {
|
|
69
|
+
const result = await testing.executeTool(
|
|
70
|
+
params.name,
|
|
71
|
+
JSON.stringify(params.arguments || {})
|
|
72
|
+
);
|
|
73
|
+
if (result === null) {
|
|
74
|
+
return {
|
|
75
|
+
isError: true,
|
|
76
|
+
content: [{ type: "text", text: "Tool execution interrupted by navigation" }]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(result);
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error(`Tool returned invalid JSON: ${String(result).slice(0, 200)}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (typeof mc.registerTool === "function") {
|
|
89
|
+
const originalRegisterTool = mc.registerTool.bind(mc);
|
|
90
|
+
mc.registerTool = (tool, options) => {
|
|
91
|
+
let registrationError;
|
|
92
|
+
try {
|
|
93
|
+
originalRegisterTool(tool, options);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
registrationError = error;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
if (registrationError) {
|
|
102
|
+
throw registrationError;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (typeof mc.unregisterTool === "function") {
|
|
107
|
+
const originalUnregisterTool = mc.unregisterTool.bind(mc);
|
|
108
|
+
mc.unregisterTool = (name) => {
|
|
109
|
+
try {
|
|
110
|
+
originalUnregisterTool(name);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
mc.dispatchEvent(new Event("toolchange"));
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
modelContextPatched = true;
|
|
120
|
+
}
|
|
121
|
+
var registry = /* @__PURE__ */ new Map();
|
|
122
|
+
var activeTools = /* @__PURE__ */ new Map();
|
|
123
|
+
var managedToolNames = /* @__PURE__ */ new Set();
|
|
124
|
+
var hasManagedTools = false;
|
|
125
|
+
function getOwnerKey(owner) {
|
|
126
|
+
return `${owner.scope}:${owner.scopeId}`;
|
|
127
|
+
}
|
|
128
|
+
function getActiveToolConfigs() {
|
|
129
|
+
return Array.from(activeTools.values()).map((record) => ({
|
|
130
|
+
name: record.name,
|
|
131
|
+
description: record.config.description,
|
|
132
|
+
inputSchema: record.config.inputSchema || { type: "object", properties: {} }
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
function registerEntry(name, scope, scopeId) {
|
|
136
|
+
const entries = registry.get(name) || [];
|
|
137
|
+
if (entries.length > 0) {
|
|
138
|
+
const isSameScopeAndId = entries.some((e) => e.scope === scope && e.scopeId === scopeId);
|
|
139
|
+
if (!isSameScopeAndId) {
|
|
140
|
+
const existing = entries[0];
|
|
141
|
+
console.warn(
|
|
142
|
+
`[webmcp] Tool "${name}" is already registered by scope "${existing.scope}:${existing.scopeId}". Re-registering from "${scope}:${scopeId}". This may cause unexpected behavior.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
entries.push({ name, scope, scopeId });
|
|
147
|
+
registry.set(name, entries);
|
|
148
|
+
}
|
|
149
|
+
function registerScopedTool(toolConfig, owner) {
|
|
150
|
+
try {
|
|
151
|
+
const ownerKey = getOwnerKey(owner);
|
|
152
|
+
hasManagedTools = true;
|
|
153
|
+
managedToolNames.add(toolConfig.name);
|
|
154
|
+
const existing = activeTools.get(toolConfig.name);
|
|
155
|
+
if (existing) {
|
|
156
|
+
existing.owners.set(ownerKey, owner);
|
|
157
|
+
existing.configs.set(ownerKey, toolConfig);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
const owners = /* @__PURE__ */ new Map([[ownerKey, owner]]);
|
|
162
|
+
const record = {
|
|
163
|
+
name: toolConfig.name,
|
|
164
|
+
config: toolConfig,
|
|
165
|
+
configs: /* @__PURE__ */ new Map([[ownerKey, toolConfig]]),
|
|
166
|
+
controller,
|
|
167
|
+
owners,
|
|
168
|
+
nativeRegistered: false
|
|
169
|
+
};
|
|
170
|
+
activeTools.set(toolConfig.name, record);
|
|
171
|
+
const mc = typeof navigator !== "undefined" ? (
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
|
+
navigator.modelContext
|
|
174
|
+
) : void 0;
|
|
175
|
+
controller.signal.addEventListener(
|
|
176
|
+
"abort",
|
|
177
|
+
() => {
|
|
178
|
+
try {
|
|
179
|
+
if (mc && typeof mc.unregisterTool === "function" && !mc.__isWebMCPPolyfill) {
|
|
180
|
+
mc.unregisterTool(toolConfig.name);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
activeTools.delete(toolConfig.name);
|
|
186
|
+
notifyToolsChanged();
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{ once: true }
|
|
191
|
+
);
|
|
192
|
+
if (mc && typeof mc.registerTool === "function") {
|
|
193
|
+
const nativeToolConfig = {
|
|
194
|
+
...toolConfig,
|
|
195
|
+
execute: async (input) => {
|
|
196
|
+
const latestRecord = activeTools.get(toolConfig.name);
|
|
197
|
+
return latestRecord?.config.execute(input);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
try {
|
|
201
|
+
mc.registerTool(nativeToolConfig, { signal: controller.signal });
|
|
202
|
+
record.nativeRegistered = true;
|
|
203
|
+
} catch {
|
|
204
|
+
try {
|
|
205
|
+
if (typeof mc.unregisterTool === "function") {
|
|
206
|
+
mc.unregisterTool(toolConfig.name);
|
|
207
|
+
mc.registerTool(nativeToolConfig, { signal: controller.signal });
|
|
208
|
+
record.nativeRegistered = true;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function unregisterScopedTool(name, owner) {
|
|
218
|
+
try {
|
|
219
|
+
const record = activeTools.get(name);
|
|
220
|
+
if (!record) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const ownerKey = getOwnerKey(owner);
|
|
224
|
+
const removedConfig = record.configs.get(ownerKey);
|
|
225
|
+
record.owners.delete(ownerKey);
|
|
226
|
+
record.configs.delete(ownerKey);
|
|
227
|
+
if (removedConfig && record.config === removedConfig) {
|
|
228
|
+
const nextConfig = record.configs.values().next().value;
|
|
229
|
+
if (nextConfig) {
|
|
230
|
+
record.config = nextConfig;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (record.owners.size > 0) return false;
|
|
234
|
+
if (!record.controller.signal.aborted) {
|
|
235
|
+
record.controller.abort();
|
|
236
|
+
notifyToolsChanged({ immediate: true });
|
|
237
|
+
} else {
|
|
238
|
+
activeTools.delete(name);
|
|
239
|
+
notifyToolsChanged({ immediate: true });
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
var pushToolsTimer = null;
|
|
247
|
+
function pushToolsToWidget() {
|
|
248
|
+
try {
|
|
249
|
+
const tools = getToolsForWidget();
|
|
250
|
+
if (typeof document === "undefined") return;
|
|
251
|
+
const iframes = document.querySelectorAll("iframe");
|
|
252
|
+
for (let i = 0; i < iframes.length; i++) {
|
|
253
|
+
const iframe = iframes[i];
|
|
254
|
+
try {
|
|
255
|
+
if (iframe.contentWindow) {
|
|
256
|
+
iframe.contentWindow.postMessage(
|
|
257
|
+
{
|
|
258
|
+
type: "webmcp.tools.changed",
|
|
259
|
+
tools
|
|
260
|
+
},
|
|
261
|
+
"*"
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getToolsForWidget() {
|
|
271
|
+
if (hasManagedTools) {
|
|
272
|
+
return getActiveToolConfigs();
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const mc = typeof navigator !== "undefined" ? navigator.modelContext : void 0;
|
|
276
|
+
if (mc && typeof mc.listTools === "function") {
|
|
277
|
+
const tools = mc.listTools().map((tool) => ({
|
|
278
|
+
name: tool.name,
|
|
279
|
+
description: tool.description || "",
|
|
280
|
+
inputSchema: typeof tool.inputSchema === "string" && tool.inputSchema.length > 0 ? safeJsonParse(tool.inputSchema) : tool.inputSchema || { type: "object", properties: {} }
|
|
281
|
+
}));
|
|
282
|
+
return tools;
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
return getActiveToolConfigs();
|
|
287
|
+
}
|
|
288
|
+
function safeJsonParse(value) {
|
|
289
|
+
try {
|
|
290
|
+
return JSON.parse(value);
|
|
291
|
+
} catch {
|
|
292
|
+
return { type: "object", properties: {} };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function schedulePushToolsToWidget() {
|
|
296
|
+
if (pushToolsTimer) {
|
|
297
|
+
clearTimeout(pushToolsTimer);
|
|
298
|
+
}
|
|
299
|
+
pushToolsTimer = setTimeout(() => {
|
|
300
|
+
pushToolsTimer = null;
|
|
301
|
+
pushToolsToWidget();
|
|
302
|
+
}, 100);
|
|
303
|
+
}
|
|
304
|
+
function pushToolsToWidgetImmediately() {
|
|
305
|
+
if (pushToolsTimer) {
|
|
306
|
+
clearTimeout(pushToolsTimer);
|
|
307
|
+
pushToolsTimer = null;
|
|
308
|
+
}
|
|
309
|
+
pushToolsToWidget();
|
|
310
|
+
}
|
|
311
|
+
function notifyToolsChanged(options = {}) {
|
|
312
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) return;
|
|
313
|
+
try {
|
|
314
|
+
navigator.modelContext.dispatchEvent(new Event("toolschanged"));
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
if (options.immediate) {
|
|
318
|
+
pushToolsToWidgetImmediately();
|
|
319
|
+
} else {
|
|
320
|
+
schedulePushToolsToWidget();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/polyfill.ts
|
|
325
|
+
import { initializeWebMCPPolyfill, cleanupWebMCPPolyfill } from "@mcp-b/webmcp-polyfill";
|
|
326
|
+
var attempted = false;
|
|
327
|
+
function ensureModelContextPolyfill() {
|
|
328
|
+
if (attempted) return;
|
|
329
|
+
attempted = true;
|
|
330
|
+
if (typeof navigator === "undefined") return;
|
|
331
|
+
if ("modelContext" in navigator) return;
|
|
332
|
+
try {
|
|
333
|
+
initializeWebMCPPolyfill({ installTestingShim: true });
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/registerGlobalTools.ts
|
|
339
|
+
function registerGlobalTools(...toolMaps) {
|
|
340
|
+
ensureModelContextPolyfill();
|
|
341
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
patchModelContextEventSupport();
|
|
346
|
+
let registeredCount = 0;
|
|
347
|
+
for (const toolMap of toolMaps) {
|
|
348
|
+
for (const [name, fn] of Object.entries(toolMap)) {
|
|
349
|
+
if (typeof fn !== "function") continue;
|
|
350
|
+
const schema = fn.__webmcpSchema;
|
|
351
|
+
if (!schema) continue;
|
|
352
|
+
registerEntry(name, "global", "app");
|
|
353
|
+
registerScopedTool(
|
|
354
|
+
{
|
|
355
|
+
name,
|
|
356
|
+
description: schema.description,
|
|
357
|
+
inputSchema: schema.inputSchema,
|
|
358
|
+
execute: async (input) => fn(input),
|
|
359
|
+
annotations: { readOnlyHint: schema.readOnly ?? false }
|
|
360
|
+
},
|
|
361
|
+
{ scope: "global", scopeId: "app" }
|
|
362
|
+
);
|
|
363
|
+
registeredCount++;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (registeredCount > 0) {
|
|
367
|
+
notifyToolsChanged();
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/useWebMcpTools.ts
|
|
374
|
+
import { useEffect, useRef, useState } from "react";
|
|
375
|
+
var scopeCounter = 0;
|
|
376
|
+
function generateScopeId() {
|
|
377
|
+
return `component-${++scopeCounter}`;
|
|
378
|
+
}
|
|
379
|
+
var hmrVersion = 0;
|
|
380
|
+
var hmrHot = typeof import.meta !== "undefined" ? import.meta.hot : void 0;
|
|
381
|
+
if (hmrHot && typeof hmrHot.on === "function") {
|
|
382
|
+
hmrHot.on("vite:afterUpdate", () => {
|
|
383
|
+
hmrVersion++;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function useWebMcpTools(...toolMaps) {
|
|
387
|
+
const merged = {};
|
|
388
|
+
for (const toolMap of toolMaps) {
|
|
389
|
+
for (const [name, fn] of Object.entries(toolMap)) {
|
|
390
|
+
if (typeof fn === "function") {
|
|
391
|
+
merged[name] = fn;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const toolsRef = useRef(merged);
|
|
396
|
+
toolsRef.current = merged;
|
|
397
|
+
const scopeIdRef = useRef("");
|
|
398
|
+
if (!scopeIdRef.current) {
|
|
399
|
+
scopeIdRef.current = generateScopeId();
|
|
400
|
+
}
|
|
401
|
+
const toolKeys = Object.keys(merged).sort().join(",");
|
|
402
|
+
const [localHmrVersion, setLocalHmrVersion] = useState(hmrVersion);
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
const hot = typeof import.meta !== "undefined" ? import.meta.hot : void 0;
|
|
405
|
+
if (hot && typeof hot.on === "function") {
|
|
406
|
+
const handler = () => setLocalHmrVersion((v) => v + 1);
|
|
407
|
+
hot.on("vite:afterUpdate", handler);
|
|
408
|
+
return () => {
|
|
409
|
+
if (typeof hot.off === "function") {
|
|
410
|
+
hot.off("vite:afterUpdate", handler);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}, []);
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
ensureModelContextPolyfill();
|
|
417
|
+
if (typeof navigator === "undefined" || !("modelContext" in navigator)) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
patchModelContextEventSupport();
|
|
421
|
+
const registeredNames = [];
|
|
422
|
+
for (const [name, fn] of Object.entries(toolsRef.current)) {
|
|
423
|
+
const schema = fn.__webmcpSchema;
|
|
424
|
+
if (!schema) continue;
|
|
425
|
+
registerScopedTool(
|
|
426
|
+
{
|
|
427
|
+
name,
|
|
428
|
+
description: schema.description,
|
|
429
|
+
inputSchema: schema.inputSchema,
|
|
430
|
+
// 通过 ref 间接调用,保证始终执行最新版本的函数
|
|
431
|
+
execute: async (input) => toolsRef.current[name](input),
|
|
432
|
+
annotations: { readOnlyHint: schema.readOnly ?? false }
|
|
433
|
+
},
|
|
434
|
+
{ scope: "component", scopeId: scopeIdRef.current }
|
|
435
|
+
);
|
|
436
|
+
registeredNames.push(name);
|
|
437
|
+
}
|
|
438
|
+
if (registeredNames.length > 0) {
|
|
439
|
+
notifyToolsChanged();
|
|
440
|
+
}
|
|
441
|
+
return () => {
|
|
442
|
+
let unregisteredAny = false;
|
|
443
|
+
for (const name of registeredNames) {
|
|
444
|
+
const shouldUnregister = unregisterScopedTool(name, {
|
|
445
|
+
scope: "component",
|
|
446
|
+
scopeId: scopeIdRef.current
|
|
447
|
+
});
|
|
448
|
+
if (shouldUnregister) {
|
|
449
|
+
unregisteredAny = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (unregisteredAny) {
|
|
453
|
+
notifyToolsChanged();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}, [toolKeys, localHmrVersion]);
|
|
457
|
+
}
|
|
458
|
+
export {
|
|
459
|
+
registerGlobalTools,
|
|
460
|
+
useWebMcpTools
|
|
461
|
+
};
|
|
462
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":";AAGA,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,SAAS,0BAA0B,6BAA6B;AAEhE,IAAI,YAAY;AAYT,SAAS,6BAAmC;AACjD,MAAI,UAAW;AACf,cAAY;AACZ,MAAI,OAAO,cAAc,YAAa;AACtC,MAAI,kBAAkB,UAAW;AACjC,MAAI;AACF,6BAAyB,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,SAAS,WAAW,QAAQ,gBAAgB;AAU5C,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,WAAW,OAAO,MAAM;AAC9B,WAAS,UAAU;AAGnB,QAAM,aAAa,OAAe,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,IAAI,SAAS,UAAU;AACjE,YAAU,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,YAAU,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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webmcp-nexus-sdk",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mcp-b/webmcp-polyfill": "^2.3.2"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
31
|
+
"@testing-library/react": "^16.3.2",
|
|
32
|
+
"@types/react": "^19.2.14",
|
|
33
|
+
"jsdom": "^29.0.2",
|
|
34
|
+
"react": "^19.2.5",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"vitest": "^4.1.5"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"clean": "rm -rf dist"
|
|
43
|
+
}
|
|
44
|
+
}
|