simple-webmcp 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,849 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ // src/errors.ts
6
+ var SimpleWebMCPError = class extends Error {
7
+ constructor(message, code, cause) {
8
+ super(message);
9
+ this.code = code;
10
+ this.cause = cause;
11
+ this.name = this.constructor.name;
12
+ if (Error.captureStackTrace) {
13
+ Error.captureStackTrace(this, this.constructor);
14
+ }
15
+ }
16
+ toJSON() {
17
+ return {
18
+ name: this.name,
19
+ message: this.message,
20
+ code: this.code,
21
+ cause: this.cause
22
+ };
23
+ }
24
+ };
25
+ var NotSupportedError = class extends SimpleWebMCPError {
26
+ constructor(message = "WebMCP is not supported in this environment", opts) {
27
+ super(message, "NOT_SUPPORTED", opts?.cause);
28
+ }
29
+ };
30
+ var NotAllowedError = class extends SimpleWebMCPError {
31
+ constructor(message = "WebMCP tools are blocked by Permissions Policy", opts) {
32
+ super(message, "NOT_ALLOWED", opts?.cause);
33
+ }
34
+ };
35
+ var RegistrationError = class extends SimpleWebMCPError {
36
+ constructor(message, opts) {
37
+ super(message, "REGISTRATION_ERROR", opts?.cause);
38
+ }
39
+ };
40
+ var ValidationError = class extends SimpleWebMCPError {
41
+ constructor(message, opts) {
42
+ super(message, "VALIDATION_ERROR", opts?.cause);
43
+ }
44
+ };
45
+ var ConfigurationError = class extends SimpleWebMCPError {
46
+ constructor(message, opts) {
47
+ super(message, "CONFIGURATION_ERROR", opts?.cause);
48
+ }
49
+ };
50
+
51
+ // src/internal/utils.ts
52
+ function toSnakeCase(name) {
53
+ if (!name) return "anonymous_tool";
54
+ 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";
55
+ }
56
+ function getFunctionName(fn) {
57
+ return fn.displayName || fn.name || "";
58
+ }
59
+ function isWebMCPSupported() {
60
+ if (typeof document === "undefined") return false;
61
+ const docAny = document;
62
+ return !!docAny.modelContext && typeof docAny.modelContext.registerTool === "function";
63
+ }
64
+ function getModelContext() {
65
+ if (typeof document === "undefined") return null;
66
+ const docAny = document;
67
+ if (docAny.modelContext && typeof docAny.modelContext.registerTool === "function") {
68
+ return docAny.modelContext;
69
+ }
70
+ return null;
71
+ }
72
+ function looksLikeJsonSchema(value) {
73
+ if (!value || typeof value !== "object") return false;
74
+ const obj = value;
75
+ if ("~standard" in obj) return false;
76
+ return "type" in obj || "properties" in obj || "$ref" in obj || "anyOf" in obj || "oneOf" in obj || "allOf" in obj || "enum" in obj;
77
+ }
78
+ function isStandardSchema(value) {
79
+ return !!value && typeof value === "object" && "~standard" in value && value["~standard"]?.validate != null && typeof value["~standard"].validate === "function";
80
+ }
81
+ var warnedNoDesc = false;
82
+ function warnNoDescription(fn) {
83
+ if (warnedNoDesc) return;
84
+ warnedNoDesc = true;
85
+ if (typeof console !== "undefined" && typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
86
+ console.warn(
87
+ `[simple-webmcp] tool "${getFunctionName(fn) || "anonymous"}" has no description. Add \`description:"..."\` or JSDoc. See https://github.com/emingure/simple-webmcp`
88
+ );
89
+ } else if (typeof console !== "undefined") {
90
+ console.warn(`[simple-webmcp] tool "${getFunctionName(fn) || "anonymous"}" has no description.`);
91
+ }
92
+ }
93
+
94
+ // src/internal/inferRuntime.ts
95
+ function parseFunctionSource(fn) {
96
+ try {
97
+ return Function.prototype.toString.call(fn);
98
+ } catch {
99
+ return "";
100
+ }
101
+ }
102
+ function parseParamNames(fn) {
103
+ const src = parseFunctionSource(fn);
104
+ const clean = src.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "").replace(/\n/g, " ");
105
+ let paramBlock = "";
106
+ const arrowIdx = clean.indexOf("=>");
107
+ const firstParen = clean.indexOf("(");
108
+ clean.indexOf("{");
109
+ if (firstParen !== -1) {
110
+ let depth = 0;
111
+ let start = firstParen;
112
+ let end = -1;
113
+ for (let i = start; i < clean.length; i++) {
114
+ const ch = clean[i];
115
+ if (ch === "(") depth++;
116
+ else if (ch === ")") {
117
+ depth--;
118
+ if (depth === 0) {
119
+ end = i;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ if (end !== -1) {
125
+ paramBlock = clean.slice(start + 1, end);
126
+ }
127
+ } else if (arrowIdx !== -1) {
128
+ const beforeArrow = clean.slice(0, arrowIdx).trim();
129
+ const single = beforeArrow.split(/\s+/).pop() || "";
130
+ if (single && !single.includes(" ")) {
131
+ paramBlock = single.replace(/^async\s+/, "");
132
+ }
133
+ }
134
+ if (!paramBlock.trim()) return [];
135
+ const params = [];
136
+ let current = "";
137
+ let depthCurly = 0;
138
+ let depthBracket = 0;
139
+ let depthParen = 0;
140
+ for (let i = 0; i < paramBlock.length; i++) {
141
+ const ch = paramBlock[i];
142
+ if (ch === "{") depthCurly++;
143
+ else if (ch === "}") depthCurly--;
144
+ else if (ch === "[") depthBracket++;
145
+ else if (ch === "]") depthBracket--;
146
+ else if (ch === "(") depthParen++;
147
+ else if (ch === ")") depthParen--;
148
+ if (ch === "," && depthCurly === 0 && depthBracket === 0 && depthParen === 0) {
149
+ const p = current.trim();
150
+ if (p) params.push(parseSingleParam(p));
151
+ current = "";
152
+ } else {
153
+ current += ch;
154
+ }
155
+ }
156
+ const last = current.trim();
157
+ if (last) params.push(parseSingleParam(last));
158
+ return params;
159
+ }
160
+ function parseSingleParam(raw) {
161
+ let s = raw.trim();
162
+ if (s.startsWith("...")) s = s.slice(3).trim();
163
+ let hasDefault = false;
164
+ let defaultValue = void 0;
165
+ let eqIdx = -1;
166
+ let depth = 0;
167
+ for (let i = 0; i < s.length; i++) {
168
+ const ch = s[i];
169
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
170
+ else if (ch === "}" || ch === ")" || ch === "]") depth--;
171
+ else if (ch === "=" && depth === 0) {
172
+ eqIdx = i;
173
+ break;
174
+ }
175
+ }
176
+ let namePart = s;
177
+ if (eqIdx !== -1) {
178
+ hasDefault = true;
179
+ namePart = s.slice(0, eqIdx).trim();
180
+ const defStr = s.slice(eqIdx + 1).trim();
181
+ defaultValue = parseDefaultLiteral(defStr);
182
+ }
183
+ let name = namePart;
184
+ if (name.startsWith("{") && name.endsWith("}")) {
185
+ name = name;
186
+ } else if (name.startsWith("[")) {
187
+ name = name;
188
+ }
189
+ return { name, hasDefault, defaultValue };
190
+ }
191
+ function parseDefaultLiteral(str) {
192
+ const t = str.trim().replace(/,$/, "");
193
+ if (t === "true") return true;
194
+ if (t === "false") return false;
195
+ if (t === "null") return null;
196
+ if (t === "undefined") return void 0;
197
+ if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
198
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'") || t.startsWith("`") && t.endsWith("`")) {
199
+ return t.slice(1, -1);
200
+ }
201
+ if (t.startsWith("{") || t.startsWith("[")) return void 0;
202
+ return void 0;
203
+ }
204
+ function inferRuntime(fn) {
205
+ const params = parseParamNames(fn);
206
+ const src = parseFunctionSource(fn);
207
+ const isAsync = /^\s*async\b/.test(src);
208
+ let isObjectParam = false;
209
+ let objectKeys = [];
210
+ if (params.length === 1 && params[0].name.startsWith("{")) {
211
+ isObjectParam = true;
212
+ const inner = params[0].name.slice(1, -1);
213
+ const keys = [];
214
+ let cur = "";
215
+ let d = 0;
216
+ for (let i = 0; i < inner.length; i++) {
217
+ const ch = inner[i];
218
+ if (ch === "{" || ch === "[" || ch === "(") d++;
219
+ else if (ch === "}" || ch === "]" || ch === ")") d--;
220
+ if (ch === "," && d === 0) {
221
+ const k = cur.trim();
222
+ if (k) keys.push(extractKey(k));
223
+ cur = "";
224
+ } else cur += ch;
225
+ }
226
+ const last = cur.trim();
227
+ if (last) keys.push(extractKey(last));
228
+ objectKeys = keys.filter(Boolean);
229
+ }
230
+ const inference = {
231
+ params,
232
+ isAsync,
233
+ isObjectParam,
234
+ objectKeys,
235
+ confidence: "low"
236
+ };
237
+ const schema = {
238
+ type: "object",
239
+ properties: {},
240
+ required: [],
241
+ additionalProperties: false
242
+ };
243
+ if (isObjectParam && objectKeys.length > 0) {
244
+ const props = {};
245
+ const required = [];
246
+ const innerRaw = params[0].name.slice(1, -1);
247
+ const entries = splitObjectEntries(innerRaw);
248
+ for (const entry of entries) {
249
+ const { key, hasDefault, defaultValue } = entry;
250
+ const prop = {};
251
+ if (typeof defaultValue === "number") prop.type = "number";
252
+ else if (typeof defaultValue === "boolean") prop.type = "boolean";
253
+ if (hasDefault && defaultValue !== void 0) prop.default = defaultValue;
254
+ props[key] = prop;
255
+ if (!hasDefault) required.push(key);
256
+ }
257
+ schema.properties = props;
258
+ schema.required = required;
259
+ } else if (params.length > 0) {
260
+ const props = {};
261
+ const required = [];
262
+ for (const p of params) {
263
+ let key = p.name;
264
+ if (key.includes(":")) key = key.split(":")[0].trim();
265
+ if (key.includes("=")) key = key.split("=")[0].trim();
266
+ if (key.startsWith("{") || key.startsWith("[")) continue;
267
+ if (!key) continue;
268
+ const prop = {};
269
+ if (typeof p.defaultValue === "number") prop.type = "number";
270
+ else if (typeof p.defaultValue === "boolean") prop.type = "boolean";
271
+ if (p.hasDefault && p.defaultValue !== void 0) prop.default = p.defaultValue;
272
+ props[key] = prop;
273
+ if (!p.hasDefault) required.push(key);
274
+ }
275
+ schema.properties = props;
276
+ schema.required = required;
277
+ }
278
+ return { inference, schema };
279
+ }
280
+ function extractKey(raw) {
281
+ let s = raw.trim();
282
+ const eq = s.indexOf("=");
283
+ if (eq !== -1) s = s.slice(0, eq).trim();
284
+ const colon = s.indexOf(":");
285
+ if (colon !== -1) s = s.slice(0, colon).trim();
286
+ if (s.startsWith("...")) s = s.slice(3).trim();
287
+ s = s.replace(/^{|}$/g, "").trim();
288
+ return s.split(/\s+/)[0] || "";
289
+ }
290
+ function splitObjectEntries(inner) {
291
+ const res = [];
292
+ let cur = "";
293
+ let d = 0;
294
+ for (let i = 0; i < inner.length; i++) {
295
+ const ch = inner[i];
296
+ if (ch === "{" || ch === "[" || ch === "(") d++;
297
+ else if (ch === "}" || ch === "]" || ch === ")") d--;
298
+ if (ch === "," && d === 0) {
299
+ if (cur.trim()) res.push(parseObjectEntry(cur.trim()));
300
+ cur = "";
301
+ } else cur += ch;
302
+ }
303
+ if (cur.trim()) res.push(parseObjectEntry(cur.trim()));
304
+ return res;
305
+ }
306
+ function parseObjectEntry(raw) {
307
+ let s = raw.trim();
308
+ let hasDefault = false;
309
+ let defaultValue;
310
+ let eqIdx = s.indexOf("=");
311
+ if (eqIdx !== -1) {
312
+ hasDefault = true;
313
+ const def = s.slice(eqIdx + 1).trim();
314
+ defaultValue = parseDefaultLiteral(def);
315
+ s = s.slice(0, eqIdx).trim();
316
+ }
317
+ const colon = s.indexOf(":");
318
+ if (colon !== -1) s = s.slice(0, colon).trim();
319
+ const key = extractKey(s);
320
+ return { key, hasDefault, defaultValue };
321
+ }
322
+
323
+ // src/internal/schema.ts
324
+ var ZOD_CONVERTER_KEY = "__simpleWebmcp_zodConverter";
325
+ function getZodConverter() {
326
+ return globalThis[ZOD_CONVERTER_KEY] ?? null;
327
+ }
328
+ function zodLikeToJsonSchema(schema) {
329
+ const conv = getZodConverter();
330
+ if (conv) {
331
+ try {
332
+ return conv(schema);
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+ return null;
338
+ }
339
+ function standardSchemaToJsonSchema(schema) {
340
+ const zodJson = zodLikeToJsonSchema(schema);
341
+ if (zodJson) return zodJson;
342
+ return null;
343
+ }
344
+ function normalizeSchemaInput(schema) {
345
+ if (!schema) return { json: null, standard: null };
346
+ if (looksLikeJsonSchema(schema)) {
347
+ return { json: schema, standard: null };
348
+ }
349
+ if (isStandardSchema(schema)) {
350
+ const std = schema;
351
+ const json = standardSchemaToJsonSchema(std);
352
+ return { json, standard: std };
353
+ }
354
+ const maybeZodJson = zodLikeToJsonSchema(schema);
355
+ if (maybeZodJson) return { json: maybeZodJson, standard: isStandardSchema(schema) ? schema : null };
356
+ if (typeof schema === "object" && schema !== null) {
357
+ return { json: schema, standard: null };
358
+ }
359
+ return { json: null, standard: null };
360
+ }
361
+ function applyFieldsPatch(base, fields) {
362
+ if (!fields || Object.keys(fields).length === 0) return base;
363
+ const result = {
364
+ ...base,
365
+ properties: { ...base.properties || {} },
366
+ required: [...base.required || []]
367
+ };
368
+ if (!result.properties) result.properties = {};
369
+ if (!result.required) result.required = [];
370
+ for (const [key, field] of Object.entries(fields)) {
371
+ const isStd = isStandardSchema(field);
372
+ if (isStd) {
373
+ const json = standardSchemaToJsonSchema(field);
374
+ const fragment = json ?? { type: "string" };
375
+ result.properties[key] = fragment;
376
+ const anyField = field;
377
+ const isOpt = anyField?._def?.typeName === "ZodOptional" || anyField?.isOptional?.() === true;
378
+ if (!isOpt) {
379
+ if (!result.required.includes(key)) result.required.push(key);
380
+ } else {
381
+ result.required = result.required.filter((k) => k !== key);
382
+ }
383
+ const desc = anyField?.description ?? anyField?._def?.description;
384
+ if (desc && !result.properties[key].description) result.properties[key].description = desc;
385
+ } else {
386
+ const patch = field;
387
+ const existing = result.properties[key] || {};
388
+ const merged = { ...existing, ...patch };
389
+ result.properties[key] = merged;
390
+ if ("required" in patch) {
391
+ const req = patch.required;
392
+ if (req === false) {
393
+ result.required = result.required.filter((k) => k !== key);
394
+ delete merged.required;
395
+ } else if (req === true) {
396
+ if (!result.required.includes(key)) result.required.push(key);
397
+ delete merged.required;
398
+ }
399
+ } else {
400
+ if (!(key in (base.properties || {})) && !patch.required) {
401
+ const hasDefault = "default" in patch;
402
+ if (!hasDefault && !result.required.includes(key)) result.required.push(key);
403
+ }
404
+ }
405
+ }
406
+ }
407
+ if (result.required && result.required.length === 0) delete result.required;
408
+ return result;
409
+ }
410
+ function buildFinalInputSchema(opts) {
411
+ const wholeNorm = normalizeSchemaInput(opts.wholeSchema);
412
+ let baseJson;
413
+ let standard = wholeNorm.standard;
414
+ if (wholeNorm.json) {
415
+ baseJson = wholeNorm.json;
416
+ } else if (wholeNorm.standard && !wholeNorm.json) {
417
+ baseJson = opts.inferred;
418
+ } else {
419
+ baseJson = opts.inferred;
420
+ }
421
+ if (!baseJson.type) baseJson.type = "object";
422
+ if (!baseJson.properties) baseJson.properties = {};
423
+ if (baseJson.additionalProperties == null) baseJson.additionalProperties = false;
424
+ const patched = applyFieldsPatch(baseJson, opts.fields);
425
+ return { json: patched, standard };
426
+ }
427
+ function normalizeOutputSchema(schema) {
428
+ if (!schema) return {};
429
+ const norm = normalizeSchemaInput(schema);
430
+ if (norm.json) return { json: norm.json, standard: norm.standard ?? void 0 };
431
+ if (norm.standard) return { standard: norm.standard };
432
+ return {};
433
+ }
434
+
435
+ // src/internal/registry.ts
436
+ var Registry = class {
437
+ constructor() {
438
+ this.entries = /* @__PURE__ */ new Map();
439
+ }
440
+ // expose for devtools/tests
441
+ list() {
442
+ return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status }));
443
+ }
444
+ clear() {
445
+ for (const e of this.entries.values()) {
446
+ try {
447
+ e.controller.abort();
448
+ } catch {
449
+ }
450
+ }
451
+ this.entries.clear();
452
+ }
453
+ isRegistered(name) {
454
+ return this.entries.get(name)?.status === "registered";
455
+ }
456
+ getStatus(name) {
457
+ return this.entries.get(name)?.status ?? "unregistered";
458
+ }
459
+ async register(contract, opts) {
460
+ const name = contract.name;
461
+ const existing = this.entries.get(name);
462
+ if (existing && (existing.status === "registered" || existing.status === "registering")) {
463
+ return existing.unregister;
464
+ }
465
+ if (!isWebMCPSupported()) {
466
+ const controller2 = new AbortController();
467
+ const entry2 = {
468
+ name,
469
+ controller: controller2,
470
+ status: "registered",
471
+ promise: Promise.resolve(),
472
+ unregister: () => {
473
+ try {
474
+ controller2.abort();
475
+ } catch {
476
+ }
477
+ this.entries.delete(name);
478
+ }
479
+ };
480
+ if (opts?.signal) {
481
+ if (opts.signal.aborted) {
482
+ controller2.abort();
483
+ this.entries.delete(name);
484
+ return () => {
485
+ };
486
+ }
487
+ opts.signal.addEventListener("abort", () => {
488
+ try {
489
+ controller2.abort();
490
+ } catch {
491
+ }
492
+ this.entries.delete(name);
493
+ }, { once: true });
494
+ }
495
+ this.entries.set(name, entry2);
496
+ return entry2.unregister;
497
+ }
498
+ const modelContext = getModelContext();
499
+ if (!modelContext) throw new NotSupportedError();
500
+ const controller = new AbortController();
501
+ if (opts?.signal) {
502
+ if (opts.signal.aborted) {
503
+ controller.abort();
504
+ } else {
505
+ opts.signal.addEventListener(
506
+ "abort",
507
+ () => {
508
+ try {
509
+ controller.abort();
510
+ } catch {
511
+ }
512
+ },
513
+ { once: true }
514
+ );
515
+ }
516
+ }
517
+ const entry = {
518
+ name,
519
+ controller,
520
+ status: "registering",
521
+ promise: null,
522
+ unregister: () => {
523
+ if (entry.status === "registered" || entry.status === "registering") {
524
+ entry.status = "unregistering";
525
+ try {
526
+ controller.abort();
527
+ } catch {
528
+ }
529
+ entry.status = "unregistered";
530
+ this.entries.delete(name);
531
+ }
532
+ }
533
+ };
534
+ this.entries.set(name, entry);
535
+ const wrappedExecute = opts?.execute ?? (async (args) => ({ content: [{ type: "text", text: `no execute for ${name}` }] }));
536
+ try {
537
+ const regPromise = modelContext.registerTool(
538
+ {
539
+ name: contract.name,
540
+ description: contract.description,
541
+ inputSchema: contract.inputSchema,
542
+ outputSchema: contract.outputSchema,
543
+ annotations: contract.annotations,
544
+ execute: wrappedExecute
545
+ },
546
+ { signal: controller.signal }
547
+ );
548
+ entry.promise = regPromise;
549
+ await regPromise;
550
+ if (controller.signal.aborted) {
551
+ entry.status = "unregistered";
552
+ this.entries.delete(name);
553
+ } else {
554
+ entry.status = "registered";
555
+ }
556
+ controller.signal.addEventListener("abort", () => {
557
+ entry.status = "unregistered";
558
+ this.entries.delete(name);
559
+ }, { once: true });
560
+ return entry.unregister;
561
+ } catch (err) {
562
+ entry.status = "error";
563
+ this.entries.delete(name);
564
+ const msg = err?.message || String(err);
565
+ if (err?.name === "NotAllowedError" || /NotAllowedError|Permissions Policy|blocked/i.test(msg)) {
566
+ throw new NotAllowedError(msg, { cause: err });
567
+ }
568
+ throw new RegistrationError(`Failed to register tool "${name}": ${msg}`, { cause: err });
569
+ }
570
+ }
571
+ unregister(name) {
572
+ const entry = this.entries.get(name);
573
+ if (!entry) return;
574
+ try {
575
+ entry.controller.abort();
576
+ } catch {
577
+ }
578
+ this.entries.delete(name);
579
+ }
580
+ };
581
+ var registry = new Registry();
582
+ function getRegistry() {
583
+ return registry;
584
+ }
585
+
586
+ // src/internal/normalize.ts
587
+ function normalizeResult(value) {
588
+ if (value == null) {
589
+ return { content: [{ type: "text", text: "" }] };
590
+ }
591
+ if (typeof value === "string") {
592
+ return { content: [{ type: "text", text: value }] };
593
+ }
594
+ if (typeof value === "number" || typeof value === "boolean") {
595
+ return { content: [{ type: "text", text: String(value) }] };
596
+ }
597
+ if (typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content)) {
598
+ return value;
599
+ }
600
+ if (typeof value === "object" && value !== null && "isError" in value) {
601
+ if ("content" in value) return value;
602
+ }
603
+ try {
604
+ const text = JSON.stringify(value, null, 2);
605
+ return { content: [{ type: "text", text }] };
606
+ } catch {
607
+ return { content: [{ type: "text", text: String(value) }] };
608
+ }
609
+ }
610
+ function normalizeError(err) {
611
+ const message = err instanceof Error ? err.message : typeof err === "string" ? err : (() => {
612
+ try {
613
+ return JSON.stringify(err);
614
+ } catch {
615
+ return String(err);
616
+ }
617
+ })();
618
+ return {
619
+ content: [{ type: "text", text: `Error: ${message}` }],
620
+ isError: true
621
+ };
622
+ }
623
+ function wrapExecute(fn, opts) {
624
+ return async (args, _ctx) => {
625
+ try {
626
+ let result;
627
+ const mode = opts?.argMode ?? "object";
628
+ if (mode === "spread" && args && typeof args === "object" && !Array.isArray(args)) {
629
+ const values = Object.values(args);
630
+ result = await fn(...values);
631
+ } else {
632
+ result = await fn(args);
633
+ }
634
+ return normalizeResult(result);
635
+ } catch (err) {
636
+ return normalizeError(err);
637
+ }
638
+ };
639
+ }
640
+
641
+ // src/webmcp.ts
642
+ function resolveScope(opts) {
643
+ if (opts?.scope) return opts.scope;
644
+ if (opts?.global) return "global";
645
+ return "scoped";
646
+ }
647
+ function webmcp(fn, options) {
648
+ if (typeof fn !== "function") {
649
+ throw new ConfigurationError("webmcp(fn, opts) \u2014 first argument must be a function");
650
+ }
651
+ const anyFn = fn;
652
+ if (anyFn.__webmcpBrand === true && anyFn.definition) {
653
+ if (!options || Object.keys(options).length === 0) return anyFn;
654
+ const original = anyFn.__fn ?? fn;
655
+ return webmcp(original, { ...anyFn.__webmcpOptions || {}, ...options });
656
+ }
657
+ const name = options?.name ?? toSnakeCase(getFunctionName(fn));
658
+ if (!name) throw new ConfigurationError('Tool name could not be inferred \u2014 pass {name:"my_tool"}');
659
+ let description = options?.description ?? anyFn.__webmcpDescription ?? "";
660
+ if (!description) {
661
+ warnNoDescription(fn);
662
+ description = "";
663
+ }
664
+ const hasSchema = !!options?.schema;
665
+ const hasFields = !!options?.fields && Object.keys(options.fields).length > 0;
666
+ if (options?.strict && !hasSchema && !hasFields) {
667
+ const { schema: inferred } = inferRuntime(fn);
668
+ const props = inferred.properties || {};
669
+ const propKeys = Object.keys(props);
670
+ const hasTypedProps = propKeys.length > 0 && propKeys.some((k) => !!props[k]?.type);
671
+ if (!hasTypedProps) {
672
+ throw new ConfigurationError(
673
+ `webmcp strict: could not infer schema for "${name}" \u2014 add TypeScript types, JSDoc, {schema} or {fields}`
674
+ );
675
+ }
676
+ }
677
+ const { schema: inferredSchema } = inferRuntime(fn);
678
+ const { json: finalInput, standard } = buildFinalInputSchema({
679
+ wholeSchema: options?.schema,
680
+ inferred: inferredSchema,
681
+ fields: options?.fields
682
+ });
683
+ const outNorm = normalizeOutputSchema(options?.outputSchema);
684
+ const annotations = options?.annotations;
685
+ const contract = {
686
+ name,
687
+ description,
688
+ inputSchema: finalInput,
689
+ outputSchema: outNorm.json,
690
+ annotations
691
+ };
692
+ const enabled = options?.enabled ?? true;
693
+ const wrapper = function(...args) {
694
+ return fn.apply(this, args);
695
+ };
696
+ try {
697
+ Object.defineProperty(wrapper, "name", { value: fn.name || name, configurable: true });
698
+ } catch {
699
+ }
700
+ try {
701
+ Object.defineProperty(wrapper, "length", { value: fn.length, configurable: true });
702
+ } catch {
703
+ }
704
+ let status = "unregistered";
705
+ let registrationPromise = null;
706
+ let unregisterFn = null;
707
+ let activeController = null;
708
+ const wrappedExec = wrapExecute(fn);
709
+ const toolWrapper = wrapper;
710
+ toolWrapper.__webmcpBrand = true;
711
+ toolWrapper.__fn = fn;
712
+ toolWrapper.__webmcpOptions = options;
713
+ if (standard) toolWrapper.__standardSchema = standard;
714
+ if (outNorm.standard) toolWrapper.__outputStandardSchema = outNorm.standard;
715
+ toolWrapper.tool = contract;
716
+ toolWrapper.definition = contract;
717
+ Object.defineProperty(toolWrapper, "status", {
718
+ get() {
719
+ return status;
720
+ },
721
+ enumerable: true
722
+ });
723
+ Object.defineProperty(toolWrapper, "registration", {
724
+ get() {
725
+ return registrationPromise;
726
+ },
727
+ enumerable: true
728
+ });
729
+ toolWrapper.isRegistered = () => status === "registered";
730
+ toolWrapper.register = async (opts) => {
731
+ if (status === "registered" || status === "registering") {
732
+ return unregisterFn ?? (() => toolWrapper.unregister());
733
+ }
734
+ if (enabled === false) {
735
+ return () => {
736
+ };
737
+ }
738
+ status = "registering";
739
+ const controller = new AbortController();
740
+ activeController = controller;
741
+ if (opts?.signal) {
742
+ if (opts.signal.aborted) {
743
+ controller.abort();
744
+ status = "unregistered";
745
+ activeController = null;
746
+ return () => {
747
+ };
748
+ }
749
+ opts.signal.addEventListener("abort", () => {
750
+ try {
751
+ controller.abort();
752
+ } catch {
753
+ }
754
+ toolWrapper.unregister();
755
+ }, { once: true });
756
+ }
757
+ controller.signal.addEventListener("abort", () => {
758
+ if (status !== "unregistered") status = "unregistered";
759
+ activeController = null;
760
+ }, { once: true });
761
+ try {
762
+ const unregister = await registry.register(contract, {
763
+ signal: controller.signal,
764
+ execute: wrappedExec
765
+ });
766
+ unregisterFn = unregister;
767
+ registrationPromise = Promise.resolve();
768
+ if (controller.signal.aborted) {
769
+ status = "unregistered";
770
+ } else {
771
+ status = "registered";
772
+ }
773
+ return () => {
774
+ try {
775
+ unregister();
776
+ } catch {
777
+ }
778
+ status = "unregistered";
779
+ activeController = null;
780
+ unregisterFn = null;
781
+ };
782
+ } catch (err) {
783
+ status = "error";
784
+ registrationPromise = Promise.reject(err);
785
+ if (err instanceof SimpleWebMCPError) throw err;
786
+ throw err;
787
+ }
788
+ };
789
+ toolWrapper.unregister = () => {
790
+ if (status === "unregistered") return;
791
+ status = "unregistering";
792
+ try {
793
+ registry.unregister(contract.name);
794
+ } catch {
795
+ }
796
+ try {
797
+ activeController?.abort();
798
+ } catch {
799
+ }
800
+ status = "unregistered";
801
+ activeController = null;
802
+ unregisterFn = null;
803
+ registrationPromise = null;
804
+ };
805
+ const scope = resolveScope(options);
806
+ if (scope === "global" && enabled !== false) {
807
+ if (typeof document !== "undefined") {
808
+ queueMicrotask(() => {
809
+ toolWrapper.register().catch(() => {
810
+ });
811
+ });
812
+ }
813
+ }
814
+ toolWrapper.toString = fn.toString.bind(fn);
815
+ return toolWrapper;
816
+ }
817
+ webmcp.global = function global(fn, opts) {
818
+ return webmcp(fn, { ...opts, global: true });
819
+ };
820
+ webmcp.isWebMCPTool = function isWebMCPTool(v) {
821
+ return !!v?.__webmcpBrand;
822
+ };
823
+ var webmcp_default = webmcp;
824
+
825
+ // src/constants.ts
826
+ var SDK_VERSION = "0.1.0";
827
+
828
+ exports.ConfigurationError = ConfigurationError;
829
+ exports.NotAllowedError = NotAllowedError;
830
+ exports.NotSupportedError = NotSupportedError;
831
+ exports.RegistrationError = RegistrationError;
832
+ exports.SDK_VERSION = SDK_VERSION;
833
+ exports.SimpleWebMCPError = SimpleWebMCPError;
834
+ exports.ValidationError = ValidationError;
835
+ exports.applyFieldsPatch = applyFieldsPatch;
836
+ exports.buildFinalInputSchema = buildFinalInputSchema;
837
+ exports.default = webmcp_default;
838
+ exports.getModelContext = getModelContext;
839
+ exports.getRegistry = getRegistry;
840
+ exports.inferRuntime = inferRuntime;
841
+ exports.isWebMCPSupported = isWebMCPSupported;
842
+ exports.normalizeError = normalizeError;
843
+ exports.normalizeResult = normalizeResult;
844
+ exports.registry = registry;
845
+ exports.toSnakeCase = toSnakeCase;
846
+ exports.webmcp = webmcp;
847
+ exports.wrapExecute = wrapExecute;
848
+ //# sourceMappingURL=index.cjs.map
849
+ //# sourceMappingURL=index.cjs.map