simple-webmcp 0.1.0 → 0.3.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.
Files changed (49) hide show
  1. package/.agents/skills/webmcp-simple/SKILL.md +27 -12
  2. package/README.md +211 -71
  3. package/dist/dev-polyfill.cjs +68 -0
  4. package/dist/dev-polyfill.cjs.map +1 -0
  5. package/dist/dev-polyfill.d.cts +1 -0
  6. package/dist/dev-polyfill.d.ts +1 -0
  7. package/dist/dev-polyfill.js +62 -0
  8. package/dist/dev-polyfill.js.map +1 -0
  9. package/dist/devtools.cjs +452 -0
  10. package/dist/devtools.cjs.map +1 -0
  11. package/dist/devtools.d.cts +14 -0
  12. package/dist/devtools.d.ts +14 -0
  13. package/dist/devtools.js +449 -0
  14. package/dist/devtools.js.map +1 -0
  15. package/dist/index.cjs +350 -41
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +60 -51
  18. package/dist/index.d.ts +60 -51
  19. package/dist/index.js +344 -42
  20. package/dist/index.js.map +1 -1
  21. package/dist/inspect.cjs +342 -0
  22. package/dist/inspect.cjs.map +1 -0
  23. package/dist/inspect.d.cts +37 -0
  24. package/dist/inspect.d.ts +37 -0
  25. package/dist/inspect.js +334 -0
  26. package/dist/inspect.js.map +1 -0
  27. package/dist/polyfill.cjs.map +1 -1
  28. package/dist/polyfill.d.cts +15 -7
  29. package/dist/polyfill.d.ts +15 -7
  30. package/dist/polyfill.js.map +1 -1
  31. package/dist/react.cjs +1191 -12
  32. package/dist/react.cjs.map +1 -1
  33. package/dist/react.d.cts +28 -10
  34. package/dist/react.d.ts +28 -10
  35. package/dist/react.js +1190 -15
  36. package/dist/react.js.map +1 -1
  37. package/dist/registry-A4DdpmDn.d.cts +38 -0
  38. package/dist/registry-Dscedahn.d.ts +38 -0
  39. package/dist/testing.cjs +68 -0
  40. package/dist/testing.cjs.map +1 -0
  41. package/dist/testing.d.cts +1 -0
  42. package/dist/testing.d.ts +1 -0
  43. package/dist/testing.js +62 -0
  44. package/dist/testing.js.map +1 -0
  45. package/dist/{types-D-cwSfEU.d.cts → types-CF8kTj5O.d.cts} +61 -2
  46. package/dist/{types-D-cwSfEU.d.ts → types-CF8kTj5O.d.ts} +61 -2
  47. package/dist/zod.d.cts +1 -1
  48. package/dist/zod.d.ts +1 -1
  49. package/package.json +30 -1
package/dist/react.js CHANGED
@@ -1,19 +1,1156 @@
1
- import { useState, useMemo, useRef, useEffect } from 'react';
2
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { createContext, useContext, useMemo, useState, useRef, useEffect } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
 
4
4
  // src/internal/utils.ts
5
+ function toSnakeCase(name) {
6
+ if (!name) return "anonymous_tool";
7
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "anonymous_tool";
8
+ }
9
+ function getFunctionName(fn) {
10
+ return fn.displayName || fn.name || "";
11
+ }
5
12
  function isWebMCPSupported() {
6
13
  if (typeof document === "undefined") return false;
7
14
  const docAny = document;
8
15
  return !!docAny.modelContext && typeof docAny.modelContext.registerTool === "function";
9
16
  }
17
+ function getModelContext() {
18
+ if (typeof document === "undefined") return null;
19
+ const docAny = document;
20
+ if (docAny.modelContext && typeof docAny.modelContext.registerTool === "function") {
21
+ return docAny.modelContext;
22
+ }
23
+ return null;
24
+ }
25
+ function looksLikeJsonSchema(value) {
26
+ if (!value || typeof value !== "object") return false;
27
+ const obj = value;
28
+ if ("~standard" in obj) return false;
29
+ return "type" in obj || "properties" in obj || "$ref" in obj || "anyOf" in obj || "oneOf" in obj || "allOf" in obj || "enum" in obj;
30
+ }
31
+ function isStandardSchema(value) {
32
+ return !!value && typeof value === "object" && "~standard" in value && value["~standard"]?.validate != null && typeof value["~standard"].validate === "function";
33
+ }
34
+ var warnedNoDesc = false;
35
+ function warnNoDescription(fn) {
36
+ if (warnedNoDesc) return;
37
+ warnedNoDesc = true;
38
+ if (typeof console !== "undefined" && typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
39
+ console.warn(
40
+ `[simple-webmcp] tool "${getFunctionName(fn) || "anonymous"}" has no description. Add \`description:"..."\` or JSDoc. See https://github.com/emingure/simple-webmcp`
41
+ );
42
+ } else if (typeof console !== "undefined") {
43
+ console.warn(`[simple-webmcp] tool "${getFunctionName(fn) || "anonymous"}" has no description.`);
44
+ }
45
+ }
46
+
47
+ // src/errors.ts
48
+ var SimpleWebMCPError = class extends Error {
49
+ constructor(message, code, cause) {
50
+ super(message);
51
+ this.code = code;
52
+ this.cause = cause;
53
+ this.name = this.constructor.name;
54
+ if (Error.captureStackTrace) {
55
+ Error.captureStackTrace(this, this.constructor);
56
+ }
57
+ }
58
+ toJSON() {
59
+ return {
60
+ name: this.name,
61
+ message: this.message,
62
+ code: this.code,
63
+ cause: this.cause
64
+ };
65
+ }
66
+ };
67
+ var NotSupportedError = class extends SimpleWebMCPError {
68
+ constructor(message = "WebMCP is not supported in this environment", opts) {
69
+ super(message, "NOT_SUPPORTED", opts?.cause);
70
+ }
71
+ };
72
+ var NotAllowedError = class extends SimpleWebMCPError {
73
+ constructor(message = "WebMCP tools are blocked by Permissions Policy", opts) {
74
+ super(message, "NOT_ALLOWED", opts?.cause);
75
+ }
76
+ };
77
+ var RegistrationError = class extends SimpleWebMCPError {
78
+ constructor(message, opts) {
79
+ super(message, "REGISTRATION_ERROR", opts?.cause);
80
+ }
81
+ };
82
+ var ValidationError = class extends SimpleWebMCPError {
83
+ constructor(message, opts) {
84
+ super(message, "VALIDATION_ERROR", opts?.cause);
85
+ }
86
+ };
87
+ var ConfigurationError = class extends SimpleWebMCPError {
88
+ constructor(message, opts) {
89
+ super(message, "CONFIGURATION_ERROR", opts?.cause);
90
+ }
91
+ };
92
+
93
+ // src/internal/inferRuntime.ts
94
+ function parseFunctionSource(fn) {
95
+ try {
96
+ return Function.prototype.toString.call(fn);
97
+ } catch {
98
+ return "";
99
+ }
100
+ }
101
+ function parseParamNames(fn) {
102
+ const src = parseFunctionSource(fn);
103
+ const clean = src.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "").replace(/\n/g, " ");
104
+ let paramBlock = "";
105
+ const arrowIdx = clean.indexOf("=>");
106
+ const firstParen = clean.indexOf("(");
107
+ clean.indexOf("{");
108
+ if (firstParen !== -1) {
109
+ let depth = 0;
110
+ let start = firstParen;
111
+ let end = -1;
112
+ for (let i = start; i < clean.length; i++) {
113
+ const ch = clean[i];
114
+ if (ch === "(") depth++;
115
+ else if (ch === ")") {
116
+ depth--;
117
+ if (depth === 0) {
118
+ end = i;
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ if (end !== -1) {
124
+ paramBlock = clean.slice(start + 1, end);
125
+ }
126
+ } else if (arrowIdx !== -1) {
127
+ const beforeArrow = clean.slice(0, arrowIdx).trim();
128
+ const single = beforeArrow.split(/\s+/).pop() || "";
129
+ if (single && !single.includes(" ")) {
130
+ paramBlock = single.replace(/^async\s+/, "");
131
+ }
132
+ }
133
+ if (!paramBlock.trim()) return [];
134
+ const params = [];
135
+ let current = "";
136
+ let depthCurly = 0;
137
+ let depthBracket = 0;
138
+ let depthParen = 0;
139
+ for (let i = 0; i < paramBlock.length; i++) {
140
+ const ch = paramBlock[i];
141
+ if (ch === "{") depthCurly++;
142
+ else if (ch === "}") depthCurly--;
143
+ else if (ch === "[") depthBracket++;
144
+ else if (ch === "]") depthBracket--;
145
+ else if (ch === "(") depthParen++;
146
+ else if (ch === ")") depthParen--;
147
+ if (ch === "," && depthCurly === 0 && depthBracket === 0 && depthParen === 0) {
148
+ const p = current.trim();
149
+ if (p) params.push(parseSingleParam(p));
150
+ current = "";
151
+ } else {
152
+ current += ch;
153
+ }
154
+ }
155
+ const last = current.trim();
156
+ if (last) params.push(parseSingleParam(last));
157
+ return params;
158
+ }
159
+ function parseSingleParam(raw) {
160
+ let s = raw.trim();
161
+ if (s.startsWith("...")) s = s.slice(3).trim();
162
+ let hasDefault = false;
163
+ let defaultValue = void 0;
164
+ let eqIdx = -1;
165
+ let depth = 0;
166
+ for (let i = 0; i < s.length; i++) {
167
+ const ch = s[i];
168
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
169
+ else if (ch === "}" || ch === ")" || ch === "]") depth--;
170
+ else if (ch === "=" && depth === 0) {
171
+ eqIdx = i;
172
+ break;
173
+ }
174
+ }
175
+ let namePart = s;
176
+ if (eqIdx !== -1) {
177
+ hasDefault = true;
178
+ namePart = s.slice(0, eqIdx).trim();
179
+ const defStr = s.slice(eqIdx + 1).trim();
180
+ defaultValue = parseDefaultLiteral(defStr);
181
+ }
182
+ let name = namePart;
183
+ if (name.startsWith("{") && name.endsWith("}")) {
184
+ name = name;
185
+ } else if (name.startsWith("[")) {
186
+ name = name;
187
+ }
188
+ return { name, hasDefault, defaultValue };
189
+ }
190
+ function parseDefaultLiteral(str) {
191
+ const t = str.trim().replace(/,$/, "");
192
+ if (t === "true") return true;
193
+ if (t === "false") return false;
194
+ if (t === "null") return null;
195
+ if (t === "undefined") return void 0;
196
+ if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
197
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") || t.startsWith("`") && t.endsWith("`")) {
198
+ return t.slice(1, -1);
199
+ }
200
+ if (t.startsWith("{") || t.startsWith("[")) return void 0;
201
+ return void 0;
202
+ }
203
+ function inferRuntime(fn) {
204
+ const params = parseParamNames(fn);
205
+ const src = parseFunctionSource(fn);
206
+ const isAsync = /^\s*async\b/.test(src);
207
+ let isObjectParam = false;
208
+ let objectKeys = [];
209
+ if (params.length === 1 && params[0].name.startsWith("{")) {
210
+ isObjectParam = true;
211
+ const inner = params[0].name.slice(1, -1);
212
+ const keys = [];
213
+ let cur = "";
214
+ let d = 0;
215
+ for (let i = 0; i < inner.length; i++) {
216
+ const ch = inner[i];
217
+ if (ch === "{" || ch === "[" || ch === "(") d++;
218
+ else if (ch === "}" || ch === "]" || ch === ")") d--;
219
+ if (ch === "," && d === 0) {
220
+ const k = cur.trim();
221
+ if (k) keys.push(extractKey(k));
222
+ cur = "";
223
+ } else cur += ch;
224
+ }
225
+ const last = cur.trim();
226
+ if (last) keys.push(extractKey(last));
227
+ objectKeys = keys.filter(Boolean);
228
+ }
229
+ const inference = {
230
+ params,
231
+ isAsync,
232
+ isObjectParam,
233
+ objectKeys,
234
+ confidence: "low"
235
+ };
236
+ const schema = {
237
+ type: "object",
238
+ properties: {},
239
+ required: [],
240
+ additionalProperties: false
241
+ };
242
+ if (isObjectParam && objectKeys.length > 0) {
243
+ const props = {};
244
+ const required = [];
245
+ const innerRaw = params[0].name.slice(1, -1);
246
+ const entries = splitObjectEntries(innerRaw);
247
+ for (const entry of entries) {
248
+ const { key, hasDefault, defaultValue } = entry;
249
+ const prop = {};
250
+ if (typeof defaultValue === "number") prop.type = "number";
251
+ else if (typeof defaultValue === "boolean") prop.type = "boolean";
252
+ if (hasDefault && defaultValue !== void 0) prop.default = defaultValue;
253
+ props[key] = prop;
254
+ if (!hasDefault) required.push(key);
255
+ }
256
+ schema.properties = props;
257
+ schema.required = required;
258
+ } else if (params.length > 0) {
259
+ const props = {};
260
+ const required = [];
261
+ for (const p of params) {
262
+ let key = p.name;
263
+ if (key.includes(":")) key = key.split(":")[0].trim();
264
+ if (key.includes("=")) key = key.split("=")[0].trim();
265
+ if (key.startsWith("{") || key.startsWith("[")) continue;
266
+ if (!key) continue;
267
+ const prop = {};
268
+ if (typeof p.defaultValue === "number") prop.type = "number";
269
+ else if (typeof p.defaultValue === "boolean") prop.type = "boolean";
270
+ if (p.hasDefault && p.defaultValue !== void 0) prop.default = p.defaultValue;
271
+ props[key] = prop;
272
+ if (!p.hasDefault) required.push(key);
273
+ }
274
+ schema.properties = props;
275
+ schema.required = required;
276
+ }
277
+ return { inference, schema };
278
+ }
279
+ function extractKey(raw) {
280
+ let s = raw.trim();
281
+ const eq = s.indexOf("=");
282
+ if (eq !== -1) s = s.slice(0, eq).trim();
283
+ const colon = s.indexOf(":");
284
+ if (colon !== -1) s = s.slice(0, colon).trim();
285
+ if (s.startsWith("...")) s = s.slice(3).trim();
286
+ s = s.replace(/^{|}$/g, "").trim();
287
+ return s.split(/\s+/)[0] || "";
288
+ }
289
+ function splitObjectEntries(inner) {
290
+ const res = [];
291
+ let cur = "";
292
+ let d = 0;
293
+ for (let i = 0; i < inner.length; i++) {
294
+ const ch = inner[i];
295
+ if (ch === "{" || ch === "[" || ch === "(") d++;
296
+ else if (ch === "}" || ch === "]" || ch === ")") d--;
297
+ if (ch === "," && d === 0) {
298
+ if (cur.trim()) res.push(parseObjectEntry(cur.trim()));
299
+ cur = "";
300
+ } else cur += ch;
301
+ }
302
+ if (cur.trim()) res.push(parseObjectEntry(cur.trim()));
303
+ return res;
304
+ }
305
+ function parseObjectEntry(raw) {
306
+ let s = raw.trim();
307
+ let hasDefault = false;
308
+ let defaultValue;
309
+ let eqIdx = s.indexOf("=");
310
+ if (eqIdx !== -1) {
311
+ hasDefault = true;
312
+ const def = s.slice(eqIdx + 1).trim();
313
+ defaultValue = parseDefaultLiteral(def);
314
+ s = s.slice(0, eqIdx).trim();
315
+ }
316
+ const colon = s.indexOf(":");
317
+ if (colon !== -1) s = s.slice(0, colon).trim();
318
+ const key = extractKey(s);
319
+ return { key, hasDefault, defaultValue };
320
+ }
321
+
322
+ // src/internal/schema.ts
323
+ var ZOD_CONVERTER_KEY = "__simpleWebmcp_zodConverter";
324
+ function getZodConverter() {
325
+ return globalThis[ZOD_CONVERTER_KEY] ?? null;
326
+ }
327
+ function zodLikeToJsonSchema(schema) {
328
+ const conv = getZodConverter();
329
+ if (conv) {
330
+ try {
331
+ return conv(schema);
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+ return null;
337
+ }
338
+ function standardSchemaToJsonSchema(schema) {
339
+ const zodJson = zodLikeToJsonSchema(schema);
340
+ if (zodJson) return zodJson;
341
+ return null;
342
+ }
343
+ function normalizeSchemaInput(schema) {
344
+ if (!schema) return { json: null, standard: null };
345
+ if (looksLikeJsonSchema(schema)) {
346
+ return { json: schema, standard: null };
347
+ }
348
+ if (isStandardSchema(schema)) {
349
+ const std = schema;
350
+ const json = standardSchemaToJsonSchema(std);
351
+ return { json, standard: std };
352
+ }
353
+ const maybeZodJson = zodLikeToJsonSchema(schema);
354
+ if (maybeZodJson) return { json: maybeZodJson, standard: isStandardSchema(schema) ? schema : null };
355
+ if (typeof schema === "object" && schema !== null) {
356
+ return { json: schema, standard: null };
357
+ }
358
+ return { json: null, standard: null };
359
+ }
360
+ function applyFieldsPatch(base, fields) {
361
+ if (!fields || Object.keys(fields).length === 0) return base;
362
+ const result = {
363
+ ...base,
364
+ properties: { ...base.properties || {} },
365
+ required: [...base.required || []]
366
+ };
367
+ if (!result.properties) result.properties = {};
368
+ if (!result.required) result.required = [];
369
+ for (const [key, field] of Object.entries(fields)) {
370
+ const isStd = isStandardSchema(field);
371
+ if (isStd) {
372
+ const json = standardSchemaToJsonSchema(field);
373
+ const fragment = json ?? { type: "string" };
374
+ result.properties[key] = fragment;
375
+ const anyField = field;
376
+ const isOpt = anyField?._def?.typeName === "ZodOptional" || anyField?.isOptional?.() === true;
377
+ if (!isOpt) {
378
+ if (!result.required.includes(key)) result.required.push(key);
379
+ } else {
380
+ result.required = result.required.filter((k) => k !== key);
381
+ }
382
+ const desc = anyField?.description ?? anyField?._def?.description;
383
+ if (desc && !result.properties[key].description) result.properties[key].description = desc;
384
+ } else {
385
+ const patch = field;
386
+ const existing = result.properties[key] || {};
387
+ const merged = { ...existing, ...patch };
388
+ result.properties[key] = merged;
389
+ if ("required" in patch) {
390
+ const req = patch.required;
391
+ if (req === false) {
392
+ result.required = result.required.filter((k) => k !== key);
393
+ delete merged.required;
394
+ } else if (req === true) {
395
+ if (!result.required.includes(key)) result.required.push(key);
396
+ delete merged.required;
397
+ }
398
+ } else {
399
+ if (!(key in (base.properties || {})) && !patch.required) {
400
+ const hasDefault = "default" in patch;
401
+ if (!hasDefault && !result.required.includes(key)) result.required.push(key);
402
+ }
403
+ }
404
+ }
405
+ }
406
+ if (result.required && result.required.length === 0) delete result.required;
407
+ return result;
408
+ }
409
+ function buildFinalInputSchema(opts) {
410
+ const wholeNorm = normalizeSchemaInput(opts.wholeSchema);
411
+ let baseJson;
412
+ let standard = wholeNorm.standard;
413
+ if (wholeNorm.json) {
414
+ baseJson = wholeNorm.json;
415
+ } else if (wholeNorm.standard && !wholeNorm.json) {
416
+ baseJson = opts.inferred;
417
+ } else {
418
+ baseJson = opts.inferred;
419
+ }
420
+ if (!baseJson.type) baseJson.type = "object";
421
+ if (!baseJson.properties) baseJson.properties = {};
422
+ if (baseJson.additionalProperties == null) baseJson.additionalProperties = false;
423
+ const patched = applyFieldsPatch(baseJson, opts.fields);
424
+ return { json: patched, standard };
425
+ }
426
+ function normalizeOutputSchema(schema) {
427
+ if (!schema) return {};
428
+ const norm = normalizeSchemaInput(schema);
429
+ if (norm.json) return { json: norm.json, standard: norm.standard ?? void 0 };
430
+ if (norm.standard) return { standard: norm.standard };
431
+ return {};
432
+ }
433
+
434
+ // src/internal/registry.ts
435
+ var Registry = class {
436
+ constructor() {
437
+ this.entries = /* @__PURE__ */ new Map();
438
+ }
439
+ // expose for devtools/tests — now includes contract for inspect
440
+ list() {
441
+ return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status, contract: e.contract }));
442
+ }
443
+ get(name) {
444
+ return this.entries.get(name);
445
+ }
446
+ getContract(name) {
447
+ return this.entries.get(name)?.contract;
448
+ }
449
+ clear() {
450
+ for (const e of this.entries.values()) {
451
+ try {
452
+ e.controller.abort();
453
+ } catch {
454
+ }
455
+ }
456
+ this.entries.clear();
457
+ }
458
+ isRegistered(name) {
459
+ return this.entries.get(name)?.status === "registered";
460
+ }
461
+ getStatus(name) {
462
+ return this.entries.get(name)?.status ?? "unregistered";
463
+ }
464
+ async register(contract, opts) {
465
+ const name = contract.name;
466
+ const existing = this.entries.get(name);
467
+ if (existing && (existing.status === "registered" || existing.status === "registering")) {
468
+ return existing.unregister;
469
+ }
470
+ if (!isWebMCPSupported()) {
471
+ if (opts?.signal?.aborted) return () => {
472
+ };
473
+ return () => {
474
+ };
475
+ }
476
+ const modelContext = getModelContext();
477
+ if (!modelContext) throw new NotSupportedError();
478
+ const controller = new AbortController();
479
+ if (opts?.signal) {
480
+ if (opts.signal.aborted) {
481
+ controller.abort();
482
+ } else {
483
+ opts.signal.addEventListener(
484
+ "abort",
485
+ () => {
486
+ try {
487
+ controller.abort();
488
+ } catch {
489
+ }
490
+ },
491
+ { once: true }
492
+ );
493
+ }
494
+ }
495
+ const entry = {
496
+ name,
497
+ controller,
498
+ status: "registering",
499
+ promise: null,
500
+ unregister: () => {
501
+ if (entry.status === "registered" || entry.status === "registering") {
502
+ entry.status = "unregistering";
503
+ try {
504
+ controller.abort();
505
+ } catch {
506
+ }
507
+ entry.status = "unregistered";
508
+ this.entries.delete(name);
509
+ }
510
+ },
511
+ contract,
512
+ execute: opts?.execute
513
+ };
514
+ this.entries.set(name, entry);
515
+ const wrappedExecute = opts?.execute ?? (async (args) => ({ content: [{ type: "text", text: `no execute for ${name}` }] }));
516
+ try {
517
+ const regPromise = modelContext.registerTool(
518
+ {
519
+ name: contract.name,
520
+ description: contract.description,
521
+ inputSchema: contract.inputSchema,
522
+ outputSchema: contract.outputSchema,
523
+ annotations: contract.annotations,
524
+ execute: wrappedExecute
525
+ },
526
+ { signal: controller.signal }
527
+ );
528
+ entry.promise = regPromise;
529
+ await regPromise;
530
+ if (controller.signal.aborted) {
531
+ entry.status = "unregistered";
532
+ this.entries.delete(name);
533
+ } else {
534
+ entry.status = "registered";
535
+ }
536
+ controller.signal.addEventListener("abort", () => {
537
+ entry.status = "unregistered";
538
+ this.entries.delete(name);
539
+ }, { once: true });
540
+ return entry.unregister;
541
+ } catch (err) {
542
+ entry.status = "error";
543
+ this.entries.delete(name);
544
+ const msg = err?.message || String(err);
545
+ if (err?.name === "NotAllowedError" || /NotAllowedError|Permissions Policy|blocked/i.test(msg)) {
546
+ throw new NotAllowedError(msg, { cause: err });
547
+ }
548
+ throw new RegistrationError(`Failed to register tool "${name}": ${msg}`, { cause: err });
549
+ }
550
+ }
551
+ unregister(name) {
552
+ const entry = this.entries.get(name);
553
+ if (!entry) return;
554
+ try {
555
+ entry.controller.abort();
556
+ } catch {
557
+ }
558
+ this.entries.delete(name);
559
+ }
560
+ };
561
+ var REGISTRY_KEY = "__simpleWebmcpRegistry";
562
+ var globalAny = globalThis;
563
+ var registry = globalAny[REGISTRY_KEY] ?? new Registry();
564
+ if (!globalAny[REGISTRY_KEY]) {
565
+ globalAny[REGISTRY_KEY] = registry;
566
+ }
567
+
568
+ // src/internal/normalize.ts
569
+ function normalizeResult(value) {
570
+ if (value == null) {
571
+ return { content: [{ type: "text", text: "" }] };
572
+ }
573
+ if (typeof value === "string") {
574
+ return { content: [{ type: "text", text: value }] };
575
+ }
576
+ if (typeof value === "number" || typeof value === "boolean") {
577
+ return { content: [{ type: "text", text: String(value) }] };
578
+ }
579
+ if (typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content)) {
580
+ return value;
581
+ }
582
+ if (typeof value === "object" && value !== null && "isError" in value) {
583
+ if ("content" in value) return value;
584
+ }
585
+ try {
586
+ const text = JSON.stringify(value, null, 2);
587
+ return { content: [{ type: "text", text }] };
588
+ } catch {
589
+ return { content: [{ type: "text", text: String(value) }] };
590
+ }
591
+ }
592
+ function normalizeError(err) {
593
+ const message = (() => {
594
+ if (err instanceof Error) return err.message;
595
+ if (typeof err === "string") return err;
596
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
597
+ return err.message;
598
+ }
599
+ try {
600
+ return JSON.stringify(err);
601
+ } catch {
602
+ return String(err);
603
+ }
604
+ })();
605
+ return {
606
+ content: [{ type: "text", text: `Error: ${message}` }],
607
+ isError: true
608
+ };
609
+ }
610
+
611
+ // src/hooks/engine.ts
612
+ var fallbackCounter = 0;
613
+ function genInvocationId() {
614
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
615
+ return crypto.randomUUID();
616
+ }
617
+ return fallbackInvocationId();
618
+ }
619
+ function fallbackInvocationId() {
620
+ fallbackCounter = (fallbackCounter + 1) % Number.MAX_SAFE_INTEGER;
621
+ return `webmcp_${Date.now().toString(36)}_${fallbackCounter}_${Math.random().toString(36).slice(2, 8)}`;
622
+ }
623
+ function isDenyResult(r) {
624
+ return !!r && typeof r === "object" && r.action === "deny";
625
+ }
626
+ async function runErrorHooks(hooks, ctx) {
627
+ if (!hooks || hooks.length === 0) return;
628
+ for (const h of hooks) {
629
+ try {
630
+ await h(ctx);
631
+ } catch {
632
+ }
633
+ }
634
+ }
635
+ async function runDeniedHooks(hooks, ctx) {
636
+ if (!hooks || hooks.length === 0) return;
637
+ for (const h of hooks) {
638
+ try {
639
+ await h(ctx);
640
+ } catch {
641
+ }
642
+ }
643
+ }
644
+ function createHookedExecute(fn, tool, contract, options) {
645
+ return async (args, _ctx) => {
646
+ const invocationId = genInvocationId();
647
+ const metadata = {};
648
+ const signal = tool.__activeSignal ?? createNeverAbortedSignal();
649
+ const merged = options.getHooks();
650
+ let currentInput = args;
651
+ const base = { invocationId, tool, contract, signal, metadata };
652
+ const beforeHooks = merged.before ?? [];
653
+ for (const hook of beforeHooks) {
654
+ if (signal.aborted) {
655
+ const abortErr = new DOMException("Aborted", "AbortError");
656
+ await runErrorHooks(merged.error, {
657
+ ...base,
658
+ input: currentInput,
659
+ error: abortErr
660
+ });
661
+ return normalizeError(abortErr);
662
+ }
663
+ let res;
664
+ try {
665
+ const ctx = { ...base, input: currentInput };
666
+ res = await hook(ctx);
667
+ } catch (hookErr) {
668
+ await runErrorHooks(merged.error, {
669
+ ...base,
670
+ input: currentInput,
671
+ error: hookErr
672
+ });
673
+ return normalizeError(hookErr);
674
+ }
675
+ if (res != null && typeof res === "object") {
676
+ if (isDenyResult(res)) {
677
+ const reason = res.message;
678
+ const code = res.code;
679
+ await runDeniedHooks(merged.denied, {
680
+ ...base,
681
+ input: currentInput,
682
+ reason,
683
+ code
684
+ });
685
+ const msg = reason ?? "Tool execution denied";
686
+ return {
687
+ content: [{ type: "text", text: `Denied: ${msg}` }],
688
+ isError: true,
689
+ ...code ? { code } : {}
690
+ };
691
+ }
692
+ if ("input" in res && res.input !== void 0) {
693
+ currentInput = res.input;
694
+ }
695
+ }
696
+ }
697
+ if (signal.aborted) {
698
+ const abortErr = new DOMException("Aborted", "AbortError");
699
+ await runErrorHooks(merged.error, {
700
+ ...base,
701
+ input: currentInput,
702
+ error: abortErr
703
+ });
704
+ return normalizeError(abortErr);
705
+ }
706
+ if (options.validate) {
707
+ try {
708
+ options.validate(currentInput);
709
+ } catch (valErr) {
710
+ await runErrorHooks(merged.error, {
711
+ ...base,
712
+ input: currentInput,
713
+ error: valErr
714
+ });
715
+ return normalizeError(valErr);
716
+ }
717
+ }
718
+ let rawOutput;
719
+ try {
720
+ rawOutput = await fn(currentInput);
721
+ } catch (fnErr) {
722
+ await runErrorHooks(merged.error, {
723
+ ...base,
724
+ input: currentInput,
725
+ error: fnErr
726
+ });
727
+ return normalizeError(fnErr);
728
+ }
729
+ let currentOutput = rawOutput;
730
+ const afterHooks = merged.after ?? [];
731
+ for (const hook of afterHooks) {
732
+ if (signal.aborted) ;
733
+ try {
734
+ const ctx = {
735
+ ...base,
736
+ input: currentInput,
737
+ output: currentOutput
738
+ };
739
+ const res = await hook(ctx);
740
+ if (res != null && typeof res === "object" && "output" in res && res.output !== void 0) {
741
+ currentOutput = res.output;
742
+ }
743
+ } catch (hookErr) {
744
+ await runErrorHooks(merged.error, {
745
+ ...base,
746
+ input: currentInput,
747
+ error: hookErr
748
+ });
749
+ return normalizeError(hookErr);
750
+ }
751
+ }
752
+ return normalizeResult(currentOutput);
753
+ };
754
+ }
755
+ function createNeverAbortedSignal() {
756
+ try {
757
+ return new AbortController().signal;
758
+ } catch {
759
+ return {
760
+ aborted: false,
761
+ addEventListener() {
762
+ },
763
+ removeEventListener() {
764
+ },
765
+ dispatchEvent() {
766
+ return false;
767
+ },
768
+ onabort: null,
769
+ reason: void 0,
770
+ throwIfAborted() {
771
+ }
772
+ };
773
+ }
774
+ }
775
+
776
+ // src/hooks/config.ts
777
+ var GLOBAL_KEY = "__simpleWebmcp_hooks";
778
+ function getStore() {
779
+ const g = globalThis;
780
+ if (!g[GLOBAL_KEY]) {
781
+ g[GLOBAL_KEY] = { hooks: {} };
782
+ }
783
+ return g[GLOBAL_KEY];
784
+ }
785
+ function getGlobalHooks() {
786
+ return getStore().hooks ?? {};
787
+ }
788
+ function configureWebMCP(opts) {
789
+ const store = getStore();
790
+ if (!opts.hooks) return;
791
+ if (opts.replace) {
792
+ store.hooks = normalizeHooks(opts.hooks);
793
+ return;
794
+ }
795
+ store.hooks = mergeHooks(store.hooks, opts.hooks);
796
+ }
797
+ function resetGlobalHooks() {
798
+ const store = getStore();
799
+ store.hooks = {};
800
+ }
801
+ function normalizeHooks(hooks) {
802
+ return {
803
+ before: hooks.before ? [...hooks.before] : void 0,
804
+ after: hooks.after ? [...hooks.after] : void 0,
805
+ error: hooks.error ? [...hooks.error] : void 0,
806
+ denied: hooks.denied ? [...hooks.denied] : void 0
807
+ };
808
+ }
809
+ function mergeHooks(a, b) {
810
+ if (!a && !b) return {};
811
+ if (!a) return normalizeHooks(b);
812
+ if (!b) return a;
813
+ return {
814
+ before: [...a.before ?? [], ...b.before ?? []],
815
+ after: [...a.after ?? [], ...b.after ?? []],
816
+ error: [...a.error ?? [], ...b.error ?? []],
817
+ denied: [...a.denied ?? [], ...b.denied ?? []]
818
+ };
819
+ }
820
+ function mergeHooksOrdered(opts) {
821
+ const globalHooks = opts.globalHooks ?? {};
822
+ const scopedHooks = opts.scopedHooks ?? {};
823
+ const toolHooks = opts.toolHooks ?? {};
824
+ const before = [
825
+ ...globalHooks.before ?? [],
826
+ ...scopedHooks.before ?? [],
827
+ ...toolHooks.before ?? []
828
+ ];
829
+ const after = [
830
+ ...toolHooks.after ?? [],
831
+ ...scopedHooks.after ?? [],
832
+ ...globalHooks.after ?? []
833
+ ];
834
+ const error = [
835
+ ...toolHooks.error ?? [],
836
+ ...scopedHooks.error ?? [],
837
+ ...globalHooks.error ?? []
838
+ ];
839
+ const denied = [
840
+ ...toolHooks.denied ?? [],
841
+ ...scopedHooks.denied ?? [],
842
+ ...globalHooks.denied ?? []
843
+ ];
844
+ const out = {};
845
+ if (before.length) out.before = before;
846
+ if (after.length) out.after = after;
847
+ if (error.length) out.error = error;
848
+ if (denied.length) out.denied = denied;
849
+ return out;
850
+ }
851
+
852
+ // src/webmcp.ts
853
+ function resolveScope(opts) {
854
+ if (opts?.scope) return opts.scope;
855
+ if (opts?.global) return "global";
856
+ return "scoped";
857
+ }
858
+ function webmcp(fn, options) {
859
+ if (typeof fn !== "function") {
860
+ throw new ConfigurationError("webmcp(fn, opts) \u2014 first argument must be a function");
861
+ }
862
+ const anyFn = fn;
863
+ if (anyFn.__webmcpBrand === true && anyFn.definition) {
864
+ if (!options || Object.keys(options).length === 0) return anyFn;
865
+ const prevOpts = anyFn.__webmcpOptions || {};
866
+ const mergedOpts = { ...prevOpts, ...options };
867
+ if (prevOpts.hooks || options?.hooks) {
868
+ const prevHooks = prevOpts.hooks ?? {};
869
+ const nextHooks = options.hooks ?? {};
870
+ mergedOpts.hooks = {
871
+ before: [...prevHooks.before ?? [], ...nextHooks.before ?? []],
872
+ after: [...prevHooks.after ?? [], ...nextHooks.after ?? []],
873
+ error: [...prevHooks.error ?? [], ...nextHooks.error ?? []],
874
+ denied: [...prevHooks.denied ?? [], ...nextHooks.denied ?? []]
875
+ };
876
+ for (const k of ["before", "after", "error", "denied"]) {
877
+ if (mergedOpts.hooks[k]?.length === 0) delete mergedOpts.hooks[k];
878
+ }
879
+ if (mergedOpts.hooks && Object.keys(mergedOpts.hooks).length === 0) delete mergedOpts.hooks;
880
+ }
881
+ const original = anyFn.__fn ?? fn;
882
+ return webmcp(original, mergedOpts);
883
+ }
884
+ const name = options?.name ?? toSnakeCase(getFunctionName(fn));
885
+ if (!name) throw new ConfigurationError('Tool name could not be inferred \u2014 pass {name:"my_tool"}');
886
+ let description = options?.description ?? anyFn.__webmcpDescription ?? "";
887
+ if (!description) {
888
+ warnNoDescription(fn);
889
+ description = "";
890
+ }
891
+ const hasSchema = !!options?.schema;
892
+ const hasFields = !!options?.fields && Object.keys(options.fields).length > 0;
893
+ if (options?.strict && !hasSchema && !hasFields) {
894
+ const { schema: inferred } = inferRuntime(fn);
895
+ const props = inferred.properties || {};
896
+ const propKeys = Object.keys(props);
897
+ const hasTypedProps = propKeys.length > 0 && propKeys.some((k) => !!props[k]?.type);
898
+ if (!hasTypedProps) {
899
+ throw new ConfigurationError(
900
+ `webmcp strict: could not infer schema for "${name}" \u2014 add TypeScript types, JSDoc, {schema} or {fields}`
901
+ );
902
+ }
903
+ }
904
+ const { schema: inferredSchema } = inferRuntime(fn);
905
+ const { json: finalInput, standard } = buildFinalInputSchema({
906
+ wholeSchema: options?.schema,
907
+ inferred: inferredSchema,
908
+ fields: options?.fields
909
+ });
910
+ const outNorm = normalizeOutputSchema(options?.outputSchema);
911
+ const annotations = options?.annotations;
912
+ const contract = {
913
+ name,
914
+ description,
915
+ inputSchema: finalInput,
916
+ outputSchema: outNorm.json,
917
+ annotations
918
+ };
919
+ const enabled = options?.enabled ?? true;
920
+ const wrapper = function(...args) {
921
+ return fn.apply(this, args);
922
+ };
923
+ try {
924
+ Object.defineProperty(wrapper, "name", { value: fn.name || name, configurable: true });
925
+ } catch {
926
+ }
927
+ try {
928
+ Object.defineProperty(wrapper, "length", { value: fn.length, configurable: true });
929
+ } catch {
930
+ }
931
+ let status = "unregistered";
932
+ let registrationPromise = null;
933
+ let unregisterFn = null;
934
+ let activeController = null;
935
+ const toolHooks = options?.hooks ? { ...options.hooks } : void 0;
936
+ const toolWrapper = wrapper;
937
+ toolWrapper.__webmcpBrand = true;
938
+ toolWrapper.__fn = fn;
939
+ toolWrapper.__webmcpOptions = options;
940
+ if (standard) toolWrapper.__standardSchema = standard;
941
+ if (outNorm.standard) toolWrapper.__outputStandardSchema = outNorm.standard;
942
+ if (toolHooks) toolWrapper.__hooks = toolHooks;
943
+ toolWrapper.tool = contract;
944
+ toolWrapper.definition = contract;
945
+ const hookedExec = createHookedExecute(fn, toolWrapper, contract, {
946
+ getHooks: () => {
947
+ const globalHooks = getGlobalHooks();
948
+ const scopeHooks = toolWrapper.__scopeHooks;
949
+ return mergeHooksOrdered({ globalHooks, scopedHooks: scopeHooks, toolHooks });
950
+ },
951
+ validate: standard ? (input) => {
952
+ const res = standard["~standard"].validate(input);
953
+ if ("issues" in res && res.issues && res.issues.length > 0) {
954
+ const msg = res.issues.map((i) => i.message).join("; ");
955
+ throw new ValidationError(`Validation failed: ${msg}`);
956
+ }
957
+ } : void 0
958
+ });
959
+ Object.defineProperty(toolWrapper, "status", {
960
+ get() {
961
+ return status;
962
+ },
963
+ enumerable: true,
964
+ configurable: true
965
+ });
966
+ Object.defineProperty(toolWrapper, "registration", {
967
+ get() {
968
+ return registrationPromise;
969
+ },
970
+ enumerable: true,
971
+ configurable: true
972
+ });
973
+ toolWrapper.isRegistered = () => status === "registered";
974
+ toolWrapper.register = async (opts) => {
975
+ if (status === "registered" || status === "registering") {
976
+ return unregisterFn ?? (() => toolWrapper.unregister());
977
+ }
978
+ if (enabled === false) {
979
+ return () => {
980
+ };
981
+ }
982
+ if (!isWebMCPSupported()) {
983
+ status = "unsupported";
984
+ return () => {
985
+ status = "unregistered";
986
+ };
987
+ }
988
+ status = "registering";
989
+ const controller = new AbortController();
990
+ activeController = controller;
991
+ toolWrapper.__activeSignal = controller.signal;
992
+ if (opts?.signal) {
993
+ if (opts.signal.aborted) {
994
+ controller.abort();
995
+ status = "unregistered";
996
+ activeController = null;
997
+ try {
998
+ delete toolWrapper.__activeSignal;
999
+ } catch {
1000
+ }
1001
+ return () => {
1002
+ };
1003
+ }
1004
+ opts.signal.addEventListener("abort", () => {
1005
+ try {
1006
+ controller.abort();
1007
+ } catch {
1008
+ }
1009
+ toolWrapper.unregister();
1010
+ }, { once: true });
1011
+ }
1012
+ controller.signal.addEventListener("abort", () => {
1013
+ if (status !== "unregistered") status = "unregistered";
1014
+ activeController = null;
1015
+ try {
1016
+ delete toolWrapper.__activeSignal;
1017
+ } catch {
1018
+ }
1019
+ }, { once: true });
1020
+ try {
1021
+ const unregister = await registry.register(contract, {
1022
+ signal: controller.signal,
1023
+ execute: hookedExec
1024
+ });
1025
+ unregisterFn = unregister;
1026
+ registrationPromise = Promise.resolve();
1027
+ if (controller.signal.aborted) {
1028
+ status = "unregistered";
1029
+ } else if (!isWebMCPSupported()) {
1030
+ status = "unsupported";
1031
+ } else {
1032
+ status = "registered";
1033
+ }
1034
+ return () => {
1035
+ try {
1036
+ unregister();
1037
+ } catch {
1038
+ }
1039
+ status = "unregistered";
1040
+ activeController = null;
1041
+ try {
1042
+ delete toolWrapper.__activeSignal;
1043
+ } catch {
1044
+ }
1045
+ unregisterFn = null;
1046
+ };
1047
+ } catch (err) {
1048
+ if (err instanceof NotSupportedError || err?.code === "NOT_SUPPORTED" || err?.name === "NotSupportedError") {
1049
+ status = "unsupported";
1050
+ } else {
1051
+ status = "error";
1052
+ }
1053
+ activeController = null;
1054
+ try {
1055
+ delete toolWrapper.__activeSignal;
1056
+ } catch {
1057
+ }
1058
+ registrationPromise = Promise.reject(err);
1059
+ if (err instanceof SimpleWebMCPError) throw err;
1060
+ throw err;
1061
+ }
1062
+ };
1063
+ toolWrapper.unregister = () => {
1064
+ if (status === "unregistered") return;
1065
+ status = "unregistering";
1066
+ try {
1067
+ registry.unregister(contract.name);
1068
+ } catch {
1069
+ }
1070
+ try {
1071
+ activeController?.abort();
1072
+ } catch {
1073
+ }
1074
+ status = "unregistered";
1075
+ activeController = null;
1076
+ try {
1077
+ delete toolWrapper.__activeSignal;
1078
+ } catch {
1079
+ }
1080
+ unregisterFn = null;
1081
+ registrationPromise = null;
1082
+ };
1083
+ const scope = resolveScope(options);
1084
+ if (scope === "global" && enabled !== false) {
1085
+ if (typeof document !== "undefined") {
1086
+ queueMicrotask(() => {
1087
+ toolWrapper.register().catch(() => {
1088
+ });
1089
+ });
1090
+ }
1091
+ }
1092
+ toolWrapper.toString = fn.toString.bind(fn);
1093
+ return toolWrapper;
1094
+ }
1095
+ webmcp.global = function global(fn, opts) {
1096
+ return webmcp(fn, { ...opts, global: true });
1097
+ };
1098
+ webmcp.configure = configureWebMCP;
1099
+ webmcp.getGlobalHooks = getGlobalHooks;
1100
+ webmcp.resetGlobalHooks = resetGlobalHooks;
1101
+ webmcp.isWebMCPTool = function isWebMCPTool(v) {
1102
+ return !!v?.__webmcpBrand;
1103
+ };
1104
+ var WebMCPHooksContext = createContext({});
1105
+ function WebMCPProvider({ hooks, children }) {
1106
+ const parent = useContext(WebMCPHooksContext);
1107
+ const merged = useMemo(() => {
1108
+ if (!hooks) return parent;
1109
+ if (!parent || Object.keys(parent).length === 0) return hooks;
1110
+ return {
1111
+ before: [...parent.before ?? [], ...hooks.before ?? []],
1112
+ after: [...parent.after ?? [], ...hooks.after ?? []],
1113
+ error: [...parent.error ?? [], ...hooks.error ?? []],
1114
+ denied: [...parent.denied ?? [], ...hooks.denied ?? []]
1115
+ };
1116
+ }, [parent, hooks]);
1117
+ return /* @__PURE__ */ jsx(WebMCPHooksContext.Provider, { value: merged, children });
1118
+ }
1119
+ function useWebMCPHooksContext() {
1120
+ return useContext(WebMCPHooksContext);
1121
+ }
10
1122
 
11
1123
  // src/react/useWebMCP.ts
12
1124
  function isTool(v) {
13
1125
  return !!v?.__webmcpBrand;
14
1126
  }
15
- function useWebMCP(tool, opts) {
1127
+ function useWebMCP(arg, opts) {
16
1128
  const enabled = opts?.enabled ?? true;
1129
+ const scopedHooks = useWebMCPHooksContext();
1130
+ const isRawFunction = useMemo(() => {
1131
+ return typeof arg === "function" && !isTool(arg);
1132
+ }, [arg]);
1133
+ const tool = useMemo(() => {
1134
+ if (isRawFunction) {
1135
+ const { enabled: _e, ...webmcpOpts } = opts ?? {};
1136
+ return webmcp(arg, webmcpOpts);
1137
+ }
1138
+ return arg;
1139
+ }, [
1140
+ arg,
1141
+ isRawFunction,
1142
+ opts?.name,
1143
+ opts?.description,
1144
+ opts?.schema,
1145
+ opts?.fields,
1146
+ opts?.annotations,
1147
+ opts?.scope,
1148
+ opts?.global,
1149
+ opts?.strict,
1150
+ opts?.outputSchema,
1151
+ opts?.hooks,
1152
+ opts?.enabled
1153
+ ]);
17
1154
  const [registered, setRegistered] = useState(false);
18
1155
  const [error, setError] = useState(null);
19
1156
  const supported = useMemo(() => isWebMCPSupported(), []);
@@ -25,21 +1162,37 @@ function useWebMCP(tool, opts) {
25
1162
  const toolRef = useRef(tool);
26
1163
  toolRef.current = tool;
27
1164
  useEffect(() => {
28
- if (!enabled) {
29
- setRegistered(false);
30
- setError(null);
31
- return;
32
- }
33
1165
  const t = toolRef.current;
34
- if (!isTool(t)) {
35
- if (typeof console !== "undefined") {
36
- console.warn("[simple-webmcp] useWebMCP expected webmcp(fn) tool, got plain function. Wrap with webmcp() first.");
1166
+ const hasScoped = scopedHooks && Object.keys(scopedHooks).length > 0;
1167
+ if (hasScoped) t.__scopeHooks = scopedHooks;
1168
+ else if (t.__scopeHooks) delete t.__scopeHooks;
1169
+ return () => {
1170
+ const cur = t.__scopeHooks;
1171
+ if (cur === scopedHooks) {
1172
+ try {
1173
+ delete t.__scopeHooks;
1174
+ } catch {
1175
+ }
37
1176
  }
38
- setError(new Error("useWebMCP: wrap fn with webmcp() first"));
1177
+ };
1178
+ }, [scopedHooks, tool]);
1179
+ if (scopedHooks && Object.keys(scopedHooks).length > 0) {
1180
+ tool.__scopeHooks = scopedHooks;
1181
+ } else if (tool.__scopeHooks) {
1182
+ if (!(scopedHooks && Object.keys(scopedHooks).length > 0)) {
1183
+ try {
1184
+ delete tool.__scopeHooks;
1185
+ } catch {
1186
+ }
1187
+ }
1188
+ }
1189
+ useEffect(() => {
1190
+ if (!enabled) {
39
1191
  setRegistered(false);
1192
+ setError(null);
40
1193
  return;
41
1194
  }
42
- const webTool = t;
1195
+ const webTool = toolRef.current;
43
1196
  if (webTool.status === "registered") {
44
1197
  setRegistered(true);
45
1198
  setError(null);
@@ -86,8 +1239,30 @@ function useWebMCP(tool, opts) {
86
1239
  };
87
1240
  }, [enabled, supported, tool]);
88
1241
  const status = !supported ? "unsupported" : error ? "error" : registered ? "registered" : enabled ? "registering" : "unregistered";
89
- return { supported: !!supported, registered, error, isPolyfilled, status };
1242
+ const result = { supported: !!supported, registered, error, isPolyfilled, status };
1243
+ if (isRawFunction) {
1244
+ const augmented = (...args) => tool(...args);
1245
+ Object.assign(augmented, tool);
1246
+ Object.setPrototypeOf(augmented, Object.getPrototypeOf(tool));
1247
+ for (const [k, v] of Object.entries(result)) {
1248
+ Object.defineProperty(augmented, k, {
1249
+ value: v,
1250
+ writable: true,
1251
+ configurable: true,
1252
+ enumerable: true
1253
+ });
1254
+ }
1255
+ Object.defineProperty(augmented, "__rawTool", {
1256
+ value: tool,
1257
+ writable: false,
1258
+ configurable: true,
1259
+ enumerable: false
1260
+ });
1261
+ return augmented;
1262
+ }
1263
+ return result;
90
1264
  }
1265
+ var useTool = useWebMCP;
91
1266
  function ToolRegistrar({ tool, enabled }) {
92
1267
  useWebMCP(tool, { enabled });
93
1268
  return null;
@@ -101,6 +1276,6 @@ function Scope({ tools, enabled = true, children }) {
101
1276
  }
102
1277
  var WebMCPScope = Scope;
103
1278
 
104
- export { Scope, WebMCPScope, useWebMCP };
1279
+ export { Scope, WebMCPHooksContext, WebMCPProvider, WebMCPScope, useTool, useWebMCP, useWebMCPHooksContext };
105
1280
  //# sourceMappingURL=react.js.map
106
1281
  //# sourceMappingURL=react.js.map