simple-webmcp 0.1.0 → 0.2.0
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/.agents/skills/webmcp-simple/SKILL.md +27 -12
- package/README.md +194 -72
- package/dist/dev-polyfill.cjs +68 -0
- package/dist/dev-polyfill.cjs.map +1 -0
- package/dist/dev-polyfill.d.cts +1 -0
- package/dist/dev-polyfill.d.ts +1 -0
- package/dist/dev-polyfill.js +62 -0
- package/dist/dev-polyfill.js.map +1 -0
- package/dist/devtools.cjs +452 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +14 -0
- package/dist/devtools.d.ts +14 -0
- package/dist/devtools.js +449 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +37 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -21
- package/dist/index.d.ts +4 -21
- package/dist/index.js +37 -37
- package/dist/index.js.map +1 -1
- package/dist/inspect.cjs +342 -0
- package/dist/inspect.cjs.map +1 -0
- package/dist/inspect.d.cts +37 -0
- package/dist/inspect.d.ts +37 -0
- package/dist/inspect.js +334 -0
- package/dist/inspect.js.map +1 -0
- package/dist/polyfill.cjs.map +1 -1
- package/dist/polyfill.d.cts +15 -7
- package/dist/polyfill.d.ts +15 -7
- package/dist/polyfill.js.map +1 -1
- package/dist/react.cjs +850 -12
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +7 -10
- package/dist/react.d.ts +7 -10
- package/dist/react.js +851 -14
- package/dist/react.js.map +1 -1
- package/dist/registry-BcPw1qCf.d.cts +38 -0
- package/dist/registry-C6Pz_Rja.d.ts +38 -0
- package/dist/testing.cjs +68 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +62 -0
- package/dist/testing.js.map +1 -0
- package/dist/{types-D-cwSfEU.d.cts → types-DH78HDAE.d.cts} +2 -2
- package/dist/{types-D-cwSfEU.d.ts → types-DH78HDAE.d.ts} +2 -2
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/package.json +30 -1
package/dist/inspect.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// src/internal/utils.ts
|
|
2
|
+
function isWebMCPSupported() {
|
|
3
|
+
if (typeof document === "undefined") return false;
|
|
4
|
+
const docAny = document;
|
|
5
|
+
return !!docAny.modelContext && typeof docAny.modelContext.registerTool === "function";
|
|
6
|
+
}
|
|
7
|
+
function getModelContext() {
|
|
8
|
+
if (typeof document === "undefined") return null;
|
|
9
|
+
const docAny = document;
|
|
10
|
+
if (docAny.modelContext && typeof docAny.modelContext.registerTool === "function") {
|
|
11
|
+
return docAny.modelContext;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/errors.ts
|
|
17
|
+
var SimpleWebMCPError = class extends Error {
|
|
18
|
+
constructor(message, code, cause) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.cause = cause;
|
|
22
|
+
this.name = this.constructor.name;
|
|
23
|
+
if (Error.captureStackTrace) {
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
name: this.name,
|
|
30
|
+
message: this.message,
|
|
31
|
+
code: this.code,
|
|
32
|
+
cause: this.cause
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var NotSupportedError = class extends SimpleWebMCPError {
|
|
37
|
+
constructor(message = "WebMCP is not supported in this environment", opts) {
|
|
38
|
+
super(message, "NOT_SUPPORTED", opts?.cause);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var NotAllowedError = class extends SimpleWebMCPError {
|
|
42
|
+
constructor(message = "WebMCP tools are blocked by Permissions Policy", opts) {
|
|
43
|
+
super(message, "NOT_ALLOWED", opts?.cause);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var RegistrationError = class extends SimpleWebMCPError {
|
|
47
|
+
constructor(message, opts) {
|
|
48
|
+
super(message, "REGISTRATION_ERROR", opts?.cause);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/internal/registry.ts
|
|
53
|
+
var Registry = class {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
56
|
+
}
|
|
57
|
+
// expose for devtools/tests — now includes contract for inspect
|
|
58
|
+
list() {
|
|
59
|
+
return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status, contract: e.contract }));
|
|
60
|
+
}
|
|
61
|
+
get(name) {
|
|
62
|
+
return this.entries.get(name);
|
|
63
|
+
}
|
|
64
|
+
getContract(name) {
|
|
65
|
+
return this.entries.get(name)?.contract;
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
for (const e of this.entries.values()) {
|
|
69
|
+
try {
|
|
70
|
+
e.controller.abort();
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.entries.clear();
|
|
75
|
+
}
|
|
76
|
+
isRegistered(name) {
|
|
77
|
+
return this.entries.get(name)?.status === "registered";
|
|
78
|
+
}
|
|
79
|
+
getStatus(name) {
|
|
80
|
+
return this.entries.get(name)?.status ?? "unregistered";
|
|
81
|
+
}
|
|
82
|
+
async register(contract, opts) {
|
|
83
|
+
const name = contract.name;
|
|
84
|
+
const existing = this.entries.get(name);
|
|
85
|
+
if (existing && (existing.status === "registered" || existing.status === "registering")) {
|
|
86
|
+
return existing.unregister;
|
|
87
|
+
}
|
|
88
|
+
if (!isWebMCPSupported()) {
|
|
89
|
+
if (opts?.signal?.aborted) return () => {
|
|
90
|
+
};
|
|
91
|
+
return () => {
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const modelContext = getModelContext();
|
|
95
|
+
if (!modelContext) throw new NotSupportedError();
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
if (opts?.signal) {
|
|
98
|
+
if (opts.signal.aborted) {
|
|
99
|
+
controller.abort();
|
|
100
|
+
} else {
|
|
101
|
+
opts.signal.addEventListener(
|
|
102
|
+
"abort",
|
|
103
|
+
() => {
|
|
104
|
+
try {
|
|
105
|
+
controller.abort();
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{ once: true }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const entry = {
|
|
114
|
+
name,
|
|
115
|
+
controller,
|
|
116
|
+
status: "registering",
|
|
117
|
+
promise: null,
|
|
118
|
+
unregister: () => {
|
|
119
|
+
if (entry.status === "registered" || entry.status === "registering") {
|
|
120
|
+
entry.status = "unregistering";
|
|
121
|
+
try {
|
|
122
|
+
controller.abort();
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
entry.status = "unregistered";
|
|
126
|
+
this.entries.delete(name);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
contract,
|
|
130
|
+
execute: opts?.execute
|
|
131
|
+
};
|
|
132
|
+
this.entries.set(name, entry);
|
|
133
|
+
const wrappedExecute = opts?.execute ?? (async (args) => ({ content: [{ type: "text", text: `no execute for ${name}` }] }));
|
|
134
|
+
try {
|
|
135
|
+
const regPromise = modelContext.registerTool(
|
|
136
|
+
{
|
|
137
|
+
name: contract.name,
|
|
138
|
+
description: contract.description,
|
|
139
|
+
inputSchema: contract.inputSchema,
|
|
140
|
+
outputSchema: contract.outputSchema,
|
|
141
|
+
annotations: contract.annotations,
|
|
142
|
+
execute: wrappedExecute
|
|
143
|
+
},
|
|
144
|
+
{ signal: controller.signal }
|
|
145
|
+
);
|
|
146
|
+
entry.promise = regPromise;
|
|
147
|
+
await regPromise;
|
|
148
|
+
if (controller.signal.aborted) {
|
|
149
|
+
entry.status = "unregistered";
|
|
150
|
+
this.entries.delete(name);
|
|
151
|
+
} else {
|
|
152
|
+
entry.status = "registered";
|
|
153
|
+
}
|
|
154
|
+
controller.signal.addEventListener("abort", () => {
|
|
155
|
+
entry.status = "unregistered";
|
|
156
|
+
this.entries.delete(name);
|
|
157
|
+
}, { once: true });
|
|
158
|
+
return entry.unregister;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
entry.status = "error";
|
|
161
|
+
this.entries.delete(name);
|
|
162
|
+
const msg = err?.message || String(err);
|
|
163
|
+
if (err?.name === "NotAllowedError" || /NotAllowedError|Permissions Policy|blocked/i.test(msg)) {
|
|
164
|
+
throw new NotAllowedError(msg, { cause: err });
|
|
165
|
+
}
|
|
166
|
+
throw new RegistrationError(`Failed to register tool "${name}": ${msg}`, { cause: err });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
unregister(name) {
|
|
170
|
+
const entry = this.entries.get(name);
|
|
171
|
+
if (!entry) return;
|
|
172
|
+
try {
|
|
173
|
+
entry.controller.abort();
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
this.entries.delete(name);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var REGISTRY_KEY = "__simpleWebmcpRegistry";
|
|
180
|
+
var globalAny = globalThis;
|
|
181
|
+
var registry = globalAny[REGISTRY_KEY] ?? new Registry();
|
|
182
|
+
if (!globalAny[REGISTRY_KEY]) {
|
|
183
|
+
globalAny[REGISTRY_KEY] = registry;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/inspect.ts
|
|
187
|
+
function getModelContextAny() {
|
|
188
|
+
return getModelContext();
|
|
189
|
+
}
|
|
190
|
+
function listTools() {
|
|
191
|
+
const map = /* @__PURE__ */ new Map();
|
|
192
|
+
for (const entry of registry.list()) {
|
|
193
|
+
if (entry.contract) {
|
|
194
|
+
map.set(entry.name, {
|
|
195
|
+
...entry.contract,
|
|
196
|
+
status: entry.status,
|
|
197
|
+
registered: entry.status === "registered",
|
|
198
|
+
source: "registry"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const mc = getModelContextAny();
|
|
203
|
+
if (mc) {
|
|
204
|
+
let nativeTools = [];
|
|
205
|
+
try {
|
|
206
|
+
if (typeof mc.getTools === "function") {
|
|
207
|
+
nativeTools = mc.getTools() ?? [];
|
|
208
|
+
if (nativeTools instanceof Promise) {
|
|
209
|
+
nativeTools = [];
|
|
210
|
+
}
|
|
211
|
+
} else if (typeof mc.listTools === "function") {
|
|
212
|
+
nativeTools = mc.listTools() ?? [];
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
for (const t of nativeTools) {
|
|
217
|
+
if (!t?.name) continue;
|
|
218
|
+
const existing = map.get(t.name);
|
|
219
|
+
if (existing) {
|
|
220
|
+
existing.source = "both";
|
|
221
|
+
} else {
|
|
222
|
+
map.set(t.name, {
|
|
223
|
+
name: t.name,
|
|
224
|
+
description: t.description ?? "",
|
|
225
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} },
|
|
226
|
+
outputSchema: t.outputSchema,
|
|
227
|
+
annotations: t.annotations,
|
|
228
|
+
status: "registered",
|
|
229
|
+
registered: true,
|
|
230
|
+
source: "modelContext"
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return Array.from(map.values());
|
|
236
|
+
}
|
|
237
|
+
async function listToolsAsync() {
|
|
238
|
+
const sync = listTools();
|
|
239
|
+
const mc = getModelContextAny();
|
|
240
|
+
if (mc && typeof mc.getTools === "function") {
|
|
241
|
+
try {
|
|
242
|
+
const native = await mc.getTools();
|
|
243
|
+
if (Array.isArray(native)) {
|
|
244
|
+
const map = new Map(sync.map((t) => [t.name, t]));
|
|
245
|
+
for (const t of native) {
|
|
246
|
+
if (!map.has(t.name)) {
|
|
247
|
+
map.set(t.name, {
|
|
248
|
+
name: t.name,
|
|
249
|
+
description: t.description ?? "",
|
|
250
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} },
|
|
251
|
+
outputSchema: t.outputSchema,
|
|
252
|
+
annotations: t.annotations,
|
|
253
|
+
status: "registered",
|
|
254
|
+
registered: true,
|
|
255
|
+
source: "modelContext"
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return Array.from(map.values());
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return sync;
|
|
265
|
+
}
|
|
266
|
+
function getTool(name) {
|
|
267
|
+
return listTools().find((t) => t.name === name);
|
|
268
|
+
}
|
|
269
|
+
async function invokeTool(name, args) {
|
|
270
|
+
const mc = getModelContextAny();
|
|
271
|
+
if (mc) {
|
|
272
|
+
try {
|
|
273
|
+
if (typeof mc.executeTool === "function") {
|
|
274
|
+
return await mc.executeTool(name, args);
|
|
275
|
+
}
|
|
276
|
+
if (typeof mc.invokeTool === "function") {
|
|
277
|
+
return await mc.invokeTool(name, args);
|
|
278
|
+
}
|
|
279
|
+
if (typeof mc.callTool === "function") {
|
|
280
|
+
return await mc.callTool(name, args);
|
|
281
|
+
}
|
|
282
|
+
} catch (e) {
|
|
283
|
+
if (e?.name === "NotFoundError" || /not found/i.test(String(e?.message))) ; else {
|
|
284
|
+
throw e;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const entry = registry.get(name);
|
|
289
|
+
if (entry?.execute) {
|
|
290
|
+
return await entry.execute(args);
|
|
291
|
+
}
|
|
292
|
+
if (mc && mc._tools) {
|
|
293
|
+
const toolsMap = mc._tools;
|
|
294
|
+
const found = toolsMap.get(name);
|
|
295
|
+
if (found?.def?.execute) {
|
|
296
|
+
return await found.def.execute(args);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
throw new Error(`Tool "${name}" not found or not invokable in this environment`);
|
|
300
|
+
}
|
|
301
|
+
function isSupported() {
|
|
302
|
+
return isWebMCPSupported();
|
|
303
|
+
}
|
|
304
|
+
function onToolsChanged(callback) {
|
|
305
|
+
let cancelled = false;
|
|
306
|
+
const mc = getModelContextAny();
|
|
307
|
+
if (mc && typeof mc.addEventListener === "function") {
|
|
308
|
+
const handler = () => {
|
|
309
|
+
if (!cancelled) callback(listTools());
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
mc.addEventListener("toolchange", handler);
|
|
313
|
+
return () => {
|
|
314
|
+
cancelled = true;
|
|
315
|
+
try {
|
|
316
|
+
mc.removeEventListener("toolchange", handler);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const interval = setInterval(() => {
|
|
324
|
+
if (!cancelled) callback(listTools());
|
|
325
|
+
}, 500);
|
|
326
|
+
return () => {
|
|
327
|
+
cancelled = true;
|
|
328
|
+
clearInterval(interval);
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export { getTool, invokeTool, isSupported, listTools, listToolsAsync, onToolsChanged, registry };
|
|
333
|
+
//# sourceMappingURL=inspect.js.map
|
|
334
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/utils.ts","../src/errors.ts","../src/internal/registry.ts","../src/inspect.ts"],"names":[],"mappings":"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","file":"inspect.js","sourcesContent":["import type { JsonSchema, ModelContextLike } from '../types.js';\n\n/**\n * Convert camelCase / PascalCase to snake_case for tool naming.\n * searchCustomers → search_customers\n * SearchCustomers → search_customers\n */\nexport function toSnakeCase(name: string): string {\n if (!name) return 'anonymous_tool';\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')\n .toLowerCase()\n .replace(/[^a-z0-9_]+/g, '_')\n .replace(/^_+|_+$/g, '') || 'anonymous_tool';\n}\n\n/**\n * Totally minimal doc extraction — best effort.\n * Full JSDoc inference is build-time. Runtime we just note presence.\n */\nexport function getFunctionName(fn: Function): string {\n return (fn as any).displayName || fn.name || '';\n}\n\n/**\n * Check if WebMCP is supported (native or polyfilled)\n */\nexport function isWebMCPSupported(): boolean {\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n return !!docAny.modelContext && typeof docAny.modelContext.registerTool === 'function';\n}\n\nexport function getModelContext(): ModelContextLike | null {\n if (typeof document === 'undefined') return null;\n const docAny = document as any;\n if (docAny.modelContext && typeof docAny.modelContext.registerTool === 'function') {\n return docAny.modelContext as ModelContextLike;\n }\n return null;\n}\n\n/**\n * Heuristic check if value looks like JSON Schema (vs StandardSchema)\n */\nexport function looksLikeJsonSchema(value: unknown): value is JsonSchema {\n if (!value || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n // JSON Schema typically has `type` at top, no `~standard`\n if ('~standard' in obj) return false;\n // Has type or properties or $ref etc\n return (\n 'type' in obj ||\n 'properties' in obj ||\n '$ref' in obj ||\n 'anyOf' in obj ||\n 'oneOf' in obj ||\n 'allOf' in obj ||\n 'enum' in obj\n );\n}\n\n/**\n * Check if value is StandardSchema\n */\nexport function isStandardSchema(value: unknown): boolean {\n return (\n !!value &&\n typeof value === 'object' &&\n '~standard' in (value as any) &&\n (value as any)['~standard']?.validate != null &&\n typeof (value as any)['~standard'].validate === 'function'\n );\n}\n\n/**\n * Cheap JSDoc/description fallback — runtime can't parse JSDoc,\n * but we check if fn has __webmcpDescription attached by build plugin or manual.\n */\nexport function getDescription(fn: Function, override?: string): string {\n if (override != null) return override;\n const anyFn = fn as any;\n if (typeof anyFn.__webmcpDescription === 'string') return anyFn.__webmcpDescription;\n // No JSDoc at runtime — return empty and warn elsewhere\n return '';\n}\n\nlet warnedNoDesc = false;\nexport function warnNoDescription(fn: Function) {\n if (warnedNoDesc) return;\n // Only warn once per page load\n warnedNoDesc = true;\n if (typeof console !== 'undefined' && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n // eslint-disable-next-line no-console\n console.warn(\n `[simple-webmcp] tool \"${getFunctionName(fn) || 'anonymous'}\" has no description. Add \\`description:\"...\"\\` or JSDoc. See https://github.com/emingure/simple-webmcp`,\n );\n } else if (typeof console !== 'undefined') {\n // In browser dev, also warn once\n // eslint-disable-next-line no-console\n console.warn(`[simple-webmcp] tool \"${getFunctionName(fn) || 'anonymous'}\" has no description.`);\n }\n}\n","/**\n * Base error for simple-webmcp\n */\nexport class SimpleWebMCPError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = this.constructor.name;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n cause: this.cause,\n };\n }\n}\n\nexport class NotSupportedError extends SimpleWebMCPError {\n constructor(message = 'WebMCP is not supported in this environment', opts?: { cause?: unknown }) {\n super(message, 'NOT_SUPPORTED', opts?.cause);\n }\n}\n\nexport class NotAllowedError extends SimpleWebMCPError {\n constructor(message = 'WebMCP tools are blocked by Permissions Policy', opts?: { cause?: unknown }) {\n super(message, 'NOT_ALLOWED', opts?.cause);\n }\n}\n\nexport class RegistrationError extends SimpleWebMCPError {\n constructor(message: string, opts?: { cause?: unknown }) {\n super(message, 'REGISTRATION_ERROR', opts?.cause);\n }\n}\n\nexport class ValidationError extends SimpleWebMCPError {\n constructor(message: string, opts?: { cause?: unknown }) {\n super(message, 'VALIDATION_ERROR', opts?.cause);\n }\n}\n\nexport class ConfigurationError extends SimpleWebMCPError {\n constructor(message: string, opts?: { cause?: unknown }) {\n super(message, 'CONFIGURATION_ERROR', opts?.cause);\n }\n}\n","import type { ToolContract, RegistrationStatus, ModelContextLike } from '../types.js';\nimport { getModelContext, isWebMCPSupported } from './utils.js';\nimport { RegistrationError, NotSupportedError, NotAllowedError } from '../errors.js';\nimport { wrapExecute } from './normalize.js';\n\n/**\n * Global tool registry — tracks registration state per tool name.\n * Handles async registerTool (Promise<void>) + AbortSignal lifecycle per WebMCP spec (current types).\n * Deduplicates StrictMode double-mount.\n */\n\ntype Entry = {\n name: string;\n controller: AbortController;\n status: RegistrationStatus;\n promise: Promise<void> | null;\n unregister: () => void;\n contract?: ToolContract;\n execute?: (args: unknown, ctx?: unknown) => Promise<any>;\n};\n\nclass Registry {\n private entries = new Map<string, Entry>();\n // expose for devtools/tests — now includes contract for inspect\n list(): Array<{ name: string; status: RegistrationStatus; contract?: ToolContract }> {\n return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status, contract: e.contract }));\n }\n\n get(name: string): Entry | undefined {\n return this.entries.get(name);\n }\n\n getContract(name: string): ToolContract | undefined {\n return this.entries.get(name)?.contract;\n }\n\n clear() {\n for (const e of this.entries.values()) {\n try { e.controller.abort(); } catch {}\n }\n this.entries.clear();\n }\n\n isRegistered(name: string): boolean {\n return this.entries.get(name)?.status === 'registered';\n }\n\n getStatus(name: string): RegistrationStatus {\n return this.entries.get(name)?.status ?? 'unregistered';\n }\n\n async register(\n contract: ToolContract,\n opts?: { signal?: AbortSignal; execute?: (args: unknown, ctx?: unknown) => Promise<any> },\n ): Promise<() => void> {\n const name = contract.name;\n const existing = this.entries.get(name);\n if (existing && (existing.status === 'registered' || existing.status === 'registering')) {\n // Dedup — return existing unregister\n return existing.unregister;\n }\n\n if (!isWebMCPSupported()) {\n // Do not claim success. `supported` and `registered` must be mutually exclusive.\n // Return a no-op unregister; caller should check `isWebMCPSupported()` or\n // `tool.status === 'unsupported'` instead of assuming `registered`.\n if (opts?.signal?.aborted) return () => {};\n return () => {};\n }\n\n const modelContext = getModelContext();\n if (!modelContext) throw new NotSupportedError();\n\n const controller = new AbortController();\n // Wire external signal to internal\n if (opts?.signal) {\n if (opts.signal.aborted) {\n controller.abort();\n } else {\n opts.signal.addEventListener(\n 'abort',\n () => {\n try {\n controller.abort();\n } catch {}\n },\n { once: true },\n );\n }\n }\n\n const entry: Entry = {\n name,\n controller,\n status: 'registering',\n promise: null,\n unregister: () => {\n if (entry.status === 'registered' || entry.status === 'registering') {\n entry.status = 'unregistering';\n try {\n controller.abort();\n } catch {}\n // Small delay to allow spec to unregister\n entry.status = 'unregistered';\n this.entries.delete(name);\n }\n },\n contract,\n execute: opts?.execute,\n };\n this.entries.set(name, entry);\n\n const wrappedExecute = opts?.execute ?? (async (args: unknown) => ({ content: [{ type: 'text', text: `no execute for ${name}` }] }));\n\n try {\n const regPromise = (modelContext as ModelContextLike).registerTool(\n {\n name: contract.name,\n description: contract.description,\n inputSchema: contract.inputSchema,\n outputSchema: contract.outputSchema,\n annotations: contract.annotations,\n execute: wrappedExecute,\n },\n { signal: controller.signal },\n );\n entry.promise = regPromise;\n await regPromise;\n // If not aborted in-flight\n if (controller.signal.aborted) {\n entry.status = 'unregistered';\n this.entries.delete(name);\n } else {\n entry.status = 'registered';\n }\n // Cleanup on abort signal\n controller.signal.addEventListener('abort', () => {\n entry.status = 'unregistered';\n this.entries.delete(name);\n }, { once: true });\n\n return entry.unregister;\n } catch (err: any) {\n entry.status = 'error';\n this.entries.delete(name);\n const msg = err?.message || String(err);\n // Detect Permissions Policy NotAllowedError per spec\n if (err?.name === 'NotAllowedError' || /NotAllowedError|Permissions Policy|blocked/i.test(msg)) {\n throw new NotAllowedError(msg, { cause: err });\n }\n throw new RegistrationError(`Failed to register tool \"${name}\": ${msg}`, { cause: err });\n }\n }\n\n unregister(name: string) {\n const entry = this.entries.get(name);\n if (!entry) return;\n try {\n entry.controller.abort();\n } catch {}\n this.entries.delete(name);\n }\n}\n\nconst REGISTRY_KEY = '__simpleWebmcpRegistry' as const;\nconst globalAny = globalThis as any;\nexport const registry: Registry =\n (globalAny[REGISTRY_KEY] as Registry) ?? new Registry();\nif (!globalAny[REGISTRY_KEY]) {\n globalAny[REGISTRY_KEY] = registry;\n}\n\n// Export wrap helper for external tests\nexport function getRegistry() {\n return registry;\n}\n","/**\n * simple-webmcp/inspect — programmatic inspect for registered tools.\n * Works with native WebMCP (document.modelContext) or dev shim / registry fallback.\n * Used by demo and devtools to list/invoke tools without an LLM.\n */\n\nimport type { ToolContract, RegistrationStatus } from './types.js';\nimport { registry } from './internal/registry.js';\nimport { getModelContext, isWebMCPSupported } from './internal/utils.js';\n\nexport type InspectedTool = ToolContract & {\n status: RegistrationStatus;\n registered: boolean;\n source: 'registry' | 'modelContext' | 'both';\n};\n\nfunction getModelContextAny(): any {\n return getModelContext() as any;\n}\n\n/**\n * List all tools known to this page — merges registry + modelContext.getTools().\n * Handles Chrome's `getTools()` / `listTools()` / shim variations.\n */\nexport function listTools(): InspectedTool[] {\n const map = new Map<string, InspectedTool>();\n\n // From registry (our wrapper, always available)\n for (const entry of registry.list()) {\n if (entry.contract) {\n map.set(entry.name, {\n ...entry.contract,\n status: entry.status,\n registered: entry.status === 'registered',\n source: 'registry',\n });\n }\n }\n\n // From native modelContext.getTools() / listTools() if available\n const mc: any = getModelContextAny();\n if (mc) {\n let nativeTools: any[] = [];\n try {\n if (typeof mc.getTools === 'function') {\n nativeTools = mc.getTools() ?? [];\n // getTools may be async in some specs\n if (nativeTools instanceof Promise) {\n // For sync listTools(), we can't await — caller can use listToolsAsync()\n nativeTools = [];\n }\n } else if (typeof mc.listTools === 'function') {\n nativeTools = mc.listTools() ?? [];\n }\n } catch {\n // ignore\n }\n for (const t of nativeTools) {\n if (!t?.name) continue;\n const existing = map.get(t.name);\n if (existing) {\n existing.source = 'both';\n // Prefer native status if registry says unregistered but native has it\n // Keep our contract's inputSchema as source of truth\n } else {\n map.set(t.name, {\n name: t.name,\n description: t.description ?? '',\n inputSchema: t.inputSchema ?? { type: 'object', properties: {} },\n outputSchema: t.outputSchema,\n annotations: t.annotations,\n status: 'registered',\n registered: true,\n source: 'modelContext',\n });\n }\n }\n }\n\n return Array.from(map.values());\n}\n\n/**\n * Async variant that awaits native getTools() if it returns a promise.\n */\nexport async function listToolsAsync(): Promise<InspectedTool[]> {\n const sync = listTools();\n const mc: any = getModelContextAny();\n if (mc && typeof mc.getTools === 'function') {\n try {\n const native = await mc.getTools();\n if (Array.isArray(native)) {\n const map = new Map(sync.map((t) => [t.name, t]));\n for (const t of native) {\n if (!map.has(t.name)) {\n map.set(t.name, {\n name: t.name,\n description: t.description ?? '',\n inputSchema: t.inputSchema ?? { type: 'object', properties: {} },\n outputSchema: t.outputSchema,\n annotations: t.annotations,\n status: 'registered',\n registered: true,\n source: 'modelContext',\n });\n }\n }\n return Array.from(map.values());\n }\n } catch {}\n }\n return sync;\n}\n\nexport function getTool(name: string): InspectedTool | undefined {\n return listTools().find((t) => t.name === name);\n}\n\n/**\n * Invoke a tool by name with args. Tries in order:\n * 1. modelContext.executeTool / invokeTool / execute (native or shim)\n * 2. registry's stored execute (via our wrapper)\n */\nexport async function invokeTool(name: string, args: unknown): Promise<unknown> {\n const mc: any = getModelContextAny();\n\n // Try native modelContext invoke paths\n if (mc) {\n try {\n if (typeof mc.executeTool === 'function') {\n return await mc.executeTool(name, args);\n }\n if (typeof mc.invokeTool === 'function') {\n return await mc.invokeTool(name, args);\n }\n if (typeof mc.callTool === 'function') {\n return await mc.callTool(name, args);\n }\n } catch (e) {\n // Fall through to registry\n if ((e as any)?.name === 'NotFoundError' || /not found/i.test(String((e as any)?.message))) {\n // continue\n } else {\n throw e;\n }\n }\n }\n\n // Fallback: registry's stored execute (our wrapper's wrappedExecute)\n const entry = registry.get(name);\n if (entry?.execute) {\n return await entry.execute(args as any);\n }\n\n // Also try shim's direct invoke if modelContext is shim with _tools\n if (mc && mc._tools) {\n const toolsMap: Map<string, any> = mc._tools;\n const found = toolsMap.get(name);\n if (found?.def?.execute) {\n return await found.def.execute(args);\n }\n }\n\n throw new Error(`Tool \"${name}\" not found or not invokable in this environment`);\n}\n\nexport function isSupported(): boolean {\n return isWebMCPSupported();\n}\n\n/**\n * Subscribe to tool changes. Uses `toolchange` event if available, otherwise polls registry.\n */\nexport function onToolsChanged(callback: (tools: InspectedTool[]) => void): () => void {\n let cancelled = false;\n const mc: any = getModelContextAny();\n\n // Native toolchange event (Chrome spec)\n if (mc && typeof mc.addEventListener === 'function') {\n const handler = () => {\n if (!cancelled) callback(listTools());\n };\n try {\n mc.addEventListener('toolchange', handler);\n return () => {\n cancelled = true;\n try { mc.removeEventListener('toolchange', handler); } catch {}\n };\n } catch {}\n }\n\n // Fallback: poll registry every 500ms\n const interval = setInterval(() => {\n if (!cancelled) callback(listTools());\n }, 500);\n\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n}\n\n// Re-export for convenience\nexport { registry };\n"]}
|
package/dist/polyfill.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";;;;;AAwBA,IAAI,SAAA,GAAY,KAAA;AAET,SAAS,gBAAgB,IAAA,EAAiC;AAC/D,EAAA,IAAI,SAAA,IAAa,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,IAAA;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAA;AACf,EAAA,IAAI,MAAA,CAAO,YAAA,IAAgB,CAAC,IAAA,EAAM,KAAA,EAAO;AACvC,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT;AAIA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAMhB;AAEF,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,WAAA,EAAa,IAAA;AAAA,IACb,MAAA,EAAQ,KAAA;AAAA,IACR,MAAM,YAAA,CAAa,GAAA,EAAUA,KAAAA,EAAiC;AAC5D,MAAA,MAAM,OAAO,GAAA,EAAK,IAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,6BAA6B,CAAA;AACxD,MAAA,IAAI,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAG;AAEnB,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,IAAI,IAAA,EAAM,EAAE,KAAK,MAAA,EAAQA,KAAAA,EAAM,QAAQ,CAAA;AAC7C,MAAA,IAAIA,OAAM,MAAA,EAAQ;AAChB,QAAA,MAAM,UAAU,MAAM;AACpB,UAAA,IAAI,KAAA,CAAM,IAAI,IAAI,CAAA,EAAG,WAAWA,KAAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AAAA,QAChE,CAAA;AACA,QAAA,IAAIA,KAAAA,CAAK,OAAO,OAAA,EAAS;AACvB,UAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,QACnB,CAAA,MAAO;AACL,UAAAA,KAAAA,CAAK,OAAO,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,SAAA,GAAY;AACV,MAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAA;AAAA,IACpD,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,IAAA,EAAc,IAAA,EAAe;AAC5C,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC5B,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,IAAI,CAAA,UAAA,CAAY,CAAA;AACpD,MAAA,OAAO,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,IAC/B;AAAA,GACF;AAEA,EAAA,MAAA,CAAO,YAAA,GAAe,QAAA;AACtB,EAAA,SAAA,GAAY,IAAA;AACZ,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,YAAA,GAAwB;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,OAAO,CAAC,CAAE,QAAA,CAAiB,YAAA,EAAc,WAAA;AAC3C;AAGA,IAAI,OAAO,aAAa,WAAA,EAAa;AAEnC,EAAA,cAAA,CAAe,MAAM;AACnB,IAAA,MAAM,MAAA,GAAS,QAAA;AACf,IAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,MAAA,eAAA,EAAgB;AAAA,IAClB;AAAA,EACF,CAAC,CAAA;AACH;AAEA,IAAO,gBAAA,GAAQ","file":"polyfill.cjs","sourcesContent":["/**\n * simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).\n *\n * This is an in-memory registry for tests, Storybook, and local dev without Chrome.\n * It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.\n *\n * For real cross-browser WebMCP (Firefox/Safari), use the dedicated\n * `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)\n * — ~6k weekly downloads, broader MCP ecosystem. Example:\n * `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`\n *\n * In production Chrome with native `document.modelContext`, this is a no-op\n * (detected via native presence). Prefer the real polyfill for production\n * cross-browser, and this shim for testing.\n *\n * New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and\n * `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`\n * is kept for backward compat but will be documented as dev shim.\n */\n\nexport type PolyfillOptions = {\n force?: boolean;\n};\n\nlet installed = false;\n\nexport function installPolyfill(opts?: PolyfillOptions): boolean {\n if (installed && !opts?.force) return true;\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n if (docAny.modelContext && !opts?.force) {\n installed = true;\n return true;\n }\n\n // Minimal polyfill — tracks tools in memory, invokes via same ABI.\n // Not a full MCP transport; sufficient for registry + tests + devtools.\n const tools = new Map<\n string,\n {\n def: any;\n signal?: AbortSignal;\n }\n >();\n\n const polyfill = {\n _isPolyfill: true,\n _tools: tools,\n async registerTool(def: any, opts?: { signal?: AbortSignal }) {\n const name = def?.name;\n if (!name) throw new Error('registerTool: name required');\n if (tools.has(name)) {\n // Dedup like native — ignore second registration\n return;\n }\n tools.set(name, { def, signal: opts?.signal });\n if (opts?.signal) {\n const onAbort = () => {\n if (tools.get(name)?.signal === opts.signal) tools.delete(name);\n };\n if (opts.signal.aborted) {\n tools.delete(name);\n } else {\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n },\n listTools() {\n return Array.from(tools.values()).map((v) => v.def);\n },\n async invokeTool(name: string, args: unknown) {\n const entry = tools.get(name);\n if (!entry) throw new Error(`Tool ${name} not found`);\n return entry.def.execute(args);\n },\n };\n\n docAny.modelContext = polyfill;\n installed = true;\n return true;\n}\n\nexport function isPolyfilled(): boolean {\n if (typeof document === 'undefined') return false;\n return !!(document as any).modelContext?._isPolyfill;\n}\n\n// Auto-install side-effect import: `import 'simple-webmcp/polyfill'`\nif (typeof document !== 'undefined') {\n // Defer to next tick to allow native detection first\n queueMicrotask(() => {\n const docAny = document as any;\n if (!docAny.modelContext) {\n installPolyfill();\n }\n });\n}\n\nexport default installPolyfill;\n"]}
|
package/dist/polyfill.d.cts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* simple-webmcp/polyfill —
|
|
3
|
-
* We do not hard-depend on a specific shim; we attach a minimal no-op polyfill that
|
|
4
|
-
* allows `registry.register` to succeed in non-Chrome environments for dev/testing.
|
|
5
|
-
* In production Chrome with native WebMCP, this is a no-op (detected via native presence).
|
|
2
|
+
* simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This is an in-memory registry for tests, Storybook, and local dev without Chrome.
|
|
5
|
+
* It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
7
|
+
* For real cross-browser WebMCP (Firefox/Safari), use the dedicated
|
|
8
|
+
* `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)
|
|
9
|
+
* — ~6k weekly downloads, broader MCP ecosystem. Example:
|
|
10
|
+
* `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`
|
|
11
|
+
*
|
|
12
|
+
* In production Chrome with native `document.modelContext`, this is a no-op
|
|
13
|
+
* (detected via native presence). Prefer the real polyfill for production
|
|
14
|
+
* cross-browser, and this shim for testing.
|
|
15
|
+
*
|
|
16
|
+
* New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and
|
|
17
|
+
* `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`
|
|
18
|
+
* is kept for backward compat but will be documented as dev shim.
|
|
11
19
|
*/
|
|
12
20
|
type PolyfillOptions = {
|
|
13
21
|
force?: boolean;
|
package/dist/polyfill.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* simple-webmcp/polyfill —
|
|
3
|
-
* We do not hard-depend on a specific shim; we attach a minimal no-op polyfill that
|
|
4
|
-
* allows `registry.register` to succeed in non-Chrome environments for dev/testing.
|
|
5
|
-
* In production Chrome with native WebMCP, this is a no-op (detected via native presence).
|
|
2
|
+
* simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This is an in-memory registry for tests, Storybook, and local dev without Chrome.
|
|
5
|
+
* It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
7
|
+
* For real cross-browser WebMCP (Firefox/Safari), use the dedicated
|
|
8
|
+
* `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)
|
|
9
|
+
* — ~6k weekly downloads, broader MCP ecosystem. Example:
|
|
10
|
+
* `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`
|
|
11
|
+
*
|
|
12
|
+
* In production Chrome with native `document.modelContext`, this is a no-op
|
|
13
|
+
* (detected via native presence). Prefer the real polyfill for production
|
|
14
|
+
* cross-browser, and this shim for testing.
|
|
15
|
+
*
|
|
16
|
+
* New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and
|
|
17
|
+
* `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`
|
|
18
|
+
* is kept for backward compat but will be documented as dev shim.
|
|
11
19
|
*/
|
|
12
20
|
type PolyfillOptions = {
|
|
13
21
|
force?: boolean;
|
package/dist/polyfill.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../src/polyfill.ts"],"names":["opts"],"mappings":";AAwBA,IAAI,SAAA,GAAY,KAAA;AAET,SAAS,gBAAgB,IAAA,EAAiC;AAC/D,EAAA,IAAI,SAAA,IAAa,CAAC,IAAA,EAAM,KAAA,EAAO,OAAO,IAAA;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAA;AACf,EAAA,IAAI,MAAA,CAAO,YAAA,IAAgB,CAAC,IAAA,EAAM,KAAA,EAAO;AACvC,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT;AAIA,EAAA,MAAM,KAAA,uBAAY,GAAA,EAMhB;AAEF,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,WAAA,EAAa,IAAA;AAAA,IACb,MAAA,EAAQ,KAAA;AAAA,IACR,MAAM,YAAA,CAAa,GAAA,EAAUA,KAAAA,EAAiC;AAC5D,MAAA,MAAM,OAAO,GAAA,EAAK,IAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,6BAA6B,CAAA;AACxD,MAAA,IAAI,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,EAAG;AAEnB,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,IAAI,IAAA,EAAM,EAAE,KAAK,MAAA,EAAQA,KAAAA,EAAM,QAAQ,CAAA;AAC7C,MAAA,IAAIA,OAAM,MAAA,EAAQ;AAChB,QAAA,MAAM,UAAU,MAAM;AACpB,UAAA,IAAI,KAAA,CAAM,IAAI,IAAI,CAAA,EAAG,WAAWA,KAAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA;AAAA,QAChE,CAAA;AACA,QAAA,IAAIA,KAAAA,CAAK,OAAO,OAAA,EAAS;AACvB,UAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,QACnB,CAAA,MAAO;AACL,UAAAA,KAAAA,CAAK,OAAO,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,SAAA,GAAY;AACV,MAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAA;AAAA,IACpD,CAAA;AAAA,IACA,MAAM,UAAA,CAAW,IAAA,EAAc,IAAA,EAAe;AAC5C,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC5B,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,IAAI,CAAA,UAAA,CAAY,CAAA;AACpD,MAAA,OAAO,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,IAC/B;AAAA,GACF;AAEA,EAAA,MAAA,CAAO,YAAA,GAAe,QAAA;AACtB,EAAA,SAAA,GAAY,IAAA;AACZ,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,YAAA,GAAwB;AACtC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,OAAO,CAAC,CAAE,QAAA,CAAiB,YAAA,EAAc,WAAA;AAC3C;AAGA,IAAI,OAAO,aAAa,WAAA,EAAa;AAEnC,EAAA,cAAA,CAAe,MAAM;AACnB,IAAA,MAAM,MAAA,GAAS,QAAA;AACf,IAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,MAAA,eAAA,EAAgB;AAAA,IAClB;AAAA,EACF,CAAC,CAAA;AACH;AAEA,IAAO,gBAAA,GAAQ","file":"polyfill.js","sourcesContent":["/**\n * simple-webmcp/polyfill — dev/testing shim (NOT a WebMCP interoperability polyfill).\n *\n * This is an in-memory registry for tests, Storybook, and local dev without Chrome.\n * It implements `registerTool`/`listTools`/`invokeTool` but not full MCP transport.\n *\n * For real cross-browser WebMCP (Firefox/Safari), use the dedicated\n * `@mcp-b/webmcp-polyfill` (https://www.npmjs.com/package/@mcp-b/webmcp-polyfill)\n * — ~6k weekly downloads, broader MCP ecosystem. Example:\n * `npm i @mcp-b/webmcp-polyfill && import '@mcp-b/webmcp-polyfill'`\n *\n * In production Chrome with native `document.modelContext`, this is a no-op\n * (detected via native presence). Prefer the real polyfill for production\n * cross-browser, and this shim for testing.\n *\n * New aliases (preferred for clarity): `simple-webmcp/dev-polyfill` and\n * `simple-webmcp/testing` — same module, clearer intent. `simple-webmcp/polyfill`\n * is kept for backward compat but will be documented as dev shim.\n */\n\nexport type PolyfillOptions = {\n force?: boolean;\n};\n\nlet installed = false;\n\nexport function installPolyfill(opts?: PolyfillOptions): boolean {\n if (installed && !opts?.force) return true;\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n if (docAny.modelContext && !opts?.force) {\n installed = true;\n return true;\n }\n\n // Minimal polyfill — tracks tools in memory, invokes via same ABI.\n // Not a full MCP transport; sufficient for registry + tests + devtools.\n const tools = new Map<\n string,\n {\n def: any;\n signal?: AbortSignal;\n }\n >();\n\n const polyfill = {\n _isPolyfill: true,\n _tools: tools,\n async registerTool(def: any, opts?: { signal?: AbortSignal }) {\n const name = def?.name;\n if (!name) throw new Error('registerTool: name required');\n if (tools.has(name)) {\n // Dedup like native — ignore second registration\n return;\n }\n tools.set(name, { def, signal: opts?.signal });\n if (opts?.signal) {\n const onAbort = () => {\n if (tools.get(name)?.signal === opts.signal) tools.delete(name);\n };\n if (opts.signal.aborted) {\n tools.delete(name);\n } else {\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n },\n listTools() {\n return Array.from(tools.values()).map((v) => v.def);\n },\n async invokeTool(name: string, args: unknown) {\n const entry = tools.get(name);\n if (!entry) throw new Error(`Tool ${name} not found`);\n return entry.def.execute(args);\n },\n };\n\n docAny.modelContext = polyfill;\n installed = true;\n return true;\n}\n\nexport function isPolyfilled(): boolean {\n if (typeof document === 'undefined') return false;\n return !!(document as any).modelContext?._isPolyfill;\n}\n\n// Auto-install side-effect import: `import 'simple-webmcp/polyfill'`\nif (typeof document !== 'undefined') {\n // Defer to next tick to allow native detection first\n queueMicrotask(() => {\n const docAny = document as any;\n if (!docAny.modelContext) {\n installPolyfill();\n }\n });\n}\n\nexport default installPolyfill;\n"]}
|