@nekuda/webmcp-sdk 0.4.0 → 0.6.0-dev.17.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/CHANGELOG.md +116 -6
- package/README.md +32 -0
- package/dist/define.d.ts +39 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +303 -51
- package/dist/pages.d.ts +6 -0
- package/dist/register.d.ts +49 -9
- package/dist/spec.d.ts +15 -4
- package/dist/telemetry-events.d.ts +26 -2
- package/dist/telemetry-fields.d.ts +4 -0
- package/dist/telemetry.d.ts +47 -2
- package/dist/tracking.d.ts +31 -0
- package/dist/transport.d.ts +24 -0
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,105 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## Unreleased —
|
|
3
|
+
## Unreleased — telemetry beacons without a preflight
|
|
4
|
+
|
|
5
|
+
- The telemetry beacon is sent as `text/plain` instead of `application/json`. The
|
|
6
|
+
body is unchanged; the header makes an anonymous beacon a CORS simple request, so
|
|
7
|
+
the browser stops sending an `OPTIONS` preflight before every event. Keyed beacons
|
|
8
|
+
still carry `x-api-key` and still preflight. The edge has mapped `text/plain` to
|
|
9
|
+
the same template since ADR-0014, so no server change is needed.
|
|
10
|
+
|
|
11
|
+
## 0.6.0 — 2026-09-18
|
|
12
|
+
|
|
13
|
+
Five additive changes, no breaking change. The wire schema stays at `2`. Four of them
|
|
14
|
+
are opt-in options — `builtWith`, `pages`, `sessionId` and `createCallTracker` — each
|
|
15
|
+
silent when unset, so **an npm consumer who sets none of them sends exactly the bytes
|
|
16
|
+
0.5.0 sent**. The fifth is a non-enumerable mark this SDK writes on the tool objects it
|
|
17
|
+
registers under a batch that is already posting to the collect edge; it is invisible to
|
|
18
|
+
enumeration and to `JSON` and never reaches the wire, so it changes no bytes either.
|
|
19
|
+
|
|
20
|
+
Beside them is a **mechanism nothing switches on**: page-level telemetry sampling exists
|
|
21
|
+
as a `bun build --define`, and no build this release ships passes a rate. **Every
|
|
22
|
+
install, npm and CDN snippet alike, is unsampled** and sends every page-level event,
|
|
23
|
+
exactly as 0.5.0 did.
|
|
24
|
+
|
|
25
|
+
### Page-level telemetry sampling — available, off everywhere
|
|
26
|
+
|
|
27
|
+
- A build **may** sample the two page-level telemetry events (`sdk_init`,
|
|
28
|
+
`tool_registration`) with `--define '__WEBMCP_TELEMETRY_SAMPLE_RATE__="0.1"'`. The
|
|
29
|
+
decision is one coin per page load from the `sessionId`, so a page's events are kept
|
|
30
|
+
or dropped together, and every kept event carries `sampleRate` for weighting.
|
|
31
|
+
`tool_call` is never sampled. Unset, `1`, or anything outside (0, 1), sends
|
|
32
|
+
everything — the previous behaviour byte for byte.
|
|
33
|
+
- **Nothing opts in.** Neither publish lane passes the define, and the CDN snippet
|
|
34
|
+
build (`infra/deploy-snippet.sh`) defines the rate as `1`. These two events carry
|
|
35
|
+
the connects, registered-tool counts, refreshes and unique user agents the product
|
|
36
|
+
counts active users from, so they ship complete; the beacon volume gets a fast-path
|
|
37
|
+
ingest queue rather than a tenth of the observations. Turning sampling on for one
|
|
38
|
+
deploy is `SNIPPET_TELEMETRY_SAMPLE_RATE=<rate>` in front of that script, and the
|
|
39
|
+
kept events then say what rate they were kept at.
|
|
40
|
+
|
|
41
|
+
### `tracking.builtWith`
|
|
42
|
+
|
|
43
|
+
- **`TrackingOptions.builtWith`** names what generated the integration, as
|
|
44
|
+
`<tool>[/<path>]@<version>` (`webmcp-kit/implement@<plugin version>`,
|
|
45
|
+
`webmcp-kit/connect-existing-tools@<plugin version>`), and is reported verbatim
|
|
46
|
+
as `config.builtWith` on `tool_registration`. Additive and optional: unset emits
|
|
47
|
+
exactly the bytes it did before. It exists so kit-built sites are countable on
|
|
48
|
+
the default-on channel without a Connect. Dropped, never truncated, when empty
|
|
49
|
+
or over 64 characters.
|
|
50
|
+
|
|
51
|
+
### Page-scoped tools
|
|
52
|
+
|
|
53
|
+
- `defineTool({ pages })` filters registration on load and SPA navigation; `matchPage`
|
|
54
|
+
and `currentPageKey` expose the fixture-pinned matching rule. SDK metadata stays off
|
|
55
|
+
the native tool object. `ready` covers the initial filtered set; `current()` and
|
|
56
|
+
`onChange` expose live names. Unregistration uses native abort signals, with legacy
|
|
57
|
+
`unregisterTool` support and explicit retention on surfaces supporting neither.
|
|
58
|
+
|
|
59
|
+
### A host may own the session and track its own tools
|
|
60
|
+
|
|
61
|
+
Two additive `tracking`-channel surfaces for a host that owns the page it runs on
|
|
62
|
+
(the CDN snippet). Nothing existing changes: both are opt-in, both are silent
|
|
63
|
+
under the same `trackingOutputs` gate as everything else on this channel, and a
|
|
64
|
+
consumer that sets neither emits exactly the bytes it did before.
|
|
65
|
+
|
|
66
|
+
- **`TrackingOptions.sessionId`** reports under a session identity the host
|
|
67
|
+
already has — a tab session that predates any `registerTools` call, or a
|
|
68
|
+
Journey runner's synthetic `syn_…` id — instead of the one this channel mints
|
|
69
|
+
in `sessionStorage` under an `apiKey`-derived namespace, which would split one
|
|
70
|
+
visit into two sessions the pipeline cannot rejoin. Supplying it means no
|
|
71
|
+
`sessionStorage` read or write at all for that batch, `last_seen` included, so
|
|
72
|
+
the 30-minute inactivity boundary becomes the host's to enforce; `visitorId` is
|
|
73
|
+
untouched. Validated like a stored id (non-empty, ≤ 64 chars, a string) —
|
|
74
|
+
anything else falls back to the minted session, because identity fields skip the
|
|
75
|
+
truncation ladder.
|
|
76
|
+
- **`createCallTracker(tool, tracking)`** emits the request/response pair for a
|
|
77
|
+
tool the host registered on `document.modelContext` itself, in the same bytes an
|
|
78
|
+
SDK-registered tool's call produces — same event names, same correlated `callId`,
|
|
79
|
+
same anonymous identity — so the projection reads one shape rather than two. The
|
|
80
|
+
host owns the rest: one tracker per invocation, `tool_call_request` before the
|
|
81
|
+
handler and `tool_call_response` after, and the `duration_ms` it reports. The
|
|
82
|
+
tool's `name` is its `stableKey`, since a host-wrapped tool has no
|
|
83
|
+
developer-authored durable identity to carry — and that rule is applied *over*
|
|
84
|
+
the caller's object, so a `stableKey` riding in on a tool descriptor from a page
|
|
85
|
+
the host does not control cannot key that tool differently from every other
|
|
86
|
+
reader of the same page.
|
|
87
|
+
|
|
88
|
+
### A tracked tool says so, for a host that owns the surface
|
|
89
|
+
|
|
90
|
+
- A tool registered by a batch that posts to the collect edge now carries the registry
|
|
91
|
+
symbol `Symbol.for("webmcp.sdk.tracked")` (non-enumerable, value `true`) on the object
|
|
92
|
+
handed to `registerTool`. It exists for a host that owns the page's WebMCP surface and
|
|
93
|
+
wraps what it finds there — the CDN snippet's coexistence gate — which could not
|
|
94
|
+
otherwise tell a tool whose calls this SDK already reports from a merchant's bare one,
|
|
95
|
+
and reported every such invocation a second time. Nothing else changes: the mark is
|
|
96
|
+
written only when the channel really is posting (an unkeyed batch, an empty
|
|
97
|
+
`tracking: {}`, a closed consent gate and an otel-only batch carry none), it is
|
|
98
|
+
invisible to enumeration and to `JSON`, and no export is added. It is the one change
|
|
99
|
+
here that is not an option a consumer sets — but being non-enumerable it reaches
|
|
100
|
+
neither the wire nor `JSON.stringify`, so no bytes move.
|
|
101
|
+
|
|
102
|
+
## 0.5.0 — 2026-08-24 — usage telemetry schema 2 (breaking wire format)
|
|
4
103
|
|
|
5
104
|
The usage-telemetry channel emits **three events instead of two**, carrying
|
|
6
105
|
derived signals instead of raw page and payload strings. `schema` bumps `1` → `2`.
|
|
@@ -11,11 +110,11 @@ unchanged. See `docs/telemetry-schema.md`.
|
|
|
11
110
|
|
|
12
111
|
### Breaking
|
|
13
112
|
|
|
14
|
-
- **`sdk_init` fires once per page load, not once per `registerTools` call.**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
events.
|
|
113
|
+
- **`sdk_init` fires once per page load, not once per `registerTools` call.** The
|
|
114
|
+
first telemetry-live batch schedules a next-task flush with its own key and
|
|
115
|
+
endpoint; an import-time one-second fallback still fires when nobody registers
|
|
116
|
+
anything, which is the only way a broken integration is visible. A page with
|
|
117
|
+
three `registerTools` calls no longer emits three "init" events.
|
|
19
118
|
- **Per-call information moved to the new `tool_registration` event**, one per
|
|
20
119
|
`registerTools` call: `registrationIndex`, `trigger`, `settleMs`, the `config.*`
|
|
21
120
|
booleans (moved off `sdk_init`, which fires before any batch may have run), and
|
|
@@ -65,6 +164,17 @@ unchanged. See `docs/telemetry-schema.md`.
|
|
|
65
164
|
|
|
66
165
|
### Added
|
|
67
166
|
|
|
167
|
+
- **One `console.error` when a configured publishable key is refused.** The telemetry
|
|
168
|
+
route now answers `401` for a key that was *sent* and did not resolve; the beacon is
|
|
169
|
+
still recorded, anonymously, so nothing is lost but the attribution. The SDK reports
|
|
170
|
+
that at most once per page load — naming the SDK, saying recording continues
|
|
171
|
+
anonymously before saying what broke, and pointing at re-running Connect. It never
|
|
172
|
+
echoes the key or anything off the wire. No throw, no retry, no second send, and no
|
|
173
|
+
change to the send path. Anonymous beacons are never inspected (they have no key to
|
|
174
|
+
be wrong about), and every other status, a network failure, and an environment with
|
|
175
|
+
no `console` stay silent as before. This narrows 0.2.0's "the SDK is silent by
|
|
176
|
+
default": it is the only output the package produces, and only a broken key produces
|
|
177
|
+
it.
|
|
68
178
|
- **`globalThis.__WEBMCP_TELEMETRY__ = false`** silences the channel page-wide
|
|
69
179
|
without touching a `registerTools` call site — the only lever a site has when the
|
|
70
180
|
calls come from generated code it does not edit. Strictly `false`, like GPC is
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @nekuda/webmcp-sdk
|
|
2
|
+
|
|
3
|
+
Define tools with `defineTool`, then pass them to `registerTools(tools, options)`.
|
|
4
|
+
`ready` resolves with initial per-tool outcomes; `unregister()` or `options.signal`
|
|
5
|
+
ends the batch. Browsers without a WebMCP surface report `unsupported`.
|
|
6
|
+
|
|
7
|
+
## Page-scoped tools
|
|
8
|
+
|
|
9
|
+
Set `pages: ["/products/*"]` on a tool definition to register it only on matching
|
|
10
|
+
pages. Missing/empty `pages` means everywhere; lists match any entry. `*` matches
|
|
11
|
+
any characters within one segment (`/products/*`, `/product.html*`), `**` zero or
|
|
12
|
+
more segments; queries and trailing slashes are ignored on both patterns and locations,
|
|
13
|
+
and repeated slashes collapse to one. Hash
|
|
14
|
+
routers use patterns such as `/#/product/*`. With no readable location (SSR),
|
|
15
|
+
all tools are eligible because the page cannot be evaluated.
|
|
16
|
+
|
|
17
|
+
The SDK reconciles tools on `pushState`, `replaceState`, `popstate`, and
|
|
18
|
+
`hashchange`, sharing one route listener. If History cannot be patched, registration
|
|
19
|
+
still succeeds and navigation detection falls back to `popstate`/`hashchange` only.
|
|
20
|
+
A failing route subscriber never escapes into the merchant's History calls or blocks
|
|
21
|
+
other subscribers. `registration.current()` lists live
|
|
22
|
+
names; `options.onChange(names)` keeps host displays in sync. `ready` covers only
|
|
23
|
+
the initially eligible tools. SDK-only `pages` never reaches native `registerTool`.
|
|
24
|
+
|
|
25
|
+
The [WebMCP draft](https://webmachinelearning.github.io/webmcp/), checked
|
|
26
|
+
2026-09-10, unregisters via the registration's **AbortSignal**, not
|
|
27
|
+
`unregisterTool`; this works on either resolved modelContext global. Legacy
|
|
28
|
+
surfaces exposing `unregisterTool(name)` are supported too. A surface that neither
|
|
29
|
+
reads the registration signal nor exposes that method keeps a tool once registered;
|
|
30
|
+
the SDK reports it as still live rather than claiming removal. Ending a batch always
|
|
31
|
+
removes its route subscription. Fixtures under `fixtures/` pin matching semantics
|
|
32
|
+
for tests and stay out of the published package.
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import type { ToolAnnotations } from "./spec.js";
|
|
2
|
+
/**
|
|
3
|
+
* Spec rule: tool names are 1–128 chars of ASCII alphanumerics, `_`, `-`, `.`.
|
|
4
|
+
*
|
|
5
|
+
* Exported for the seam pin only (`tests/tool-key-patterns.test.ts`), not re-exported by
|
|
6
|
+
* `index.ts`: the platform's catalog CHECKs the same shape, and a widening here that the
|
|
7
|
+
* database refuses would reject tools this SDK already accepted in the wild.
|
|
8
|
+
*/
|
|
9
|
+
export declare const NAME_PATTERN: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* `stableKey` rule: dot-namespaced `domain.action` — two or more `[a-z0-9_]+`
|
|
12
|
+
* segments joined by `.`. Rejects the common authoring mistake of copying the
|
|
13
|
+
* wire `name` into `stableKey` (e.g. `search_blog_posts`), which defeats the
|
|
14
|
+
* field's purpose: a `name` can be renamed freely, but a `stableKey` that is
|
|
15
|
+
* just a `name` copy renames right along with it.
|
|
16
|
+
*
|
|
17
|
+
* Exported for the seam pin only — see {@link NAME_PATTERN}.
|
|
18
|
+
*/
|
|
19
|
+
export declare const STABLE_KEY_PATTERN: RegExp;
|
|
2
20
|
/** How the tool came to exist. Reported on telemetry as `tools[].source`. */
|
|
3
21
|
export type ToolSource = "scanner_generated" | "merchant_authored";
|
|
4
22
|
/** What the tool is for. Reported on telemetry as `tools[].intent`. */
|
|
@@ -33,6 +51,8 @@ export interface ToolDefinition<TInput extends Record<string, unknown> = Record<
|
|
|
33
51
|
/** JSON Schema for `execute`'s input, as a plain object. */
|
|
34
52
|
inputSchema?: Record<string, unknown>;
|
|
35
53
|
annotations?: ToolAnnotations;
|
|
54
|
+
/** Page patterns where this tool is available; absent/empty means everywhere. SDK-only. */
|
|
55
|
+
pages?: string[];
|
|
36
56
|
/**
|
|
37
57
|
* Optional per-tool version, surfaced on emitted tracking events as `toolVersion`
|
|
38
58
|
* for drift analytics. Free-form string (e.g. semver or a codegen hash); when
|
|
@@ -51,6 +71,25 @@ export interface ToolDefinition<TInput extends Record<string, unknown> = Record<
|
|
|
51
71
|
* rather than per-batch because one tool answers while another transacts.
|
|
52
72
|
*/
|
|
53
73
|
intent?: ToolIntent;
|
|
74
|
+
/**
|
|
75
|
+
* The identity the connected platform assigned this tool, if the host knows it —
|
|
76
|
+
* an opaque string the SDK copies and never interprets.
|
|
77
|
+
*
|
|
78
|
+
* `stableKey` is the DEVELOPER's durable identity and survives renames; this is the
|
|
79
|
+
* server's, handed back once a site is connected. Both are reported because they
|
|
80
|
+
* answer different questions: a key the developer chose can collide across two
|
|
81
|
+
* scopes of one site, while the assigned id cannot, and only the developer's key
|
|
82
|
+
* exists before a site is connected at all. Absent is the ordinary state — a tool
|
|
83
|
+
* declared in a codebase that has never been connected simply has no such id, and
|
|
84
|
+
* the SDK never invents one.
|
|
85
|
+
*/
|
|
86
|
+
inventoryToolId?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The contract revision the host believes this tool matches, if it knows one.
|
|
89
|
+
* Reported so a stale bundle can be told apart from a tool that genuinely changed
|
|
90
|
+
* shape; never validated here, and never used for anything on the page.
|
|
91
|
+
*/
|
|
92
|
+
contractRevision?: number;
|
|
54
93
|
/**
|
|
55
94
|
* The page-owned behavior. May return anything JSON-serializable, a plain string,
|
|
56
95
|
* or a ready-made `{ content: [...] }` result — the SDK normalizes for the agent.
|
package/dist/index.d.ts
CHANGED
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
* and is never sent to the browser.
|
|
15
15
|
* 3. Registration lifecycle: `registerTools(tools, { signal?, tracking?, telemetry? })`
|
|
16
16
|
* registers on call
|
|
17
|
-
* and returns `{ ready, unregister, signal }`.
|
|
18
|
-
* `unregister()` or
|
|
17
|
+
* and returns `{ ready, current, unregister, signal }`. Page-scoped tools also
|
|
18
|
+
* register/unregister as routes change; `unregister()` or an external abort ends the batch.
|
|
19
19
|
* Browsers without a WebMCP surface are a graceful no-op (`state: "unsupported"`).
|
|
20
20
|
* 4. "Connect later without rewrite": generated modules only `defineTool` and
|
|
21
21
|
* EXPORT tools; one entry module calls `registerTools`. Connecting a site to the
|
|
22
|
-
* platform later
|
|
23
|
-
*
|
|
22
|
+
* platform later adds a publishable tracking key to wrapper config only — generated
|
|
23
|
+
* modules and their imports do not change. The default-on anonymous channel in point 6
|
|
24
|
+
* is independent of Connect. See `examples/add-to-cart.ts`.
|
|
24
25
|
* 5. Anonymous tool-call tracking is opt-in via `registerTools`'s `tracking`
|
|
25
26
|
* option (see `TrackingOptions`) and default-silent: with neither `apiKey`
|
|
26
27
|
* nor `otel` set, no event is built at all — no identity resolution, no
|
|
@@ -37,7 +38,13 @@
|
|
|
37
38
|
* both outputs — but for THIS channel only; the default-on telemetry channel of
|
|
38
39
|
* point 6 has its own opt-out (`telemetry: false`) and `disabled` does not
|
|
39
40
|
* narrow it. See `src/tracking.ts`, `src/transport.ts`, and
|
|
40
|
-
* `examples/add-to-cart.ts`.
|
|
41
|
+
* `examples/add-to-cart.ts`. `TrackingOptions.sessionId` lets a host that is
|
|
42
|
+
* already the page's session authority report under its own id instead of the
|
|
43
|
+
* one this channel mints; `createCallTracker(tool, tracking)` lets that same
|
|
44
|
+
* host emit the pair for a tool it registered on the surface *itself*, in the
|
|
45
|
+
* same bytes. Both exist for one caller — a host of this SDK that owns the page
|
|
46
|
+
* (the CDN snippet) — and neither is part of what plugin codegen targets:
|
|
47
|
+
* generated code registers tools and reads nothing here.
|
|
41
48
|
* 6. Anonymous usage telemetry is a SECOND, independent channel: default-ON, so it
|
|
42
49
|
* reports from every site the SDK runs on, not only from `apiKey` tenants. Three
|
|
43
50
|
* events (`TelemetryEvent`), all `schema: 2` and joined on an in-memory
|
|
@@ -73,7 +80,8 @@
|
|
|
73
80
|
* platform stays a config-only change to the entry module (point 4).
|
|
74
81
|
*/
|
|
75
82
|
export { type AnyWebMCPTool, type ToolDefinition, type ToolIntent, type ToolSource, type WebMCPTool, defineTool, } from "./define.js";
|
|
76
|
-
export {
|
|
83
|
+
export { currentPageKey, matchPage } from "./pages.js";
|
|
84
|
+
export { type CallTracker, type RegisterToolsOptions, type ToolRegistration, type ToolRegistrationResult, type ToolRegistrationState, type TrackedCall, createCallTracker, registerTools, } from "./register.js";
|
|
77
85
|
export { type ModelContextLike, type RegisterToolOptions, type SpecTool, type ToolAnnotations, resolveModelContext, } from "./spec.js";
|
|
78
86
|
export type { AgentRuntime, FormFactor, FrameContext, PageVisibility, ReferrerClass, SurfaceGlobal, SurfaceInfo, SurfaceProvenance, } from "./telemetry-context.js";
|
|
79
87
|
export type { ClientContext, InstallMode, PageContext, RegisteredToolEntry, RegistrationTrigger, SdkInfo, SdkInitEvent, StrippedToolEntry, TelemetryEnvelope, TelemetryEvent, TelemetryEventName, ToolCallEvent, ToolCallOutcome, ToolCallResponseMetrics, ToolCallToolInfo, ToolRegistrationEvent, ToolRegistrationOutcome, TrackingConfigInfo, TruncatedTools, } from "./telemetry-events.js";
|