@happyvertical/smrt-web 0.46.0 → 0.47.1

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.d.ts CHANGED
@@ -565,6 +565,16 @@ export declare function registerDurableResource(namespace: string, resource: Dur
565
565
  * `compileViewIntentToolSpec` from `intent.target`. No author-supplied code
566
566
  * runs, and the only thing it can do is dispatch one browser registry
567
567
  * command — the runtime half of the no-REST invariant.
568
+ *
569
+ * The intent's derived tool name is claimed against the document-global
570
+ * tool-name lock (#2613), so an id whose flattened name collides with a
571
+ * generated model tool or a fixed UI tool throws
572
+ * {@link WebMcpToolNameCollisionError} here rather than silently losing the
573
+ * tool at the host. `defineIntent`'s own intent-vs-intent check cannot see
574
+ * either, because both depend on a runtime `namespace` / `prefix` the
575
+ * declaration never sees.
576
+ *
577
+ * @throws {WebMcpToolNameCollisionError} when the derived name is already held.
568
578
  */
569
579
  export declare function registerViewIntent(intent: ViewIntent, binding: ViewIntentBinding, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
570
580
 
@@ -578,9 +588,19 @@ export declare function registerViewIntent(intent: ViewIntent, binding: ViewInte
578
588
  * `maxTools` are out of scope for a bespoke tool; see
579
589
  * {@link RegisterWebMcpBespokeToolOptions}.
580
590
  *
591
+ * The tool's name is also claimed against the
592
+ * document-global tool-name lock before the browser sees it. A name a
593
+ * generated model tool, a fixed UI tool, or another live bespoke tool /
594
+ * intent already holds throws {@link WebMcpToolNameCollisionError}
595
+ * SYNCHRONOUSLY, naming the colliding name and the owner that holds it.
596
+ * Previously the host accepted the call and silently dropped the losing
597
+ * registration. Disposing releases the name, so register/dispose/re-register
598
+ * under one name still succeeds.
599
+ *
581
600
  * @returns a disposer that deregisters the tool this call registered (a
582
601
  * no-op double-call). On a browser without WebMCP, or when policy excludes
583
602
  * the tool's effect, the call is a no-op and the disposer is inert.
603
+ * @throws {WebMcpToolNameCollisionError} when the name is already held.
584
604
  */
585
605
  export declare function registerWebMcpBespokeTool(spec: WebMcpBespokeToolSpec, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
586
606
 
@@ -593,13 +613,36 @@ export declare interface RegisterWebMcpBespokeToolOptions {
593
613
  * an unrelated generated tool set fail to register.
594
614
  */
595
615
  effects?: readonly WebMcpToolEffect[];
616
+ /**
617
+ * Which path this registration belongs to, used ONLY to label its
618
+ * tool-name reservation (#2613) so a later collision names the right
619
+ * source. Defaults to `bespoke`.
620
+ *
621
+ * A UI-framework binding that compiles a declared view intent itself and
622
+ * registers it here — rather than through {@link registerViewIntent} —
623
+ * must pass `'intent'`. `useViewIntent` in `@happyvertical/smrt-svelte`
624
+ * does exactly that, because reusing `useWebMcpTool`'s single WebMCP
625
+ * lifecycle is a documented invariant of that package; without this it
626
+ * would be the only shipped intent path and every intent collision would
627
+ * blame a `useWebMcpTool` call that does not exist.
628
+ *
629
+ * Purely a diagnostic label: it grants no capability, narrows no policy,
630
+ * and changes nothing about how the tool registers or executes.
631
+ */
632
+ owner?: Extract<WebMcpToolNameOwner, 'intent' | 'bespoke'>;
596
633
  }
597
634
 
598
635
  /**
599
636
  * Register every collection's generated tool descriptors with WebMCP.
600
637
  *
638
+ * Every selected tool name is claimed against the document-global tool-name
639
+ * lock (#2613) before the first browser registration, so a collision with a
640
+ * UI-layer or bespoke tool throws {@link WebMcpToolNameCollisionError} with
641
+ * the offending name and its current owner instead of reaching the host.
642
+ *
601
643
  * @returns a disposer that deregisters all tools this call registered. On a
602
644
  * browser without WebMCP the call is a no-op and the disposer is inert.
645
+ * @throws {WebMcpToolNameCollisionError} when a selected name is already held.
603
646
  */
604
647
  export declare function registerWebMcpTools(definitions: readonly WebMcpRegistrationDefinition[], options?: RegisterWebMcpToolsOptions): WebMcpRegistrationDisposer;
605
648
 
@@ -630,6 +673,34 @@ export declare interface RegisterWebMcpToolsOptions extends WebMcpExposurePolicy
630
673
  filterTool?: (definition: WebMcpToolDefinition) => boolean;
631
674
  }
632
675
 
676
+ /**
677
+ * Reserve `names` for `owner` against the document's WebMCP registry.
678
+ *
679
+ * Reservation is ALL-OR-NOTHING: if any name is already held — by this path
680
+ * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}
681
+ * naming the colliding name and its current owner is thrown synchronously,
682
+ * before any tool reaches `document.modelContext`. Duplicates within `names`
683
+ * itself collide the same way.
684
+ *
685
+ * Off-WebMCP (no document) the call is a no-op returning an inert
686
+ * reservation, matching every registrar's own no-op guarantee.
687
+ *
688
+ * A reservation releases only the names it took: `release()` never drops a
689
+ * name a later reservation has since acquired, so a dispose that lands after
690
+ * a same-name re-registration cannot revoke the new holder's claim.
691
+ */
692
+ export declare function reserveWebMcpToolNames(names: readonly string[], owner: WebMcpToolNameOwner, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameReservation;
693
+
694
+ declare interface ReserveWebMcpToolNamesOptions {
695
+ /**
696
+ * The document whose `modelContext` the names are registered against.
697
+ * Defaults to `globalThis.document`. Registrars that accept an injectable
698
+ * document (tests, non-window hosts) must pass the SAME object they read
699
+ * `modelContext` from, or their reservations land in a different table.
700
+ */
701
+ document?: unknown;
702
+ }
703
+
633
704
  /**
634
705
  * Run the `wrapMutation` hook across `capabilities` in array order for one
635
706
  * mutation, short-circuiting on the FIRST capability that returns `{ handled:
@@ -1667,6 +1738,89 @@ export declare interface WebMcpToolDefinition extends WebToolDescriptor {
1667
1738
  */
1668
1739
  export declare type WebMcpToolEffect = 'read' | 'write' | 'destructive';
1669
1740
 
1741
+ /**
1742
+ * Thrown synchronously when a name is already held. Carries the colliding
1743
+ * name and the owner that holds it so a host can report both without parsing
1744
+ * the message.
1745
+ */
1746
+ export declare class WebMcpToolNameCollisionError extends Error {
1747
+ readonly toolName: string;
1748
+ /** The path that already holds {@link toolName}. */
1749
+ readonly owner: WebMcpToolNameOwner;
1750
+ /** The path that tried to take it. */
1751
+ readonly requestedBy: WebMcpToolNameOwner;
1752
+ constructor(toolName: string,
1753
+ /** The path that already holds {@link toolName}. */
1754
+ owner: WebMcpToolNameOwner,
1755
+ /** The path that tried to take it. */
1756
+ requestedBy: WebMcpToolNameOwner);
1757
+ }
1758
+
1759
+ /**
1760
+ * Document-global WebMCP tool-name lock (#2613).
1761
+ *
1762
+ * A browser tool name is derived independently on three paths that all end at
1763
+ * the same `document.modelContext`:
1764
+ *
1765
+ * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by
1766
+ * `registerWebMcpTools` from the exposure policy;
1767
+ * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a
1768
+ * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);
1769
+ * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written
1770
+ * `useWebMcpTool` tools, whose names come from the declaration `id` or the
1771
+ * spec `name`.
1772
+ *
1773
+ * None of the three can see the others at declaration time: a namespace and a
1774
+ * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`
1775
+ * rejects duplicates WITHIN its own prospective set, but nothing checked
1776
+ * ACROSS the paths, so a collision reached the host — which rejects the later
1777
+ * registration and leaves the tool silently absent.
1778
+ *
1779
+ * This module is the shared reservation table those paths coordinate through.
1780
+ * It is an AVAILABILITY guard, not a trust boundary: every path stays
1781
+ * fail-closed and browser-only regardless, and a collision loses a tool rather
1782
+ * than granting one. The authenticated REST surface remains the auth, tenant,
1783
+ * and field-write boundary.
1784
+ *
1785
+ * ## Why the table lives on the document, not in this module
1786
+ *
1787
+ * The lock must be shared by every registrar reachable from one page, which
1788
+ * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly
1789
+ * more than one copy of this module — a second bundle chunk, a duplicated
1790
+ * dependency, or an HMR reload that replaces module state while the host
1791
+ * registry keeps every tool it already accepted. Module-level state loses the
1792
+ * reservations in all of those cases, and a lost reservation is worse than no
1793
+ * lock: it lets a duplicate through, or (on the release side) strands a name
1794
+ * forever. The table is therefore stored on the `document` object itself under
1795
+ * a cross-realm `Symbol.for` key, which every copy of this module resolves to
1796
+ * the same slot.
1797
+ *
1798
+ * The table is additionally stamped with the `modelContext` it was built for.
1799
+ * When a host installs a NEW model context on the same document — a fresh
1800
+ * origin-trial registry, or a test harness swapping the double — every tool
1801
+ * the old registry held is gone, so the table resets rather than stranding
1802
+ * those names.
1803
+ *
1804
+ * This module is dependency-free by design (see the package's dependency-DAG
1805
+ * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`
1806
+ * entry so a UI layer can reserve its fixed tool names without pulling in the
1807
+ * client-data engine.
1808
+ */
1809
+ /** Which registration path holds a reserved WebMCP tool name. */
1810
+ export declare type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';
1811
+
1812
+ /**
1813
+ * The owner currently holding `name` on this document, or undefined. For
1814
+ * diagnostics and tests; registration paths use
1815
+ * {@link reserveWebMcpToolNames}, which is atomic.
1816
+ */
1817
+ export declare function webMcpToolNameOwner(name: string, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameOwner | undefined;
1818
+
1819
+ /** Releases every name one reservation holds. Idempotent. */
1820
+ export declare interface WebMcpToolNameReservation {
1821
+ release(): void;
1822
+ }
1823
+
1670
1824
  /**
1671
1825
  * One generated collection definition: everything needed to construct a client
1672
1826
  * collection over the generated REST surface. The `_row` property is a phantom
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import { A as MAX_SMRT_WEB_DATA_QUERY_OFFSET, C as offlineOutbox, D as MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, E as wipeDurableStore, F as MAX_SMRT_WEB_DATA_QUERY_WARNINGS, I as executeSmrtWebDataQuery, L as normalizeSmrtWebDataQueryResult, M as MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, N as MAX_SMRT_WEB_DATA_QUERY_ROWS, O as MAX_SMRT_WEB_DATA_QUERY_FACETS, P as MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, R as runWrapMutation, S as getOutboxHandle, T as registerDurableResource, _ as createSmrtWebEventSubscriber, a as createSmrtWebClient, b as DEFAULT_PERSIST_DEBOUNCE_MS, c as newLocalId, d as unwrapListResult, f as validateSmrtWebClient, g as createUpdateState, h as registerWebMcpTools, i as createSmrtCollection, j as MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, k as MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, l as throwIfSmrtWebError, m as registerWebMcpBespokeTool, n as buildListQuery, o as getEngineCollection, p as registerViewIntent, r as createDefinitionFetchers, s as invalidateSmrtWebCollections, t as SmrtWebRequestError, u as unwrapItemResult, v as liveInvalidation, w as durableStoreNamespace, x as persistCollection, y as createSmrtWebQuery } from "./chunks/src-D1ZtD6Bt.js";
1
+ import { A as MAX_SMRT_WEB_DATA_QUERY_OFFSET, C as offlineOutbox, D as MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, E as wipeDurableStore, F as MAX_SMRT_WEB_DATA_QUERY_WARNINGS, I as executeSmrtWebDataQuery, L as normalizeSmrtWebDataQueryResult, M as MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, N as MAX_SMRT_WEB_DATA_QUERY_ROWS, O as MAX_SMRT_WEB_DATA_QUERY_FACETS, P as MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, R as runWrapMutation, S as getOutboxHandle, T as registerDurableResource, _ as createSmrtWebEventSubscriber, a as createSmrtWebClient, b as DEFAULT_PERSIST_DEBOUNCE_MS, c as newLocalId, d as unwrapListResult, f as validateSmrtWebClient, g as createUpdateState, h as registerWebMcpTools, i as createSmrtCollection, j as MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, k as MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, l as throwIfSmrtWebError, m as registerWebMcpBespokeTool, n as buildListQuery, o as getEngineCollection, p as registerViewIntent, r as createDefinitionFetchers, s as invalidateSmrtWebCollections, t as SmrtWebRequestError, u as unwrapItemResult, v as liveInvalidation, w as durableStoreNamespace, x as persistCollection, y as createSmrtWebQuery } from "./chunks/src-DHH8ptH8.js";
2
2
  import { n as compileViewIntentToolSpec, o as viewIntentToolName } from "./chunks/intents-BUTyN7YQ.js";
3
- export { DEFAULT_PERSIST_DEBOUNCE_MS, MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, MAX_SMRT_WEB_DATA_QUERY_FACETS, MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, MAX_SMRT_WEB_DATA_QUERY_OFFSET, MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, MAX_SMRT_WEB_DATA_QUERY_ROWS, MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, MAX_SMRT_WEB_DATA_QUERY_WARNINGS, SmrtWebRequestError, buildListQuery, compileViewIntentToolSpec, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createSmrtWebQuery, createUpdateState, durableStoreNamespace, executeSmrtWebDataQuery, getEngineCollection, getOutboxHandle, invalidateSmrtWebCollections, liveInvalidation, newLocalId, normalizeSmrtWebDataQueryResult, offlineOutbox, persistCollection, registerDurableResource, registerViewIntent, registerWebMcpBespokeTool, registerWebMcpTools, runWrapMutation, throwIfSmrtWebError, unwrapItemResult, unwrapListResult, validateSmrtWebClient, viewIntentToolName, wipeDurableStore };
3
+ import { WebMcpToolNameCollisionError, reserveWebMcpToolNames, webMcpToolNameOwner } from "./webmcp-tool-names.js";
4
+ export { DEFAULT_PERSIST_DEBOUNCE_MS, MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, MAX_SMRT_WEB_DATA_QUERY_FACETS, MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, MAX_SMRT_WEB_DATA_QUERY_OFFSET, MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, MAX_SMRT_WEB_DATA_QUERY_ROWS, MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, MAX_SMRT_WEB_DATA_QUERY_WARNINGS, SmrtWebRequestError, WebMcpToolNameCollisionError, buildListQuery, compileViewIntentToolSpec, createDefinitionFetchers, createSmrtCollection, createSmrtWebClient, createSmrtWebEventSubscriber, createSmrtWebQuery, createUpdateState, durableStoreNamespace, executeSmrtWebDataQuery, getEngineCollection, getOutboxHandle, invalidateSmrtWebCollections, liveInvalidation, newLocalId, normalizeSmrtWebDataQueryResult, offlineOutbox, persistCollection, registerDurableResource, registerViewIntent, registerWebMcpBespokeTool, registerWebMcpTools, reserveWebMcpToolNames, runWrapMutation, throwIfSmrtWebError, unwrapItemResult, unwrapListResult, validateSmrtWebClient, viewIntentToolName, webMcpToolNameOwner, wipeDurableStore };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Reserve `names` for `owner` against the document's WebMCP registry.
3
+ *
4
+ * Reservation is ALL-OR-NOTHING: if any name is already held — by this path
5
+ * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}
6
+ * naming the colliding name and its current owner is thrown synchronously,
7
+ * before any tool reaches `document.modelContext`. Duplicates within `names`
8
+ * itself collide the same way.
9
+ *
10
+ * Off-WebMCP (no document) the call is a no-op returning an inert
11
+ * reservation, matching every registrar's own no-op guarantee.
12
+ *
13
+ * A reservation releases only the names it took: `release()` never drops a
14
+ * name a later reservation has since acquired, so a dispose that lands after
15
+ * a same-name re-registration cannot revoke the new holder's claim.
16
+ */
17
+ export declare function reserveWebMcpToolNames(names: readonly string[], owner: WebMcpToolNameOwner, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameReservation;
18
+
19
+ export declare interface ReserveWebMcpToolNamesOptions {
20
+ /**
21
+ * The document whose `modelContext` the names are registered against.
22
+ * Defaults to `globalThis.document`. Registrars that accept an injectable
23
+ * document (tests, non-window hosts) must pass the SAME object they read
24
+ * `modelContext` from, or their reservations land in a different table.
25
+ */
26
+ document?: unknown;
27
+ }
28
+
29
+ /**
30
+ * Thrown synchronously when a name is already held. Carries the colliding
31
+ * name and the owner that holds it so a host can report both without parsing
32
+ * the message.
33
+ */
34
+ export declare class WebMcpToolNameCollisionError extends Error {
35
+ readonly toolName: string;
36
+ /** The path that already holds {@link toolName}. */
37
+ readonly owner: WebMcpToolNameOwner;
38
+ /** The path that tried to take it. */
39
+ readonly requestedBy: WebMcpToolNameOwner;
40
+ constructor(toolName: string,
41
+ /** The path that already holds {@link toolName}. */
42
+ owner: WebMcpToolNameOwner,
43
+ /** The path that tried to take it. */
44
+ requestedBy: WebMcpToolNameOwner);
45
+ }
46
+
47
+ /**
48
+ * Document-global WebMCP tool-name lock (#2613).
49
+ *
50
+ * A browser tool name is derived independently on three paths that all end at
51
+ * the same `document.modelContext`:
52
+ *
53
+ * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by
54
+ * `registerWebMcpTools` from the exposure policy;
55
+ * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a
56
+ * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);
57
+ * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written
58
+ * `useWebMcpTool` tools, whose names come from the declaration `id` or the
59
+ * spec `name`.
60
+ *
61
+ * None of the three can see the others at declaration time: a namespace and a
62
+ * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`
63
+ * rejects duplicates WITHIN its own prospective set, but nothing checked
64
+ * ACROSS the paths, so a collision reached the host — which rejects the later
65
+ * registration and leaves the tool silently absent.
66
+ *
67
+ * This module is the shared reservation table those paths coordinate through.
68
+ * It is an AVAILABILITY guard, not a trust boundary: every path stays
69
+ * fail-closed and browser-only regardless, and a collision loses a tool rather
70
+ * than granting one. The authenticated REST surface remains the auth, tenant,
71
+ * and field-write boundary.
72
+ *
73
+ * ## Why the table lives on the document, not in this module
74
+ *
75
+ * The lock must be shared by every registrar reachable from one page, which
76
+ * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly
77
+ * more than one copy of this module — a second bundle chunk, a duplicated
78
+ * dependency, or an HMR reload that replaces module state while the host
79
+ * registry keeps every tool it already accepted. Module-level state loses the
80
+ * reservations in all of those cases, and a lost reservation is worse than no
81
+ * lock: it lets a duplicate through, or (on the release side) strands a name
82
+ * forever. The table is therefore stored on the `document` object itself under
83
+ * a cross-realm `Symbol.for` key, which every copy of this module resolves to
84
+ * the same slot.
85
+ *
86
+ * The table is additionally stamped with the `modelContext` it was built for.
87
+ * When a host installs a NEW model context on the same document — a fresh
88
+ * origin-trial registry, or a test harness swapping the double — every tool
89
+ * the old registry held is gone, so the table resets rather than stranding
90
+ * those names.
91
+ *
92
+ * This module is dependency-free by design (see the package's dependency-DAG
93
+ * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`
94
+ * entry so a UI layer can reserve its fixed tool names without pulling in the
95
+ * client-data engine.
96
+ */
97
+ /** Which registration path holds a reserved WebMCP tool name. */
98
+ export declare type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';
99
+
100
+ /**
101
+ * The owner currently holding `name` on this document, or undefined. For
102
+ * diagnostics and tests; registration paths use
103
+ * {@link reserveWebMcpToolNames}, which is atomic.
104
+ */
105
+ export declare function webMcpToolNameOwner(name: string, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameOwner | undefined;
106
+
107
+ /** Releases every name one reservation holds. Idempotent. */
108
+ export declare interface WebMcpToolNameReservation {
109
+ release(): void;
110
+ }
111
+
112
+ export { }
@@ -0,0 +1,73 @@
1
+ //#region src/webmcp-tool-names.ts
2
+ var WebMcpToolNameCollisionError = class extends Error {
3
+ constructor(toolName, owner, requestedBy) {
4
+ super(`WebMCP tool name "${toolName}" is already registered by the ${owner} path; the ${requestedBy} path cannot register it. Rename the ${requestedBy} tool, or give the generated tools a webmcp namespace / the UI tools a different prefix.`);
5
+ this.toolName = toolName;
6
+ this.owner = owner;
7
+ this.requestedBy = requestedBy;
8
+ this.name = "WebMcpToolNameCollisionError";
9
+ }
10
+ toolName;
11
+ owner;
12
+ requestedBy;
13
+ };
14
+ var TABLE_KEY = /* @__PURE__ */ Symbol.for("@happyvertical/smrt-web:webmcp-tool-names");
15
+ var INERT_RESERVATION = { release: () => {} };
16
+ function resolveDocument(documentLike) {
17
+ const doc = documentLike ?? globalThis.document ?? void 0;
18
+ return doc && typeof doc === "object" ? doc : void 0;
19
+ }
20
+ function tableFor(doc) {
21
+ const slot = doc;
22
+ const modelContext = doc.modelContext;
23
+ const existing = slot[TABLE_KEY];
24
+ if (existing && existing.modelContext === modelContext) return existing;
25
+ const table = {
26
+ modelContext,
27
+ names: /* @__PURE__ */ new Map()
28
+ };
29
+ Object.defineProperty(doc, TABLE_KEY, {
30
+ value: table,
31
+ configurable: true,
32
+ enumerable: false,
33
+ writable: true
34
+ });
35
+ return table;
36
+ }
37
+ function reserveWebMcpToolNames(names, owner, options = {}) {
38
+ const doc = resolveDocument(options.document);
39
+ if (!doc) return INERT_RESERVATION;
40
+ const table = tableFor(doc);
41
+ const token = {};
42
+ const taken = [];
43
+ const drop = () => {
44
+ for (const name of taken) if (table.names.get(name)?.token === token) table.names.delete(name);
45
+ };
46
+ for (const name of names) {
47
+ const held = table.names.get(name);
48
+ if (held) {
49
+ drop();
50
+ throw new WebMcpToolNameCollisionError(name, held.owner, owner);
51
+ }
52
+ table.names.set(name, {
53
+ owner,
54
+ token
55
+ });
56
+ taken.push(name);
57
+ }
58
+ let released = false;
59
+ return { release: () => {
60
+ if (released) return;
61
+ released = true;
62
+ drop();
63
+ } };
64
+ }
65
+ function webMcpToolNameOwner(name, options = {}) {
66
+ const doc = resolveDocument(options.document);
67
+ if (!doc) return void 0;
68
+ return tableFor(doc).names.get(name)?.owner;
69
+ }
70
+ //#endregion
71
+ export { WebMcpToolNameCollisionError, reserveWebMcpToolNames, webMcpToolNameOwner };
72
+
73
+ //# sourceMappingURL=webmcp-tool-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-tool-names.js","names":[],"sources":["../src/webmcp-tool-names.ts"],"sourcesContent":["/**\n * Document-global WebMCP tool-name lock (#2613).\n *\n * A browser tool name is derived independently on three paths that all end at\n * the same `document.modelContext`:\n *\n * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by\n * `registerWebMcpTools` from the exposure policy;\n * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a\n * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);\n * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written\n * `useWebMcpTool` tools, whose names come from the declaration `id` or the\n * spec `name`.\n *\n * None of the three can see the others at declaration time: a namespace and a\n * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`\n * rejects duplicates WITHIN its own prospective set, but nothing checked\n * ACROSS the paths, so a collision reached the host — which rejects the later\n * registration and leaves the tool silently absent.\n *\n * This module is the shared reservation table those paths coordinate through.\n * It is an AVAILABILITY guard, not a trust boundary: every path stays\n * fail-closed and browser-only regardless, and a collision loses a tool rather\n * than granting one. The authenticated REST surface remains the auth, tenant,\n * and field-write boundary.\n *\n * ## Why the table lives on the document, not in this module\n *\n * The lock must be shared by every registrar reachable from one page, which\n * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly\n * more than one copy of this module — a second bundle chunk, a duplicated\n * dependency, or an HMR reload that replaces module state while the host\n * registry keeps every tool it already accepted. Module-level state loses the\n * reservations in all of those cases, and a lost reservation is worse than no\n * lock: it lets a duplicate through, or (on the release side) strands a name\n * forever. The table is therefore stored on the `document` object itself under\n * a cross-realm `Symbol.for` key, which every copy of this module resolves to\n * the same slot.\n *\n * The table is additionally stamped with the `modelContext` it was built for.\n * When a host installs a NEW model context on the same document — a fresh\n * origin-trial registry, or a test harness swapping the double — every tool\n * the old registry held is gone, so the table resets rather than stranding\n * those names.\n *\n * This module is dependency-free by design (see the package's dependency-DAG\n * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`\n * entry so a UI layer can reserve its fixed tool names without pulling in the\n * client-data engine.\n */\n\n/** Which registration path holds a reserved WebMCP tool name. */\nexport type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';\n\n/**\n * Thrown synchronously when a name is already held. Carries the colliding\n * name and the owner that holds it so a host can report both without parsing\n * the message.\n */\nexport class WebMcpToolNameCollisionError extends Error {\n constructor(\n readonly toolName: string,\n /** The path that already holds {@link toolName}. */\n readonly owner: WebMcpToolNameOwner,\n /** The path that tried to take it. */\n readonly requestedBy: WebMcpToolNameOwner,\n ) {\n super(\n `WebMCP tool name \"${toolName}\" is already registered by the ${owner} path; the ${requestedBy} path cannot register it. Rename the ${requestedBy} tool, or give the generated tools a webmcp namespace / the UI tools a different prefix.`,\n );\n this.name = 'WebMcpToolNameCollisionError';\n }\n}\n\n/** Releases every name one reservation holds. Idempotent. */\nexport interface WebMcpToolNameReservation {\n release(): void;\n}\n\nexport interface ReserveWebMcpToolNamesOptions {\n /**\n * The document whose `modelContext` the names are registered against.\n * Defaults to `globalThis.document`. Registrars that accept an injectable\n * document (tests, non-window hosts) must pass the SAME object they read\n * `modelContext` from, or their reservations land in a different table.\n */\n document?: unknown;\n}\n\n/** A held name plus the token identifying the reservation that holds it. */\ninterface HeldName {\n owner: WebMcpToolNameOwner;\n token: object;\n}\n\ninterface ToolNameTable {\n /** The model context these reservations were taken against. */\n modelContext: unknown;\n names: Map<string, HeldName>;\n}\n\nconst TABLE_KEY = Symbol.for('@happyvertical/smrt-web:webmcp-tool-names');\n\nconst INERT_RESERVATION: WebMcpToolNameReservation = { release: () => {} };\n\nfunction resolveDocument(documentLike: unknown): object | undefined {\n const doc =\n documentLike ??\n (globalThis as { document?: unknown }).document ??\n undefined;\n return doc && typeof doc === 'object' ? (doc as object) : undefined;\n}\n\nfunction tableFor(doc: object): ToolNameTable {\n const slot = doc as Record<symbol, ToolNameTable | undefined>;\n const modelContext = (doc as { modelContext?: unknown }).modelContext;\n const existing = slot[TABLE_KEY];\n // A replaced model context means the host registry that held the old\n // reservations no longer exists; start clean rather than stranding names.\n if (existing && existing.modelContext === modelContext) return existing;\n const table: ToolNameTable = { modelContext, names: new Map() };\n Object.defineProperty(doc, TABLE_KEY, {\n value: table,\n configurable: true,\n enumerable: false,\n writable: true,\n });\n return table;\n}\n\n/**\n * Reserve `names` for `owner` against the document's WebMCP registry.\n *\n * Reservation is ALL-OR-NOTHING: if any name is already held — by this path\n * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}\n * naming the colliding name and its current owner is thrown synchronously,\n * before any tool reaches `document.modelContext`. Duplicates within `names`\n * itself collide the same way.\n *\n * Off-WebMCP (no document) the call is a no-op returning an inert\n * reservation, matching every registrar's own no-op guarantee.\n *\n * A reservation releases only the names it took: `release()` never drops a\n * name a later reservation has since acquired, so a dispose that lands after\n * a same-name re-registration cannot revoke the new holder's claim.\n */\nexport function reserveWebMcpToolNames(\n names: readonly string[],\n owner: WebMcpToolNameOwner,\n options: ReserveWebMcpToolNamesOptions = {},\n): WebMcpToolNameReservation {\n const doc = resolveDocument(options.document);\n if (!doc) return INERT_RESERVATION;\n\n const table = tableFor(doc);\n const token = {};\n const taken: string[] = [];\n const drop = (): void => {\n for (const name of taken) {\n // Only ever drop a name this reservation still holds. Together with the\n // `released` latch below, a stale handle can neither release twice nor\n // revoke a claim a later reservation has since taken under the same name.\n if (table.names.get(name)?.token === token) table.names.delete(name);\n }\n };\n\n for (const name of names) {\n const held = table.names.get(name);\n if (held) {\n // All-or-nothing: give back everything taken so far, so a rejected\n // reservation leaves no name stranded and the caller can retry with a\n // corrected set.\n drop();\n throw new WebMcpToolNameCollisionError(name, held.owner, owner);\n }\n table.names.set(name, { owner, token });\n taken.push(name);\n }\n\n let released = false;\n return {\n release: () => {\n if (released) return;\n released = true;\n drop();\n },\n };\n}\n\n/**\n * The owner currently holding `name` on this document, or undefined. For\n * diagnostics and tests; registration paths use\n * {@link reserveWebMcpToolNames}, which is atomic.\n */\nexport function webMcpToolNameOwner(\n name: string,\n options: ReserveWebMcpToolNamesOptions = {},\n): WebMcpToolNameOwner | undefined {\n const doc = resolveDocument(options.document);\n if (!doc) return undefined;\n return tableFor(doc).names.get(name)?.owner;\n}\n"],"mappings":";AA2DO,IAAM,+BAAN,cAA2C,MAAM;CACtD,YACW,UAEA,OAEA,aACT;EACA,MACE,qBAAqB,SAAQ,iCAAkC,MAAK,aAAc,YAAW,uCAAwC,YAAW,yFAClJ;EARS,KAAA,WAAA;EAEA,KAAA,QAAA;EAEA,KAAA,cAAA;EAKT,KAAK,OAAO;CACd;CAVW;CAEA;CAEA;AAOb;AA6BA,IAAM,YAAY,uBAAO,IAAI,2CAA2C;AAExE,IAAM,oBAA+C,EAAE,eAAe,CAAC,EAAE;AAEzE,SAAS,gBAAgB,cAA2C;CAClE,MAAM,MACJ,gBACC,WAAsC,YACvC,KAAA;CACF,OAAO,OAAO,OAAO,QAAQ,WAAY,MAAiB,KAAA;AAC5D;AAEA,SAAS,SAAS,KAA4B;CAC5C,MAAM,OAAO;CACb,MAAM,eAAgB,IAAmC;CACzD,MAAM,WAAW,KAAK;CAGtB,IAAI,YAAY,SAAS,iBAAiB,cAAc,OAAO;CAC/D,MAAM,QAAuB;EAAE;EAAc,uBAAO,IAAI,IAAI;CAAE;CAC9D,OAAO,eAAe,KAAK,WAAW;EACpC,OAAO;EACP,cAAc;EACd,YAAY;EACZ,UAAU;CACZ,CAAC;CACD,OAAO;AACT;AAkBO,SAAS,uBACd,OACA,OACA,UAAyC,CAAC,GACf;CAC3B,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;CAC5C,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,QAAQ,SAAS,GAAG;CAC1B,MAAM,QAAQ,CAAC;CACf,MAAM,QAAkB,CAAC;CACzB,MAAM,aAAmB;EACvB,KAAA,MAAW,QAAQ,OAIjB,IAAI,MAAM,MAAM,IAAI,IAAI,CAAA,EAAG,UAAU,OAAO,MAAM,MAAM,OAAO,IAAI;CAEvE;CAEA,KAAA,MAAW,QAAQ,OAAO;EACxB,MAAM,OAAO,MAAM,MAAM,IAAI,IAAI;EACjC,IAAI,MAAM;GAIR,KAAK;GACL,MAAM,IAAI,6BAA6B,MAAM,KAAK,OAAO,KAAK;EAChE;EACA,MAAM,MAAM,IAAI,MAAM;GAAE;GAAO;EAAM,CAAC;EACtC,MAAM,KAAK,IAAI;CACjB;CAEA,IAAI,WAAW;CACf,OAAO,EACL,eAAe;EACb,IAAI,UAAU;EACd,WAAW;EACX,KAAK;CACP,EACF;AACF;AAOO,SAAS,oBACd,MACA,UAAyC,CAAC,GACT;CACjC,MAAM,MAAM,gBAAgB,QAAQ,QAAQ;CAC5C,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO,SAAS,GAAG,CAAA,CAAE,MAAM,IAAI,IAAI,CAAA,EAAG;AACxC"}
package/dist/webmcp.d.ts CHANGED
@@ -13,6 +13,16 @@
13
13
  * `compileViewIntentToolSpec` from `intent.target`. No author-supplied code
14
14
  * runs, and the only thing it can do is dispatch one browser registry
15
15
  * command — the runtime half of the no-REST invariant.
16
+ *
17
+ * The intent's derived tool name is claimed against the document-global
18
+ * tool-name lock (#2613), so an id whose flattened name collides with a
19
+ * generated model tool or a fixed UI tool throws
20
+ * {@link WebMcpToolNameCollisionError} here rather than silently losing the
21
+ * tool at the host. `defineIntent`'s own intent-vs-intent check cannot see
22
+ * either, because both depend on a runtime `namespace` / `prefix` the
23
+ * declaration never sees.
24
+ *
25
+ * @throws {WebMcpToolNameCollisionError} when the derived name is already held.
16
26
  */
17
27
  export declare function registerViewIntent(intent: ViewIntent, binding: ViewIntentBinding, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
18
28
 
@@ -26,9 +36,19 @@ export declare function registerViewIntent(intent: ViewIntent, binding: ViewInte
26
36
  * `maxTools` are out of scope for a bespoke tool; see
27
37
  * {@link RegisterWebMcpBespokeToolOptions}.
28
38
  *
39
+ * The tool's name is also claimed against the
40
+ * document-global tool-name lock before the browser sees it. A name a
41
+ * generated model tool, a fixed UI tool, or another live bespoke tool /
42
+ * intent already holds throws {@link WebMcpToolNameCollisionError}
43
+ * SYNCHRONOUSLY, naming the colliding name and the owner that holds it.
44
+ * Previously the host accepted the call and silently dropped the losing
45
+ * registration. Disposing releases the name, so register/dispose/re-register
46
+ * under one name still succeeds.
47
+ *
29
48
  * @returns a disposer that deregisters the tool this call registered (a
30
49
  * no-op double-call). On a browser without WebMCP, or when policy excludes
31
50
  * the tool's effect, the call is a no-op and the disposer is inert.
51
+ * @throws {WebMcpToolNameCollisionError} when the name is already held.
32
52
  */
33
53
  export declare function registerWebMcpBespokeTool(spec: WebMcpBespokeToolSpec, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
34
54
 
@@ -41,13 +61,36 @@ export declare interface RegisterWebMcpBespokeToolOptions {
41
61
  * an unrelated generated tool set fail to register.
42
62
  */
43
63
  effects?: readonly WebMcpToolEffect[];
64
+ /**
65
+ * Which path this registration belongs to, used ONLY to label its
66
+ * tool-name reservation (#2613) so a later collision names the right
67
+ * source. Defaults to `bespoke`.
68
+ *
69
+ * A UI-framework binding that compiles a declared view intent itself and
70
+ * registers it here — rather than through {@link registerViewIntent} —
71
+ * must pass `'intent'`. `useViewIntent` in `@happyvertical/smrt-svelte`
72
+ * does exactly that, because reusing `useWebMcpTool`'s single WebMCP
73
+ * lifecycle is a documented invariant of that package; without this it
74
+ * would be the only shipped intent path and every intent collision would
75
+ * blame a `useWebMcpTool` call that does not exist.
76
+ *
77
+ * Purely a diagnostic label: it grants no capability, narrows no policy,
78
+ * and changes nothing about how the tool registers or executes.
79
+ */
80
+ owner?: Extract<WebMcpToolNameOwner, 'intent' | 'bespoke'>;
44
81
  }
45
82
 
46
83
  /**
47
84
  * Register every collection's generated tool descriptors with WebMCP.
48
85
  *
86
+ * Every selected tool name is claimed against the document-global tool-name
87
+ * lock (#2613) before the first browser registration, so a collision with a
88
+ * UI-layer or bespoke tool throws {@link WebMcpToolNameCollisionError} with
89
+ * the offending name and its current owner instead of reaching the host.
90
+ *
49
91
  * @returns a disposer that deregisters all tools this call registered. On a
50
92
  * browser without WebMCP the call is a no-op and the disposer is inert.
93
+ * @throws {WebMcpToolNameCollisionError} when a selected name is already held.
51
94
  */
52
95
  export declare function registerWebMcpTools(definitions: readonly WebMcpRegistrationDefinition[], options?: RegisterWebMcpToolsOptions): WebMcpRegistrationDisposer;
53
96
 
@@ -78,6 +121,34 @@ export declare interface RegisterWebMcpToolsOptions extends WebMcpExposurePolicy
78
121
  filterTool?: (definition: WebMcpToolDefinition) => boolean;
79
122
  }
80
123
 
124
+ /**
125
+ * Reserve `names` for `owner` against the document's WebMCP registry.
126
+ *
127
+ * Reservation is ALL-OR-NOTHING: if any name is already held — by this path
128
+ * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}
129
+ * naming the colliding name and its current owner is thrown synchronously,
130
+ * before any tool reaches `document.modelContext`. Duplicates within `names`
131
+ * itself collide the same way.
132
+ *
133
+ * Off-WebMCP (no document) the call is a no-op returning an inert
134
+ * reservation, matching every registrar's own no-op guarantee.
135
+ *
136
+ * A reservation releases only the names it took: `release()` never drops a
137
+ * name a later reservation has since acquired, so a dispose that lands after
138
+ * a same-name re-registration cannot revoke the new holder's claim.
139
+ */
140
+ export declare function reserveWebMcpToolNames(names: readonly string[], owner: WebMcpToolNameOwner, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameReservation;
141
+
142
+ declare interface ReserveWebMcpToolNamesOptions {
143
+ /**
144
+ * The document whose `modelContext` the names are registered against.
145
+ * Defaults to `globalThis.document`. Registrars that accept an injectable
146
+ * document (tests, non-window hosts) must pass the SAME object they read
147
+ * `modelContext` from, or their reservations land in a different table.
148
+ */
149
+ document?: unknown;
150
+ }
151
+
81
152
  /**
82
153
  * The per-collection CRUD surface of the generated REST client
83
154
  * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).
@@ -457,6 +528,89 @@ declare interface WebMcpToolDefinition extends WebToolDescriptor {
457
528
  */
458
529
  export declare type WebMcpToolEffect = 'read' | 'write' | 'destructive';
459
530
 
531
+ /**
532
+ * Thrown synchronously when a name is already held. Carries the colliding
533
+ * name and the owner that holds it so a host can report both without parsing
534
+ * the message.
535
+ */
536
+ export declare class WebMcpToolNameCollisionError extends Error {
537
+ readonly toolName: string;
538
+ /** The path that already holds {@link toolName}. */
539
+ readonly owner: WebMcpToolNameOwner;
540
+ /** The path that tried to take it. */
541
+ readonly requestedBy: WebMcpToolNameOwner;
542
+ constructor(toolName: string,
543
+ /** The path that already holds {@link toolName}. */
544
+ owner: WebMcpToolNameOwner,
545
+ /** The path that tried to take it. */
546
+ requestedBy: WebMcpToolNameOwner);
547
+ }
548
+
549
+ /**
550
+ * Document-global WebMCP tool-name lock (#2613).
551
+ *
552
+ * A browser tool name is derived independently on three paths that all end at
553
+ * the same `document.modelContext`:
554
+ *
555
+ * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by
556
+ * `registerWebMcpTools` from the exposure policy;
557
+ * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a
558
+ * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);
559
+ * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written
560
+ * `useWebMcpTool` tools, whose names come from the declaration `id` or the
561
+ * spec `name`.
562
+ *
563
+ * None of the three can see the others at declaration time: a namespace and a
564
+ * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`
565
+ * rejects duplicates WITHIN its own prospective set, but nothing checked
566
+ * ACROSS the paths, so a collision reached the host — which rejects the later
567
+ * registration and leaves the tool silently absent.
568
+ *
569
+ * This module is the shared reservation table those paths coordinate through.
570
+ * It is an AVAILABILITY guard, not a trust boundary: every path stays
571
+ * fail-closed and browser-only regardless, and a collision loses a tool rather
572
+ * than granting one. The authenticated REST surface remains the auth, tenant,
573
+ * and field-write boundary.
574
+ *
575
+ * ## Why the table lives on the document, not in this module
576
+ *
577
+ * The lock must be shared by every registrar reachable from one page, which
578
+ * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly
579
+ * more than one copy of this module — a second bundle chunk, a duplicated
580
+ * dependency, or an HMR reload that replaces module state while the host
581
+ * registry keeps every tool it already accepted. Module-level state loses the
582
+ * reservations in all of those cases, and a lost reservation is worse than no
583
+ * lock: it lets a duplicate through, or (on the release side) strands a name
584
+ * forever. The table is therefore stored on the `document` object itself under
585
+ * a cross-realm `Symbol.for` key, which every copy of this module resolves to
586
+ * the same slot.
587
+ *
588
+ * The table is additionally stamped with the `modelContext` it was built for.
589
+ * When a host installs a NEW model context on the same document — a fresh
590
+ * origin-trial registry, or a test harness swapping the double — every tool
591
+ * the old registry held is gone, so the table resets rather than stranding
592
+ * those names.
593
+ *
594
+ * This module is dependency-free by design (see the package's dependency-DAG
595
+ * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`
596
+ * entry so a UI layer can reserve its fixed tool names without pulling in the
597
+ * client-data engine.
598
+ */
599
+ /** Which registration path holds a reserved WebMCP tool name. */
600
+ export declare type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';
601
+
602
+ /**
603
+ * The owner currently holding `name` on this document, or undefined. For
604
+ * diagnostics and tests; registration paths use
605
+ * {@link reserveWebMcpToolNames}, which is atomic.
606
+ */
607
+ export declare function webMcpToolNameOwner(name: string, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameOwner | undefined;
608
+
609
+ /** Releases every name one reservation holds. Idempotent. */
610
+ export declare interface WebMcpToolNameReservation {
611
+ release(): void;
612
+ }
613
+
460
614
  /**
461
615
  * One generated collection definition: everything needed to construct a client
462
616
  * collection over the generated REST surface. The `_row` property is a phantom
package/dist/webmcp.js CHANGED
@@ -1,2 +1,3 @@
1
- import { h as registerWebMcpTools, m as registerWebMcpBespokeTool, p as registerViewIntent } from "./chunks/src-D1ZtD6Bt.js";
2
- export { registerViewIntent, registerWebMcpBespokeTool, registerWebMcpTools };
1
+ import { h as registerWebMcpTools, m as registerWebMcpBespokeTool, p as registerViewIntent } from "./chunks/src-DHH8ptH8.js";
2
+ import { WebMcpToolNameCollisionError, reserveWebMcpToolNames, webMcpToolNameOwner } from "./webmcp-tool-names.js";
3
+ export { WebMcpToolNameCollisionError, registerViewIntent, registerWebMcpBespokeTool, registerWebMcpTools, reserveWebMcpToolNames, webMcpToolNameOwner };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-web",
3
- "version": "0.46.0",
3
+ "version": "0.47.1",
4
4
  "description": "SMRT browser client data runtime: typed collection factory wrapping the client-data engine over generated REST clients",
5
5
  "author": "HappyVertical",
6
6
  "type": "module",
@@ -24,6 +24,10 @@
24
24
  "./intents": {
25
25
  "types": "./dist/intents.d.ts",
26
26
  "import": "./dist/intents.js"
27
+ },
28
+ "./webmcp-tool-names": {
29
+ "types": "./dist/webmcp-tool-names.d.ts",
30
+ "import": "./dist/webmcp-tool-names.js"
27
31
  }
28
32
  },
29
33
  "dependencies": {
@@ -32,13 +36,13 @@
32
36
  "@tanstack/query-db-collection": "^1.0.46"
33
37
  },
34
38
  "devDependencies": {
39
+ "@happyvertical/smrt-core": "0.47.1",
40
+ "@happyvertical/smrt-scanner": "0.47.1",
35
41
  "@types/node": "24.13.2",
36
42
  "fake-indexeddb": "^6.2.5",
37
43
  "typescript": "5.9.3",
38
44
  "vite": "8.1.4",
39
- "vitest": "4.1.10",
40
- "@happyvertical/smrt-core": "0.46.0",
41
- "@happyvertical/smrt-scanner": "0.46.0"
45
+ "vitest": "4.1.10"
42
46
  },
43
47
  "publishConfig": {
44
48
  "registry": "https://registry.npmjs.org",