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.
- package/.agents/skills/webmcp-simple/SKILL.md +27 -12
- package/README.md +211 -71
- 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 +350 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -51
- package/dist/index.d.ts +60 -51
- package/dist/index.js +344 -42
- 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 +1191 -12
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +28 -10
- package/dist/react.d.ts +28 -10
- package/dist/react.js +1190 -15
- package/dist/react.js.map +1 -1
- package/dist/registry-A4DdpmDn.d.cts +38 -0
- package/dist/registry-Dscedahn.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-CF8kTj5O.d.cts} +61 -2
- package/dist/{types-D-cwSfEU.d.ts → types-CF8kTj5O.d.ts} +61 -2
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/package.json +30 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { b as WebMCPHooks, a as WebMCPOptions, W as WebMCPTool, T as ToolContract, M as ModelContextLike, J as JsonSchema, F as FieldDefOrSchema, S as StandardSchemaV1 } from './types-CF8kTj5O.js';
|
|
2
|
+
export { A as AfterContext, c as AfterHook, B as BeforeContext, d as BeforeHook, D as DeniedContext, e as DeniedHook, E as ErrorContext, f as ErrorHook, g as FieldDef, R as RegistrationStatus, h as WebMCPAnnotations } from './types-CF8kTj5O.js';
|
|
3
|
+
export { g as getRegistry, r as registry } from './registry-Dscedahn.js';
|
|
4
|
+
|
|
5
|
+
declare function getGlobalHooks(): WebMCPHooks<any>;
|
|
6
|
+
declare function configureWebMCP(opts: {
|
|
7
|
+
hooks?: WebMCPHooks<any>;
|
|
8
|
+
replace?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
declare function resetGlobalHooks(): void;
|
|
11
|
+
declare function mergeHooks<F extends (...args: any) => any>(a: WebMCPHooks<any> | undefined, b: WebMCPHooks<F> | undefined): WebMCPHooks<F>;
|
|
12
|
+
/**
|
|
13
|
+
* Merge with correct ordering semantics:
|
|
14
|
+
* before: global → scoped → tool
|
|
15
|
+
* after: tool → scoped → global (onion)
|
|
16
|
+
* error/denied: tool → scoped → global
|
|
17
|
+
*/
|
|
18
|
+
declare function mergeHooksOrdered<F extends (...args: any) => any>(opts: {
|
|
19
|
+
globalHooks?: WebMCPHooks<any>;
|
|
20
|
+
scopedHooks?: WebMCPHooks<any>;
|
|
21
|
+
toolHooks?: WebMCPHooks<F>;
|
|
22
|
+
}): WebMCPHooks<F>;
|
|
3
23
|
|
|
4
24
|
declare function webmcp<F extends (...args: any) => any>(fn: F, options?: WebMCPOptions<F>): WebMCPTool<F>;
|
|
5
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Normalize execute return values per WebMCP spec.
|
|
28
|
+
* WebMCP expects { content: [{type:'text', text: ...}], isError? }
|
|
29
|
+
* We allow fn to return string, object, or already-normalized shape.
|
|
30
|
+
*/
|
|
31
|
+
type NormalizedResult = {
|
|
32
|
+
content: Array<{
|
|
33
|
+
type: 'text';
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: string;
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}>;
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
[k: string]: unknown;
|
|
41
|
+
};
|
|
42
|
+
declare function normalizeResult(value: unknown): NormalizedResult;
|
|
43
|
+
declare function normalizeError(err: unknown): NormalizedResult;
|
|
44
|
+
/**
|
|
45
|
+
* Wrap an execute fn to handle:
|
|
46
|
+
* - args: object (WebMCP passes single object) vs spread
|
|
47
|
+
* - async errors
|
|
48
|
+
* - result normalization
|
|
49
|
+
* For 0.1 we assume fn expects single object param (preferred).
|
|
50
|
+
* For multi-arg legacy, user should use webmcp.bind (deferred) — here we provide auto-spread fallback heuristic.
|
|
51
|
+
*/
|
|
52
|
+
declare function wrapExecute<F extends (...args: any) => any>(fn: F, opts?: {
|
|
53
|
+
argMode?: 'object' | 'spread';
|
|
54
|
+
}): (args: unknown, ctx?: unknown) => Promise<NormalizedResult>;
|
|
55
|
+
|
|
56
|
+
declare function genInvocationId(): string;
|
|
57
|
+
type CreateHookedExecuteOptions<F extends (...args: any) => any> = {
|
|
58
|
+
getHooks: () => WebMCPHooks<F>;
|
|
59
|
+
validate?: (input: unknown) => void;
|
|
60
|
+
};
|
|
61
|
+
declare function createHookedExecute<F extends (...args: any) => any>(fn: F, tool: WebMCPTool<F>, contract: ToolContract, options: CreateHookedExecuteOptions<F>): (args: unknown, ctx?: unknown) => Promise<NormalizedResult>;
|
|
62
|
+
|
|
6
63
|
/**
|
|
7
64
|
* Base error for simple-webmcp
|
|
8
65
|
*/
|
|
@@ -55,54 +112,6 @@ declare function toSnakeCase(name: string): string;
|
|
|
55
112
|
declare function isWebMCPSupported(): boolean;
|
|
56
113
|
declare function getModelContext(): ModelContextLike | null;
|
|
57
114
|
|
|
58
|
-
declare class Registry {
|
|
59
|
-
private entries;
|
|
60
|
-
list(): Array<{
|
|
61
|
-
name: string;
|
|
62
|
-
status: RegistrationStatus;
|
|
63
|
-
}>;
|
|
64
|
-
clear(): void;
|
|
65
|
-
isRegistered(name: string): boolean;
|
|
66
|
-
getStatus(name: string): RegistrationStatus;
|
|
67
|
-
register(contract: ToolContract, opts?: {
|
|
68
|
-
signal?: AbortSignal;
|
|
69
|
-
execute?: (args: unknown, ctx?: unknown) => Promise<any>;
|
|
70
|
-
}): Promise<() => void>;
|
|
71
|
-
unregister(name: string): void;
|
|
72
|
-
}
|
|
73
|
-
declare const registry: Registry;
|
|
74
|
-
declare function getRegistry(): Registry;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Normalize execute return values per WebMCP spec.
|
|
78
|
-
* WebMCP expects { content: [{type:'text', text: ...}], isError? }
|
|
79
|
-
* We allow fn to return string, object, or already-normalized shape.
|
|
80
|
-
*/
|
|
81
|
-
type NormalizedResult = {
|
|
82
|
-
content: Array<{
|
|
83
|
-
type: 'text';
|
|
84
|
-
text: string;
|
|
85
|
-
} | {
|
|
86
|
-
type: string;
|
|
87
|
-
[k: string]: unknown;
|
|
88
|
-
}>;
|
|
89
|
-
isError?: boolean;
|
|
90
|
-
[k: string]: unknown;
|
|
91
|
-
};
|
|
92
|
-
declare function normalizeResult(value: unknown): NormalizedResult;
|
|
93
|
-
declare function normalizeError(err: unknown): NormalizedResult;
|
|
94
|
-
/**
|
|
95
|
-
* Wrap an execute fn to handle:
|
|
96
|
-
* - args: object (WebMCP passes single object) vs spread
|
|
97
|
-
* - async errors
|
|
98
|
-
* - result normalization
|
|
99
|
-
* For 0.1 we assume fn expects single object param (preferred).
|
|
100
|
-
* For multi-arg legacy, user should use webmcp.bind (deferred) — here we provide auto-spread fallback heuristic.
|
|
101
|
-
*/
|
|
102
|
-
declare function wrapExecute<F extends (...args: any) => any>(fn: F, opts?: {
|
|
103
|
-
argMode?: 'object' | 'spread';
|
|
104
|
-
}): (args: unknown, ctx?: unknown) => Promise<NormalizedResult>;
|
|
105
|
-
|
|
106
115
|
/**
|
|
107
116
|
* Runtime inference — best-effort, confidence: low (§11).
|
|
108
117
|
* We cannot know types from JS alone. We return placeholders and defaults detection.
|
|
@@ -150,4 +159,4 @@ declare function buildFinalInputSchema(opts: {
|
|
|
150
159
|
|
|
151
160
|
declare const SDK_VERSION = "0.1.0";
|
|
152
161
|
|
|
153
|
-
export { ConfigurationError, FieldDefOrSchema, JsonSchema, NotAllowedError, NotSupportedError, RegistrationError,
|
|
162
|
+
export { ConfigurationError, FieldDefOrSchema, JsonSchema, NotAllowedError, NotSupportedError, RegistrationError, SDK_VERSION, SimpleWebMCPError, StandardSchemaV1, ToolContract, ValidationError, WebMCPHooks, WebMCPOptions, WebMCPTool, applyFieldsPatch, buildFinalInputSchema, configureWebMCP, createHookedExecute, webmcp as default, genInvocationId, getGlobalHooks, getModelContext, inferRuntime, isWebMCPSupported, mergeHooks, mergeHooksOrdered, normalizeError, normalizeResult, resetGlobalHooks, toSnakeCase, webmcp, wrapExecute };
|
package/dist/index.js
CHANGED
|
@@ -433,9 +433,15 @@ var Registry = class {
|
|
|
433
433
|
constructor() {
|
|
434
434
|
this.entries = /* @__PURE__ */ new Map();
|
|
435
435
|
}
|
|
436
|
-
// expose for devtools/tests
|
|
436
|
+
// expose for devtools/tests — now includes contract for inspect
|
|
437
437
|
list() {
|
|
438
|
-
return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status }));
|
|
438
|
+
return Array.from(this.entries.values()).map((e) => ({ name: e.name, status: e.status, contract: e.contract }));
|
|
439
|
+
}
|
|
440
|
+
get(name) {
|
|
441
|
+
return this.entries.get(name);
|
|
442
|
+
}
|
|
443
|
+
getContract(name) {
|
|
444
|
+
return this.entries.get(name)?.contract;
|
|
439
445
|
}
|
|
440
446
|
clear() {
|
|
441
447
|
for (const e of this.entries.values()) {
|
|
@@ -459,37 +465,10 @@ var Registry = class {
|
|
|
459
465
|
return existing.unregister;
|
|
460
466
|
}
|
|
461
467
|
if (!isWebMCPSupported()) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
controller: controller2,
|
|
466
|
-
status: "registered",
|
|
467
|
-
promise: Promise.resolve(),
|
|
468
|
-
unregister: () => {
|
|
469
|
-
try {
|
|
470
|
-
controller2.abort();
|
|
471
|
-
} catch {
|
|
472
|
-
}
|
|
473
|
-
this.entries.delete(name);
|
|
474
|
-
}
|
|
468
|
+
if (opts?.signal?.aborted) return () => {
|
|
469
|
+
};
|
|
470
|
+
return () => {
|
|
475
471
|
};
|
|
476
|
-
if (opts?.signal) {
|
|
477
|
-
if (opts.signal.aborted) {
|
|
478
|
-
controller2.abort();
|
|
479
|
-
this.entries.delete(name);
|
|
480
|
-
return () => {
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
opts.signal.addEventListener("abort", () => {
|
|
484
|
-
try {
|
|
485
|
-
controller2.abort();
|
|
486
|
-
} catch {
|
|
487
|
-
}
|
|
488
|
-
this.entries.delete(name);
|
|
489
|
-
}, { once: true });
|
|
490
|
-
}
|
|
491
|
-
this.entries.set(name, entry2);
|
|
492
|
-
return entry2.unregister;
|
|
493
472
|
}
|
|
494
473
|
const modelContext = getModelContext();
|
|
495
474
|
if (!modelContext) throw new NotSupportedError();
|
|
@@ -525,7 +504,9 @@ var Registry = class {
|
|
|
525
504
|
entry.status = "unregistered";
|
|
526
505
|
this.entries.delete(name);
|
|
527
506
|
}
|
|
528
|
-
}
|
|
507
|
+
},
|
|
508
|
+
contract,
|
|
509
|
+
execute: opts?.execute
|
|
529
510
|
};
|
|
530
511
|
this.entries.set(name, entry);
|
|
531
512
|
const wrappedExecute = opts?.execute ?? (async (args) => ({ content: [{ type: "text", text: `no execute for ${name}` }] }));
|
|
@@ -574,7 +555,12 @@ var Registry = class {
|
|
|
574
555
|
this.entries.delete(name);
|
|
575
556
|
}
|
|
576
557
|
};
|
|
577
|
-
var
|
|
558
|
+
var REGISTRY_KEY = "__simpleWebmcpRegistry";
|
|
559
|
+
var globalAny = globalThis;
|
|
560
|
+
var registry = globalAny[REGISTRY_KEY] ?? new Registry();
|
|
561
|
+
if (!globalAny[REGISTRY_KEY]) {
|
|
562
|
+
globalAny[REGISTRY_KEY] = registry;
|
|
563
|
+
}
|
|
578
564
|
function getRegistry() {
|
|
579
565
|
return registry;
|
|
580
566
|
}
|
|
@@ -604,7 +590,12 @@ function normalizeResult(value) {
|
|
|
604
590
|
}
|
|
605
591
|
}
|
|
606
592
|
function normalizeError(err) {
|
|
607
|
-
const message =
|
|
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
|
+
}
|
|
608
599
|
try {
|
|
609
600
|
return JSON.stringify(err);
|
|
610
601
|
} catch {
|
|
@@ -634,6 +625,247 @@ function wrapExecute(fn, opts) {
|
|
|
634
625
|
};
|
|
635
626
|
}
|
|
636
627
|
|
|
628
|
+
// src/hooks/engine.ts
|
|
629
|
+
var fallbackCounter = 0;
|
|
630
|
+
function genInvocationId() {
|
|
631
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
632
|
+
return crypto.randomUUID();
|
|
633
|
+
}
|
|
634
|
+
return fallbackInvocationId();
|
|
635
|
+
}
|
|
636
|
+
function fallbackInvocationId() {
|
|
637
|
+
fallbackCounter = (fallbackCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
638
|
+
return `webmcp_${Date.now().toString(36)}_${fallbackCounter}_${Math.random().toString(36).slice(2, 8)}`;
|
|
639
|
+
}
|
|
640
|
+
function isDenyResult(r) {
|
|
641
|
+
return !!r && typeof r === "object" && r.action === "deny";
|
|
642
|
+
}
|
|
643
|
+
async function runErrorHooks(hooks, ctx) {
|
|
644
|
+
if (!hooks || hooks.length === 0) return;
|
|
645
|
+
for (const h of hooks) {
|
|
646
|
+
try {
|
|
647
|
+
await h(ctx);
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function runDeniedHooks(hooks, ctx) {
|
|
653
|
+
if (!hooks || hooks.length === 0) return;
|
|
654
|
+
for (const h of hooks) {
|
|
655
|
+
try {
|
|
656
|
+
await h(ctx);
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function createHookedExecute(fn, tool, contract, options) {
|
|
662
|
+
return async (args, _ctx) => {
|
|
663
|
+
const invocationId = genInvocationId();
|
|
664
|
+
const metadata = {};
|
|
665
|
+
const signal = tool.__activeSignal ?? createNeverAbortedSignal();
|
|
666
|
+
const merged = options.getHooks();
|
|
667
|
+
let currentInput = args;
|
|
668
|
+
const base = { invocationId, tool, contract, signal, metadata };
|
|
669
|
+
const beforeHooks = merged.before ?? [];
|
|
670
|
+
for (const hook of beforeHooks) {
|
|
671
|
+
if (signal.aborted) {
|
|
672
|
+
const abortErr = new DOMException("Aborted", "AbortError");
|
|
673
|
+
await runErrorHooks(merged.error, {
|
|
674
|
+
...base,
|
|
675
|
+
input: currentInput,
|
|
676
|
+
error: abortErr
|
|
677
|
+
});
|
|
678
|
+
return normalizeError(abortErr);
|
|
679
|
+
}
|
|
680
|
+
let res;
|
|
681
|
+
try {
|
|
682
|
+
const ctx = { ...base, input: currentInput };
|
|
683
|
+
res = await hook(ctx);
|
|
684
|
+
} catch (hookErr) {
|
|
685
|
+
await runErrorHooks(merged.error, {
|
|
686
|
+
...base,
|
|
687
|
+
input: currentInput,
|
|
688
|
+
error: hookErr
|
|
689
|
+
});
|
|
690
|
+
return normalizeError(hookErr);
|
|
691
|
+
}
|
|
692
|
+
if (res != null && typeof res === "object") {
|
|
693
|
+
if (isDenyResult(res)) {
|
|
694
|
+
const reason = res.message;
|
|
695
|
+
const code = res.code;
|
|
696
|
+
await runDeniedHooks(merged.denied, {
|
|
697
|
+
...base,
|
|
698
|
+
input: currentInput,
|
|
699
|
+
reason,
|
|
700
|
+
code
|
|
701
|
+
});
|
|
702
|
+
const msg = reason ?? "Tool execution denied";
|
|
703
|
+
return {
|
|
704
|
+
content: [{ type: "text", text: `Denied: ${msg}` }],
|
|
705
|
+
isError: true,
|
|
706
|
+
...code ? { code } : {}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if ("input" in res && res.input !== void 0) {
|
|
710
|
+
currentInput = res.input;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (signal.aborted) {
|
|
715
|
+
const abortErr = new DOMException("Aborted", "AbortError");
|
|
716
|
+
await runErrorHooks(merged.error, {
|
|
717
|
+
...base,
|
|
718
|
+
input: currentInput,
|
|
719
|
+
error: abortErr
|
|
720
|
+
});
|
|
721
|
+
return normalizeError(abortErr);
|
|
722
|
+
}
|
|
723
|
+
if (options.validate) {
|
|
724
|
+
try {
|
|
725
|
+
options.validate(currentInput);
|
|
726
|
+
} catch (valErr) {
|
|
727
|
+
await runErrorHooks(merged.error, {
|
|
728
|
+
...base,
|
|
729
|
+
input: currentInput,
|
|
730
|
+
error: valErr
|
|
731
|
+
});
|
|
732
|
+
return normalizeError(valErr);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
let rawOutput;
|
|
736
|
+
try {
|
|
737
|
+
rawOutput = await fn(currentInput);
|
|
738
|
+
} catch (fnErr) {
|
|
739
|
+
await runErrorHooks(merged.error, {
|
|
740
|
+
...base,
|
|
741
|
+
input: currentInput,
|
|
742
|
+
error: fnErr
|
|
743
|
+
});
|
|
744
|
+
return normalizeError(fnErr);
|
|
745
|
+
}
|
|
746
|
+
let currentOutput = rawOutput;
|
|
747
|
+
const afterHooks = merged.after ?? [];
|
|
748
|
+
for (const hook of afterHooks) {
|
|
749
|
+
if (signal.aborted) ;
|
|
750
|
+
try {
|
|
751
|
+
const ctx = {
|
|
752
|
+
...base,
|
|
753
|
+
input: currentInput,
|
|
754
|
+
output: currentOutput
|
|
755
|
+
};
|
|
756
|
+
const res = await hook(ctx);
|
|
757
|
+
if (res != null && typeof res === "object" && "output" in res && res.output !== void 0) {
|
|
758
|
+
currentOutput = res.output;
|
|
759
|
+
}
|
|
760
|
+
} catch (hookErr) {
|
|
761
|
+
await runErrorHooks(merged.error, {
|
|
762
|
+
...base,
|
|
763
|
+
input: currentInput,
|
|
764
|
+
error: hookErr
|
|
765
|
+
});
|
|
766
|
+
return normalizeError(hookErr);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return normalizeResult(currentOutput);
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function createNeverAbortedSignal() {
|
|
773
|
+
try {
|
|
774
|
+
return new AbortController().signal;
|
|
775
|
+
} catch {
|
|
776
|
+
return {
|
|
777
|
+
aborted: false,
|
|
778
|
+
addEventListener() {
|
|
779
|
+
},
|
|
780
|
+
removeEventListener() {
|
|
781
|
+
},
|
|
782
|
+
dispatchEvent() {
|
|
783
|
+
return false;
|
|
784
|
+
},
|
|
785
|
+
onabort: null,
|
|
786
|
+
reason: void 0,
|
|
787
|
+
throwIfAborted() {
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/hooks/config.ts
|
|
794
|
+
var GLOBAL_KEY = "__simpleWebmcp_hooks";
|
|
795
|
+
function getStore() {
|
|
796
|
+
const g = globalThis;
|
|
797
|
+
if (!g[GLOBAL_KEY]) {
|
|
798
|
+
g[GLOBAL_KEY] = { hooks: {} };
|
|
799
|
+
}
|
|
800
|
+
return g[GLOBAL_KEY];
|
|
801
|
+
}
|
|
802
|
+
function getGlobalHooks() {
|
|
803
|
+
return getStore().hooks ?? {};
|
|
804
|
+
}
|
|
805
|
+
function configureWebMCP(opts) {
|
|
806
|
+
const store = getStore();
|
|
807
|
+
if (!opts.hooks) return;
|
|
808
|
+
if (opts.replace) {
|
|
809
|
+
store.hooks = normalizeHooks(opts.hooks);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
store.hooks = mergeHooks(store.hooks, opts.hooks);
|
|
813
|
+
}
|
|
814
|
+
function resetGlobalHooks() {
|
|
815
|
+
const store = getStore();
|
|
816
|
+
store.hooks = {};
|
|
817
|
+
}
|
|
818
|
+
function normalizeHooks(hooks) {
|
|
819
|
+
return {
|
|
820
|
+
before: hooks.before ? [...hooks.before] : void 0,
|
|
821
|
+
after: hooks.after ? [...hooks.after] : void 0,
|
|
822
|
+
error: hooks.error ? [...hooks.error] : void 0,
|
|
823
|
+
denied: hooks.denied ? [...hooks.denied] : void 0
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function mergeHooks(a, b) {
|
|
827
|
+
if (!a && !b) return {};
|
|
828
|
+
if (!a) return normalizeHooks(b);
|
|
829
|
+
if (!b) return a;
|
|
830
|
+
return {
|
|
831
|
+
before: [...a.before ?? [], ...b.before ?? []],
|
|
832
|
+
after: [...a.after ?? [], ...b.after ?? []],
|
|
833
|
+
error: [...a.error ?? [], ...b.error ?? []],
|
|
834
|
+
denied: [...a.denied ?? [], ...b.denied ?? []]
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function mergeHooksOrdered(opts) {
|
|
838
|
+
const globalHooks = opts.globalHooks ?? {};
|
|
839
|
+
const scopedHooks = opts.scopedHooks ?? {};
|
|
840
|
+
const toolHooks = opts.toolHooks ?? {};
|
|
841
|
+
const before = [
|
|
842
|
+
...globalHooks.before ?? [],
|
|
843
|
+
...scopedHooks.before ?? [],
|
|
844
|
+
...toolHooks.before ?? []
|
|
845
|
+
];
|
|
846
|
+
const after = [
|
|
847
|
+
...toolHooks.after ?? [],
|
|
848
|
+
...scopedHooks.after ?? [],
|
|
849
|
+
...globalHooks.after ?? []
|
|
850
|
+
];
|
|
851
|
+
const error = [
|
|
852
|
+
...toolHooks.error ?? [],
|
|
853
|
+
...scopedHooks.error ?? [],
|
|
854
|
+
...globalHooks.error ?? []
|
|
855
|
+
];
|
|
856
|
+
const denied = [
|
|
857
|
+
...toolHooks.denied ?? [],
|
|
858
|
+
...scopedHooks.denied ?? [],
|
|
859
|
+
...globalHooks.denied ?? []
|
|
860
|
+
];
|
|
861
|
+
const out = {};
|
|
862
|
+
if (before.length) out.before = before;
|
|
863
|
+
if (after.length) out.after = after;
|
|
864
|
+
if (error.length) out.error = error;
|
|
865
|
+
if (denied.length) out.denied = denied;
|
|
866
|
+
return out;
|
|
867
|
+
}
|
|
868
|
+
|
|
637
869
|
// src/webmcp.ts
|
|
638
870
|
function resolveScope(opts) {
|
|
639
871
|
if (opts?.scope) return opts.scope;
|
|
@@ -647,8 +879,24 @@ function webmcp(fn, options) {
|
|
|
647
879
|
const anyFn = fn;
|
|
648
880
|
if (anyFn.__webmcpBrand === true && anyFn.definition) {
|
|
649
881
|
if (!options || Object.keys(options).length === 0) return anyFn;
|
|
882
|
+
const prevOpts = anyFn.__webmcpOptions || {};
|
|
883
|
+
const mergedOpts = { ...prevOpts, ...options };
|
|
884
|
+
if (prevOpts.hooks || options?.hooks) {
|
|
885
|
+
const prevHooks = prevOpts.hooks ?? {};
|
|
886
|
+
const nextHooks = options.hooks ?? {};
|
|
887
|
+
mergedOpts.hooks = {
|
|
888
|
+
before: [...prevHooks.before ?? [], ...nextHooks.before ?? []],
|
|
889
|
+
after: [...prevHooks.after ?? [], ...nextHooks.after ?? []],
|
|
890
|
+
error: [...prevHooks.error ?? [], ...nextHooks.error ?? []],
|
|
891
|
+
denied: [...prevHooks.denied ?? [], ...nextHooks.denied ?? []]
|
|
892
|
+
};
|
|
893
|
+
for (const k of ["before", "after", "error", "denied"]) {
|
|
894
|
+
if (mergedOpts.hooks[k]?.length === 0) delete mergedOpts.hooks[k];
|
|
895
|
+
}
|
|
896
|
+
if (mergedOpts.hooks && Object.keys(mergedOpts.hooks).length === 0) delete mergedOpts.hooks;
|
|
897
|
+
}
|
|
650
898
|
const original = anyFn.__fn ?? fn;
|
|
651
|
-
return webmcp(original,
|
|
899
|
+
return webmcp(original, mergedOpts);
|
|
652
900
|
}
|
|
653
901
|
const name = options?.name ?? toSnakeCase(getFunctionName(fn));
|
|
654
902
|
if (!name) throw new ConfigurationError('Tool name could not be inferred \u2014 pass {name:"my_tool"}');
|
|
@@ -701,26 +949,43 @@ function webmcp(fn, options) {
|
|
|
701
949
|
let registrationPromise = null;
|
|
702
950
|
let unregisterFn = null;
|
|
703
951
|
let activeController = null;
|
|
704
|
-
const
|
|
952
|
+
const toolHooks = options?.hooks ? { ...options.hooks } : void 0;
|
|
705
953
|
const toolWrapper = wrapper;
|
|
706
954
|
toolWrapper.__webmcpBrand = true;
|
|
707
955
|
toolWrapper.__fn = fn;
|
|
708
956
|
toolWrapper.__webmcpOptions = options;
|
|
709
957
|
if (standard) toolWrapper.__standardSchema = standard;
|
|
710
958
|
if (outNorm.standard) toolWrapper.__outputStandardSchema = outNorm.standard;
|
|
959
|
+
if (toolHooks) toolWrapper.__hooks = toolHooks;
|
|
711
960
|
toolWrapper.tool = contract;
|
|
712
961
|
toolWrapper.definition = contract;
|
|
962
|
+
const hookedExec = createHookedExecute(fn, toolWrapper, contract, {
|
|
963
|
+
getHooks: () => {
|
|
964
|
+
const globalHooks = getGlobalHooks();
|
|
965
|
+
const scopeHooks = toolWrapper.__scopeHooks;
|
|
966
|
+
return mergeHooksOrdered({ globalHooks, scopedHooks: scopeHooks, toolHooks });
|
|
967
|
+
},
|
|
968
|
+
validate: standard ? (input) => {
|
|
969
|
+
const res = standard["~standard"].validate(input);
|
|
970
|
+
if ("issues" in res && res.issues && res.issues.length > 0) {
|
|
971
|
+
const msg = res.issues.map((i) => i.message).join("; ");
|
|
972
|
+
throw new ValidationError(`Validation failed: ${msg}`);
|
|
973
|
+
}
|
|
974
|
+
} : void 0
|
|
975
|
+
});
|
|
713
976
|
Object.defineProperty(toolWrapper, "status", {
|
|
714
977
|
get() {
|
|
715
978
|
return status;
|
|
716
979
|
},
|
|
717
|
-
enumerable: true
|
|
980
|
+
enumerable: true,
|
|
981
|
+
configurable: true
|
|
718
982
|
});
|
|
719
983
|
Object.defineProperty(toolWrapper, "registration", {
|
|
720
984
|
get() {
|
|
721
985
|
return registrationPromise;
|
|
722
986
|
},
|
|
723
|
-
enumerable: true
|
|
987
|
+
enumerable: true,
|
|
988
|
+
configurable: true
|
|
724
989
|
});
|
|
725
990
|
toolWrapper.isRegistered = () => status === "registered";
|
|
726
991
|
toolWrapper.register = async (opts) => {
|
|
@@ -731,14 +996,25 @@ function webmcp(fn, options) {
|
|
|
731
996
|
return () => {
|
|
732
997
|
};
|
|
733
998
|
}
|
|
999
|
+
if (!isWebMCPSupported()) {
|
|
1000
|
+
status = "unsupported";
|
|
1001
|
+
return () => {
|
|
1002
|
+
status = "unregistered";
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
734
1005
|
status = "registering";
|
|
735
1006
|
const controller = new AbortController();
|
|
736
1007
|
activeController = controller;
|
|
1008
|
+
toolWrapper.__activeSignal = controller.signal;
|
|
737
1009
|
if (opts?.signal) {
|
|
738
1010
|
if (opts.signal.aborted) {
|
|
739
1011
|
controller.abort();
|
|
740
1012
|
status = "unregistered";
|
|
741
1013
|
activeController = null;
|
|
1014
|
+
try {
|
|
1015
|
+
delete toolWrapper.__activeSignal;
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
742
1018
|
return () => {
|
|
743
1019
|
};
|
|
744
1020
|
}
|
|
@@ -753,16 +1029,22 @@ function webmcp(fn, options) {
|
|
|
753
1029
|
controller.signal.addEventListener("abort", () => {
|
|
754
1030
|
if (status !== "unregistered") status = "unregistered";
|
|
755
1031
|
activeController = null;
|
|
1032
|
+
try {
|
|
1033
|
+
delete toolWrapper.__activeSignal;
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
756
1036
|
}, { once: true });
|
|
757
1037
|
try {
|
|
758
1038
|
const unregister = await registry.register(contract, {
|
|
759
1039
|
signal: controller.signal,
|
|
760
|
-
execute:
|
|
1040
|
+
execute: hookedExec
|
|
761
1041
|
});
|
|
762
1042
|
unregisterFn = unregister;
|
|
763
1043
|
registrationPromise = Promise.resolve();
|
|
764
1044
|
if (controller.signal.aborted) {
|
|
765
1045
|
status = "unregistered";
|
|
1046
|
+
} else if (!isWebMCPSupported()) {
|
|
1047
|
+
status = "unsupported";
|
|
766
1048
|
} else {
|
|
767
1049
|
status = "registered";
|
|
768
1050
|
}
|
|
@@ -773,10 +1055,23 @@ function webmcp(fn, options) {
|
|
|
773
1055
|
}
|
|
774
1056
|
status = "unregistered";
|
|
775
1057
|
activeController = null;
|
|
1058
|
+
try {
|
|
1059
|
+
delete toolWrapper.__activeSignal;
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
776
1062
|
unregisterFn = null;
|
|
777
1063
|
};
|
|
778
1064
|
} catch (err) {
|
|
779
|
-
|
|
1065
|
+
if (err instanceof NotSupportedError || err?.code === "NOT_SUPPORTED" || err?.name === "NotSupportedError") {
|
|
1066
|
+
status = "unsupported";
|
|
1067
|
+
} else {
|
|
1068
|
+
status = "error";
|
|
1069
|
+
}
|
|
1070
|
+
activeController = null;
|
|
1071
|
+
try {
|
|
1072
|
+
delete toolWrapper.__activeSignal;
|
|
1073
|
+
} catch {
|
|
1074
|
+
}
|
|
780
1075
|
registrationPromise = Promise.reject(err);
|
|
781
1076
|
if (err instanceof SimpleWebMCPError) throw err;
|
|
782
1077
|
throw err;
|
|
@@ -795,6 +1090,10 @@ function webmcp(fn, options) {
|
|
|
795
1090
|
}
|
|
796
1091
|
status = "unregistered";
|
|
797
1092
|
activeController = null;
|
|
1093
|
+
try {
|
|
1094
|
+
delete toolWrapper.__activeSignal;
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
798
1097
|
unregisterFn = null;
|
|
799
1098
|
registrationPromise = null;
|
|
800
1099
|
};
|
|
@@ -813,6 +1112,9 @@ function webmcp(fn, options) {
|
|
|
813
1112
|
webmcp.global = function global(fn, opts) {
|
|
814
1113
|
return webmcp(fn, { ...opts, global: true });
|
|
815
1114
|
};
|
|
1115
|
+
webmcp.configure = configureWebMCP;
|
|
1116
|
+
webmcp.getGlobalHooks = getGlobalHooks;
|
|
1117
|
+
webmcp.resetGlobalHooks = resetGlobalHooks;
|
|
816
1118
|
webmcp.isWebMCPTool = function isWebMCPTool(v) {
|
|
817
1119
|
return !!v?.__webmcpBrand;
|
|
818
1120
|
};
|
|
@@ -821,6 +1123,6 @@ var webmcp_default = webmcp;
|
|
|
821
1123
|
// src/constants.ts
|
|
822
1124
|
var SDK_VERSION = "0.1.0";
|
|
823
1125
|
|
|
824
|
-
export { ConfigurationError, NotAllowedError, NotSupportedError, RegistrationError, SDK_VERSION, SimpleWebMCPError, ValidationError, applyFieldsPatch, buildFinalInputSchema, webmcp_default as default, getModelContext, getRegistry, inferRuntime, isWebMCPSupported, normalizeError, normalizeResult, registry, toSnakeCase, webmcp, wrapExecute };
|
|
1126
|
+
export { ConfigurationError, NotAllowedError, NotSupportedError, RegistrationError, SDK_VERSION, SimpleWebMCPError, ValidationError, applyFieldsPatch, buildFinalInputSchema, configureWebMCP, createHookedExecute, webmcp_default as default, genInvocationId, getGlobalHooks, getModelContext, getRegistry, inferRuntime, isWebMCPSupported, mergeHooks, mergeHooksOrdered, normalizeError, normalizeResult, registry, resetGlobalHooks, toSnakeCase, webmcp, wrapExecute };
|
|
825
1127
|
//# sourceMappingURL=index.js.map
|
|
826
1128
|
//# sourceMappingURL=index.js.map
|