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/index.d.ts CHANGED
@@ -1,8 +1,65 @@
1
- import { W as WebMCPOptions, a as WebMCPTool, M as ModelContextLike, R as RegistrationStatus, T as ToolContract, J as JsonSchema, F as FieldDefOrSchema, S as StandardSchemaV1 } from './types-D-cwSfEU.js';
2
- export { b as FieldDef, c as WebMCPAnnotations } from './types-D-cwSfEU.js';
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, RegistrationStatus, SDK_VERSION, SimpleWebMCPError, StandardSchemaV1, ToolContract, ValidationError, WebMCPOptions, WebMCPTool, applyFieldsPatch, buildFinalInputSchema, webmcp as default, getModelContext, getRegistry, inferRuntime, isWebMCPSupported, normalizeError, normalizeResult, registry, toSnakeCase, webmcp, wrapExecute };
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
- const controller2 = new AbortController();
463
- const entry2 = {
464
- name,
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 registry = new Registry();
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 = err instanceof Error ? err.message : typeof err === "string" ? 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
+ }
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, { ...anyFn.__webmcpOptions || {}, ...options });
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 wrappedExec = wrapExecute(fn);
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: wrappedExec
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
- status = "error";
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