@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/dist/register.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface ToolRegistrationResult {
|
|
|
16
16
|
error?: unknown;
|
|
17
17
|
}
|
|
18
18
|
export interface RegisterToolsOptions {
|
|
19
|
+
/** Called after the live name set changes, including initial registration and navigation. */
|
|
20
|
+
onChange?: (names: string[]) => void;
|
|
19
21
|
/**
|
|
20
22
|
* External lifetime for the registration (e.g. a component's unmount signal).
|
|
21
23
|
* Aborting it unregisters every tool in this batch, same as `unregister()`.
|
|
@@ -44,10 +46,11 @@ export interface RegisterToolsOptions {
|
|
|
44
46
|
*
|
|
45
47
|
* The other two events are per-batch, so the option is read per call: a *later*
|
|
46
48
|
* `registerTools` that omits it reports its own batch and calls. Cancelling
|
|
47
|
-
* `sdk_init` also requires winning the race with its deferred flush
|
|
48
|
-
* registered at module scope
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* `sdk_init` also requires winning the race with its deferred flush. A batch
|
|
50
|
+
* registered at module scope or from an ordinary mount effect schedules the keyed
|
|
51
|
+
* batch flush; a later consent decision can still arrive after the bounded
|
|
52
|
+
* no-registration fallback. A site that wants the whole page silent regardless of
|
|
53
|
+
* when its `registerTools` calls run (generated code, a CDN snippet) should use a
|
|
51
54
|
* page-level lever instead: `globalThis.__WEBMCP_TELEMETRY__ = false` — strictly
|
|
52
55
|
* `false`, so a truthy `"0"` does not opt out — or Global Privacy Control
|
|
53
56
|
* (`navigator.globalPrivacyControl === true`), both of which are read at emit time
|
|
@@ -63,18 +66,55 @@ export interface ToolRegistration {
|
|
|
63
66
|
* cannot mask the rest.
|
|
64
67
|
*/
|
|
65
68
|
ready: Promise<ToolRegistrationResult[]>;
|
|
69
|
+
/** Currently registered names, in declaration order; `ready` covers only the initial page. */
|
|
70
|
+
current(): string[];
|
|
66
71
|
/** Unregister every tool in this batch. Idempotent. */
|
|
67
72
|
unregister(): void;
|
|
68
73
|
/** The signal carrying this registration's lifetime (aborted once unregistered). */
|
|
69
74
|
signal: AbortSignal;
|
|
70
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Emits one invocation's authenticated-channel events. The `callId` correlating a
|
|
78
|
+
* `tool_call_request` with its `tool_call_response` is closed over, so one tracker
|
|
79
|
+
* is one call — never one tool, never one page.
|
|
80
|
+
*/
|
|
81
|
+
export type CallTracker = (eventName: string, data: Record<string, unknown>) => void;
|
|
82
|
+
/**
|
|
83
|
+
* A tool a host observes but did not register through {@link registerTools} — the
|
|
84
|
+
* only identity it can offer is the wire `name` the agent surface already knows.
|
|
85
|
+
*/
|
|
86
|
+
export interface TrackedCall {
|
|
87
|
+
name: string;
|
|
88
|
+
/** Optional per-tool version, surfaced on the events as `toolVersion`. */
|
|
89
|
+
version?: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Emitter for a call on a tool this SDK did not register, or `undefined` when the
|
|
93
|
+
* authenticated channel has no live output — the same `trackingOutputs` answer
|
|
94
|
+
* {@link registerTools} gates its own per-call emitter on, so the two cannot
|
|
95
|
+
* disagree about whether the channel is live.
|
|
96
|
+
*
|
|
97
|
+
* This exists for one caller shape: a host that owns the page's WebMCP surface and
|
|
98
|
+
* wraps tools registered on it directly (the CDN snippet's coexistence path). Those
|
|
99
|
+
* calls must land on `/v1/collect` as the same bytes an SDK-registered tool's do —
|
|
100
|
+
* same event names, same correlated pair, same anonymous identity — so the
|
|
101
|
+
* projection reads one shape rather than two. Everything else the host must do
|
|
102
|
+
* itself: call it once per invocation (a reused tracker collapses two calls into
|
|
103
|
+
* one `callId`), emit `tool_call_request` before the handler and
|
|
104
|
+
* `tool_call_response` after, and measure the `duration_ms` it reports.
|
|
105
|
+
*
|
|
106
|
+
* `stableKey` is the tool's `name`: a host-wrapped tool has no developer-authored
|
|
107
|
+
* durable identity to carry, and inventing one would key the same tool differently
|
|
108
|
+
* from every other reader of the same page.
|
|
109
|
+
*/
|
|
110
|
+
export declare function createCallTracker(tool: TrackedCall, tracking: TrackingOptions): CallTracker | undefined;
|
|
71
111
|
/**
|
|
72
112
|
* Register a batch of `defineTool` tools on the page's WebMCP surface.
|
|
73
113
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
114
|
+
* Registration starts immediately, filtered by `pages`; `ready` covers that initial set.
|
|
115
|
+
* Route changes reconcile scoped tools using per-tool signals (or legacy unregisterTool).
|
|
116
|
+
* A legacy surface that never reads the signal and has no unregister method retains a
|
|
117
|
+
* tool once registered. `unregister()`/external abort also removes the route subscription.
|
|
118
|
+
* Without a readable location (SSR), all tools are eligible; without a surface, no-op.
|
|
79
119
|
*/
|
|
80
120
|
export declare function registerTools(tools: readonly AnyWebMCPTool[], options?: RegisterToolsOptions): ToolRegistration;
|
package/dist/spec.d.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* - `registerTool(tool, { signal })` returns a promise that settles when the
|
|
12
12
|
* registration completes; it rejects on a duplicate name, an invalid tool, an
|
|
13
13
|
* inactive document, or an abort.
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* - Current unregistration is the `AbortSignal` passed at registration (also
|
|
15
|
+
* verified against the 2026-09-10 draft). Legacy surfaces may instead expose
|
|
16
|
+
* `unregisterTool(name)`; a surface supporting neither retains registered tools.
|
|
17
17
|
* - Tool names: 1–128 chars of [A-Za-z0-9_\-.].
|
|
18
18
|
* - `annotations.readOnlyHint` / `annotations.untrustedContentHint`.
|
|
19
19
|
*
|
|
@@ -48,9 +48,20 @@ export interface RegisterToolOptions {
|
|
|
48
48
|
*/
|
|
49
49
|
export interface ModelContextLike {
|
|
50
50
|
registerTool(tool: SpecTool, options?: RegisterToolOptions): Promise<void>;
|
|
51
|
+
/** Pre-draft compatibility only; current native surfaces use the registration signal. */
|
|
52
|
+
unregisterTool?(name: string): void;
|
|
53
|
+
}
|
|
54
|
+
interface GlobalWithModelContext {
|
|
55
|
+
document?: {
|
|
56
|
+
modelContext?: ModelContextLike;
|
|
57
|
+
};
|
|
58
|
+
navigator?: {
|
|
59
|
+
modelContext?: ModelContextLike;
|
|
60
|
+
};
|
|
51
61
|
}
|
|
52
62
|
/**
|
|
53
63
|
* Resolve the page's WebMCP surface; `undefined` on non-supporting browsers.
|
|
54
64
|
* Injectable global scope for tests.
|
|
55
65
|
*/
|
|
56
|
-
export declare function resolveModelContext(
|
|
66
|
+
export declare function resolveModelContext(scope?: GlobalWithModelContext): ModelContextLike | undefined;
|
|
67
|
+
export {};
|
|
@@ -81,7 +81,17 @@ export interface ClientContext {
|
|
|
81
81
|
* even when nobody ever calls `registerTools`: a site that loads the SDK and never
|
|
82
82
|
* registers is a broken integration, and this event is the only way to see it.
|
|
83
83
|
*/
|
|
84
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Present only when a build sampled the page-level events: the rate this page was kept
|
|
86
|
+
* at, strictly between 0 and 1. Absent means every such event on the page was sent. A
|
|
87
|
+
* consumer counting page loads weights each sampled event by `1 / sampleRate`. Not on
|
|
88
|
+
* the envelope, because `tool_call` never carries it — sampling is per page, and a
|
|
89
|
+
* tool invocation is not a page-level fact.
|
|
90
|
+
*/
|
|
91
|
+
export interface SampledEvent {
|
|
92
|
+
sampleRate?: number;
|
|
93
|
+
}
|
|
94
|
+
export interface SdkInitEvent extends TelemetryEnvelope, SampledEvent {
|
|
85
95
|
event: "sdk_init";
|
|
86
96
|
sdk: SdkInfo;
|
|
87
97
|
surface: SurfaceInfo;
|
|
@@ -108,6 +118,8 @@ export interface TrackingConfigInfo {
|
|
|
108
118
|
trackingEnabled: boolean;
|
|
109
119
|
otelEnabled: boolean;
|
|
110
120
|
customEndpoint: boolean;
|
|
121
|
+
/** `TrackingOptions.builtWith`, verbatim, when set and within its length cap. */
|
|
122
|
+
builtWith?: string;
|
|
111
123
|
}
|
|
112
124
|
/**
|
|
113
125
|
* One tool in a `tool_registration` batch. The shape metrics are inherited as
|
|
@@ -117,6 +129,14 @@ export interface TrackingConfigInfo {
|
|
|
117
129
|
export interface RegisteredToolEntry extends Partial<ToolShapeMetrics> {
|
|
118
130
|
name: string;
|
|
119
131
|
stableKey: string;
|
|
132
|
+
/**
|
|
133
|
+
* The connected platform's own id for this tool, when the host supplied one. Opaque
|
|
134
|
+
* and optional: a tool declared in a codebase that has never been connected has none,
|
|
135
|
+
* and its absence is the honest reading rather than a degraded one.
|
|
136
|
+
*/
|
|
137
|
+
inventoryToolId?: string;
|
|
138
|
+
/** The contract revision the host believed this tool matched, when it knew one. */
|
|
139
|
+
contractRevision?: number;
|
|
120
140
|
version?: string;
|
|
121
141
|
schemaHash?: string;
|
|
122
142
|
source?: ToolSource;
|
|
@@ -154,7 +174,7 @@ export interface TruncatedTools {
|
|
|
154
174
|
* outcomes v1 computed and threw away. A site where 3 of 8 tools fail on a
|
|
155
175
|
* duplicate name is the failure mode this event exists to make visible.
|
|
156
176
|
*/
|
|
157
|
-
export interface ToolRegistrationEvent extends TelemetryEnvelope {
|
|
177
|
+
export interface ToolRegistrationEvent extends TelemetryEnvelope, SampledEvent {
|
|
158
178
|
event: "tool_registration";
|
|
159
179
|
/** 1-based position of this batch within the page load. */
|
|
160
180
|
registrationIndex: number;
|
|
@@ -176,6 +196,10 @@ export type ToolCallOutcome = "success" | "error";
|
|
|
176
196
|
/** `tool.*` on `tool_call` — identity and the two fields worth joining calls on. */
|
|
177
197
|
export interface ToolCallToolInfo {
|
|
178
198
|
stableKey: string;
|
|
199
|
+
/** The connected platform's own id, when the host supplied one. See the registration
|
|
200
|
+
* entry's field of the same name — same value, same optionality, same opacity. */
|
|
201
|
+
inventoryToolId?: string;
|
|
202
|
+
contractRevision?: number;
|
|
179
203
|
schemaHash?: string;
|
|
180
204
|
intent?: ToolIntent;
|
|
181
205
|
}
|
|
@@ -40,6 +40,7 @@ export declare const TELEMETRY_FIELDS: {
|
|
|
40
40
|
readonly event: true;
|
|
41
41
|
readonly ts: true;
|
|
42
42
|
readonly sessionId: true;
|
|
43
|
+
readonly sampleRate: true;
|
|
43
44
|
readonly "sdk.name": true;
|
|
44
45
|
readonly "sdk.version": true;
|
|
45
46
|
readonly "sdk.installMode": true;
|
|
@@ -64,6 +65,7 @@ export declare const TELEMETRY_FIELDS: {
|
|
|
64
65
|
readonly "config.trackingEnabled": true;
|
|
65
66
|
readonly "config.otelEnabled": true;
|
|
66
67
|
readonly "config.customEndpoint": true;
|
|
68
|
+
readonly "config.builtWith": true;
|
|
67
69
|
readonly tools: true;
|
|
68
70
|
readonly callId: true;
|
|
69
71
|
readonly callIndex: true;
|
|
@@ -90,6 +92,8 @@ export declare const TELEMETRY_FIELDS: {
|
|
|
90
92
|
export declare const TELEMETRY_TOOL_FIELDS: {
|
|
91
93
|
readonly name: true;
|
|
92
94
|
readonly stableKey: true;
|
|
95
|
+
readonly inventoryToolId: true;
|
|
96
|
+
readonly contractRevision: true;
|
|
93
97
|
readonly version: true;
|
|
94
98
|
readonly schemaHash: true;
|
|
95
99
|
readonly source: true;
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -43,6 +43,22 @@ export declare const SDK_VERSION: string;
|
|
|
43
43
|
* the overwhelming majority rather than emit a value no consumer can read.
|
|
44
44
|
*/
|
|
45
45
|
export declare const SDK_INSTALL_MODE: InstallMode;
|
|
46
|
+
/**
|
|
47
|
+
* Parse a sampling rate. Anything that is not a number strictly between 0 and 1 means
|
|
48
|
+
* "send everything": an unset define is how every npm build reaches a page, and a
|
|
49
|
+
* mistyped one must degrade to complete data rather than to silence.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseSampleRate(raw: unknown): number;
|
|
52
|
+
/** The rate this build samples page-level events at; `1` sends everything. */
|
|
53
|
+
export declare const SDK_TELEMETRY_SAMPLE_RATE: number;
|
|
54
|
+
/**
|
|
55
|
+
* Whether this page load is in the sample. Decided from the session id, which every
|
|
56
|
+
* event on the page shares, so `sdk_init` and the `tool_registration`s beside it are
|
|
57
|
+
* kept or dropped together — a sampled page is a complete page, never half of one. The
|
|
58
|
+
* hash is the same FNV-1a the channel already uses for `schemaHash`; its top 32 bits
|
|
59
|
+
* over 2^32 is a uniform enough coin for a rate that only has to hold on average.
|
|
60
|
+
*/
|
|
61
|
+
export declare function pageSampled(sessionId: string, rate?: number): boolean;
|
|
46
62
|
/**
|
|
47
63
|
* Read a value that may not exist, collapsing both an absent property and a
|
|
48
64
|
* throwing getter to `undefined`. Every browser global this module touches is
|
|
@@ -173,6 +189,9 @@ export declare function buildInitEvent(params?: InitEventParams): SdkInitEvent;
|
|
|
173
189
|
export interface RegisteredTelemetryTool {
|
|
174
190
|
name: string;
|
|
175
191
|
stableKey: string;
|
|
192
|
+
/** The connected platform's own id for this tool; opaque, copied, never interpreted. */
|
|
193
|
+
inventoryToolId?: string;
|
|
194
|
+
contractRevision?: number;
|
|
176
195
|
version?: string;
|
|
177
196
|
description?: string;
|
|
178
197
|
inputSchema?: unknown;
|
|
@@ -187,6 +206,12 @@ export interface RegisteredToolParams {
|
|
|
187
206
|
/** What the surface rejected with; templated into `failureSignature` when `failed`. */
|
|
188
207
|
error?: unknown;
|
|
189
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Cap on `config.builtWith`, in characters. Sized for `<tool>@<semver-with-prerelease>`
|
|
211
|
+
* with room to spare; it is the one caller-supplied string on `tool_registration`
|
|
212
|
+
* that is not copied off a tool, so the per-entry byte cap below does not cover it.
|
|
213
|
+
*/
|
|
214
|
+
export declare const MAX_BUILT_WITH_LENGTH = 64;
|
|
190
215
|
/** Where a batch sits in the page load, and what caused it. */
|
|
191
216
|
export interface RegistrationSequence {
|
|
192
217
|
/** 1-based position among the batches this page load reports. */
|
|
@@ -278,6 +303,9 @@ export declare function nextCall(stableKey: string, tenantScope?: string): ToolC
|
|
|
278
303
|
/** What `tool_call` reads off a tool — the two fields worth joining calls on. */
|
|
279
304
|
export interface CalledTelemetryTool {
|
|
280
305
|
stableKey: string;
|
|
306
|
+
/** The connected platform's own id for this tool; opaque, copied, never interpreted. */
|
|
307
|
+
inventoryToolId?: string;
|
|
308
|
+
contractRevision?: number;
|
|
281
309
|
/** Reported as a hash only; the schema itself never leaves the page. */
|
|
282
310
|
inputSchema?: unknown;
|
|
283
311
|
intent?: ToolIntent;
|
|
@@ -360,7 +388,7 @@ export interface TelemetrySinks {
|
|
|
360
388
|
* the raw batch option: a key resolved to a value pins it, closing the window in which
|
|
361
389
|
* the module key changes between the claim and the emit.
|
|
362
390
|
*/
|
|
363
|
-
export declare function batchTelemetrySinks(apiKey: unknown): TelemetrySinks;
|
|
391
|
+
export declare function batchTelemetrySinks(apiKey: unknown, endpoint?: unknown): TelemetrySinks;
|
|
364
392
|
/**
|
|
365
393
|
* The key a batch's beacons authenticate with, resolved now instead of at emit time:
|
|
366
394
|
* its own when it supplied one, otherwise the page's.
|
|
@@ -421,7 +449,7 @@ export declare function telemetryTenantScope(apiKey: unknown): string;
|
|
|
421
449
|
* global the builders read is guarded individually too; this is the backstop that
|
|
422
450
|
* keeps the guarantee true of assembly as a whole, not of each read in turn.
|
|
423
451
|
*/
|
|
424
|
-
export declare function emitTelemetry(build: () => TelemetryEvent, sinks?: TelemetrySinks, fields?: TelemetryFieldMap, toolFields?: TelemetryToolFieldMap): void;
|
|
452
|
+
export declare function emitTelemetry(build: () => TelemetryEvent, sinks?: TelemetrySinks, fields?: TelemetryFieldMap, toolFields?: TelemetryToolFieldMap, sampleRate?: number): void;
|
|
425
453
|
/**
|
|
426
454
|
* Record the `apiKey` of a `registerTools` call so the deferred `sdk_init` flush —
|
|
427
455
|
* which runs with no batch in hand, and may run before any batch exists — can
|
|
@@ -438,6 +466,15 @@ export declare function emitTelemetry(build: () => TelemetryEvent, sinks?: Telem
|
|
|
438
466
|
export declare function captureTelemetryApiKey(apiKey: unknown): void;
|
|
439
467
|
/** The key {@link captureTelemetryApiKey} last recorded, if any. */
|
|
440
468
|
export declare function telemetryApiKey(): string | undefined;
|
|
469
|
+
/**
|
|
470
|
+
* Record the telemetry endpoint for the deferred `sdk_init` flush. Like the captured
|
|
471
|
+
* key, a later usable value overwrites an earlier one while an absent or malformed
|
|
472
|
+
* value leaves the last usable value in place. URL parsing is best-effort because an
|
|
473
|
+
* observability option must never break the visitor's page.
|
|
474
|
+
*/
|
|
475
|
+
export declare function captureTelemetryEndpoint(endpoint: unknown): void;
|
|
476
|
+
/** The endpoint {@link captureTelemetryEndpoint} last recorded, if any. */
|
|
477
|
+
export declare function telemetryEndpoint(): string | undefined;
|
|
441
478
|
/**
|
|
442
479
|
* Cancel the deferred `sdk_init` below. Called by `registerTools({ telemetry: false })`,
|
|
443
480
|
* whose opt-out is page-level and not merely batch-level: the flush describes the
|
|
@@ -462,6 +499,12 @@ export declare function cancelInitEvent(): void;
|
|
|
462
499
|
* Exported for tests; the scheduled timer below is the one that runs it on a real page.
|
|
463
500
|
*/
|
|
464
501
|
export declare function flushInitEvent(sinks?: TelemetrySinks): void;
|
|
502
|
+
/**
|
|
503
|
+
* Give a telemetry-live registration batch the next task's `sdk_init` flush. This
|
|
504
|
+
* keeps the batch's key and endpoint together while leaving the rest of the current
|
|
505
|
+
* task available for a page-level opt-out or another synchronous registration.
|
|
506
|
+
*/
|
|
507
|
+
export declare function deferInitEventForBatch(sinks: TelemetrySinks): void;
|
|
465
508
|
/**
|
|
466
509
|
* Run `run` after `ms`, returning a canceller. Guarded end to end: a runtime with no
|
|
467
510
|
* timers (SSR) and a hostile `setTimeout` (an extension, a fake-timer harness) both
|
|
@@ -469,3 +512,5 @@ export declare function flushInitEvent(sinks?: TelemetrySinks): void;
|
|
|
469
512
|
* `registerTools`. The canceller is safe to call when nothing was scheduled.
|
|
470
513
|
*/
|
|
471
514
|
export declare function afterDelay(run: () => void, ms: number): () => void;
|
|
515
|
+
/** How long a page that never registers gets to expose a real batch configuration. */
|
|
516
|
+
export declare const INIT_FALLBACK_MS = 1000;
|
package/dist/tracking.d.ts
CHANGED
|
@@ -81,8 +81,39 @@ export declare function getOrCreateSessionId(namespace: string): string;
|
|
|
81
81
|
export interface TrackingOptions {
|
|
82
82
|
/** Presence (non-empty) enables the backend transport and namespaces storage. */
|
|
83
83
|
apiKey?: string;
|
|
84
|
+
/**
|
|
85
|
+
* What generated this integration, as `<tool>[/<path>]@<version>` —
|
|
86
|
+
* `webmcp-kit/implement@<plugin version>` for the plugin's codegen,
|
|
87
|
+
* `webmcp-kit/connect-existing-tools@<plugin version>` for its migration of
|
|
88
|
+
* hand-written tools. Reported once per batch as `config.builtWith` on the
|
|
89
|
+
* default-on channel so kit-built sites are countable without a Connect; a
|
|
90
|
+
* hand-written integration leaves it unset. Free text under {@link MAX_BUILT_WITH_LENGTH}
|
|
91
|
+
* characters; anything else is dropped rather than truncated, because a partial
|
|
92
|
+
* `<tool>@<vers` is a wrong answer, not a shorter one.
|
|
93
|
+
*/
|
|
94
|
+
builtWith?: string;
|
|
84
95
|
/** Override the collect endpoint; ignored without `apiKey`. */
|
|
85
96
|
endpoint?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Report under a session identity the host already owns, verbatim, instead of
|
|
99
|
+
* the one this module mints in `sessionStorage`. For a host that is itself the
|
|
100
|
+
* page's session authority — the CDN snippet, whose tab session predates any
|
|
101
|
+
* `registerTools` call, or a Journey runner replaying under a synthetic `syn_…`
|
|
102
|
+
* id — the storage-minted id would split one visit into two sessions the
|
|
103
|
+
* pipeline cannot rejoin, because it lives in an `apiKey`-derived namespace the
|
|
104
|
+
* host does not share.
|
|
105
|
+
*
|
|
106
|
+
* Supplying it takes this module out of the session business entirely for that
|
|
107
|
+
* batch: nothing is read from or written to `sessionStorage`, including
|
|
108
|
+
* `last_seen`, so the 30-minute inactivity boundary is the host's to enforce.
|
|
109
|
+
* The `visitorId` is unaffected — a different lifetime, still this module's.
|
|
110
|
+
*
|
|
111
|
+
* Validated like a stored id ({@link MAX_ID_LENGTH}), because identity fields are
|
|
112
|
+
* copied onto every event without passing through the truncation ladder: an
|
|
113
|
+
* empty, oversized or non-string value falls back to the minted session rather
|
|
114
|
+
* than breaking `boundEventPayload`'s fit guarantee.
|
|
115
|
+
*/
|
|
116
|
+
sessionId?: string;
|
|
86
117
|
/** Emit each event as an OTEL LogRecord via the global `LoggerProvider`. */
|
|
87
118
|
otel?: boolean;
|
|
88
119
|
/**
|
package/dist/transport.d.ts
CHANGED
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
* pages that never key the authenticated one, so a keyless send is the normal
|
|
24
24
|
* case, not a failure — the backend attributes those by CORS `Origin` instead.
|
|
25
25
|
*
|
|
26
|
+
* That path has exactly one observable failure signal, and it is a `console.error`,
|
|
27
|
+
* never a throw or a retry: a *keyed* beacon whose key the backend cannot resolve
|
|
28
|
+
* is still recorded (anonymously) and answered `401`, so a site shipping a broken
|
|
29
|
+
* key is otherwise indistinguishable from a working one. See {@link warnKeyRejected}.
|
|
30
|
+
*
|
|
26
31
|
* The second, independent output is OTEL (`emitOtelLog`): each event becomes a
|
|
27
32
|
* LogRecord on the global `LoggerProvider` via the optional peer dep
|
|
28
33
|
* `@opentelemetry/api-logs`. The host app owns exporters and processing; a
|
|
@@ -65,9 +70,28 @@ export declare function sendToCollect(event: TrackingEvent, config: CollectConfi
|
|
|
65
70
|
* wire. Note that adding the header makes the request non-simple under CORS, so
|
|
66
71
|
* an authenticated beacon costs a preflight the anonymous one does not.
|
|
67
72
|
*
|
|
73
|
+
* The body is sent as `text/plain`, not `application/json`, and that is a cost
|
|
74
|
+
* decision, not a formatting one: `text/plain` is a CORS-safelisted content type,
|
|
75
|
+
* so an anonymous beacon is a "simple request" and the browser sends it with no
|
|
76
|
+
* OPTIONS preflight. With `application/json` every page load paid two requests
|
|
77
|
+
* per event at the edge; at 2026-09 volume that preflight was roughly half of the
|
|
78
|
+
* seven million requests a day the collect API billed. The edge maps `text/plain`
|
|
79
|
+
* to the same template as JSON (ingestion-edge/telemetry.tf), so nothing changes
|
|
80
|
+
* on the wire but the header. The keyed beacon keeps `x-api-key`, which forces a
|
|
81
|
+
* preflight regardless of content type; that path is a small fraction of traffic.
|
|
82
|
+
*
|
|
83
|
+
* A `401` on this path means the key that WAS sent resolved to nothing; the beacon
|
|
84
|
+
* itself was still recorded, anonymously. That is reported through
|
|
85
|
+
* {@link warnKeyRejected} and nothing else — no throw, no retry, no second send.
|
|
86
|
+
* The response is only inspected for an authenticated beacon: an anonymous one has
|
|
87
|
+
* no key to be wrong about, so a `401` there would be a backend fault the developer
|
|
88
|
+
* can do nothing with, and blaming their key for it is worse than silence.
|
|
89
|
+
*
|
|
68
90
|
* The event stays a bare `object` here so this module never has to import the
|
|
69
91
|
* assembled shape from `telemetry.ts` (which imports this function).
|
|
70
92
|
*/
|
|
93
|
+
/** CORS-safelisted, so an anonymous beacon needs no preflight — see sendTelemetry. */
|
|
94
|
+
export declare const TELEMETRY_CONTENT_TYPE = "text/plain";
|
|
71
95
|
export declare function sendTelemetry(event: object, scope?: object, endpoint?: string, apiKey?: unknown): void;
|
|
72
96
|
/** The `Logger.emit` slice we use — structural, no hard OTEL type coupling. */
|
|
73
97
|
interface LoggerLike {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nekuda/webmcp-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-dev.17.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Phase-1 WebMCP SDK: a thin wrapper over document.modelContext that plugin-generated code targets — defineTool + register/unregister lifecycle. This package pins the plugin↔SDK seam; anonymous tool-call tracking (backend transport via apiKey, OTEL LogRecords via otel) is opt-in through registerTools and default-silent, while anonymous usage telemetry is a separate unauthenticated channel that is on by default (opt out with telemetry: false).",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
15
18
|
"publishConfig": {
|
|
16
19
|
"access": "public"
|
|
17
20
|
},
|
|
@@ -36,5 +39,7 @@
|
|
|
36
39
|
"optional": true
|
|
37
40
|
}
|
|
38
41
|
},
|
|
39
|
-
"trustedDependencies": [
|
|
42
|
+
"trustedDependencies": [
|
|
43
|
+
"@biomejs/biome"
|
|
44
|
+
]
|
|
40
45
|
}
|