@nekuda/webmcp-sdk 0.6.0 → 0.7.0-dev.19.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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 — 2026-09-21
4
+
5
+ One change, made twice: a page load costs the ingest edge one request instead of
6
+ four. The wire schema stays at `2` and every event still arrives — the two savings
7
+ are a header and an envelope, not a sample. **A minor, not a patch**, because the
8
+ second of them puts a request shape on the wire that no earlier backend parses: a
9
+ `batch` is quarantined as `unknown_event` by any transform older than the one that
10
+ ships with it. The events an install emits, and the fields on them, are byte for
11
+ byte 0.6.0's.
12
+
13
+ ### Telemetry beacons without a preflight
14
+
15
+ - The telemetry beacon is sent as `text/plain` instead of `application/json`. The
16
+ body is unchanged; the header makes an anonymous beacon a CORS simple request, so
17
+ the browser stops sending an `OPTIONS` preflight before every event. Keyed beacons
18
+ still carry `x-api-key` and still preflight. The edge has mapped `text/plain` to
19
+ the same template since ADR-0014, so no server change is needed.
20
+
21
+ ### One beacon per page load
22
+
23
+ - `sdk_init` and `tool_registration` are held for 50 ms per destination (endpoint
24
+ plus key) and, when two or more are waiting, sent as one `batch` envelope
25
+ (`{"schema":2,"event":"batch","events":[…]}`); a lone event is sent as it was.
26
+ `tool_call` never waits, and drains the queue ahead of itself. `pagehide` and a
27
+ hidden tab flush at once, through the same `keepalive` fetch. The backend explodes
28
+ a batch into its events, so nothing downstream changes shape.
29
+ - Three bounds, each a fallback rather than a drop: at most 16 events per batch (the
30
+ count the backend accepts — the queue flushes at the cap and opens a new window),
31
+ at most 60 KB per request (an over-large batch goes as the per-event requests it
32
+ replaced), and a runtime that cannot schedule a timer sends immediately.
33
+
3
34
  ## 0.6.0 — 2026-09-18
4
35
 
5
36
  Five additive changes, no breaking change. The wire schema stays at `2`. Four of them
@@ -9,20 +40,26 @@ silent when unset, so **an npm consumer who sets none of them sends exactly the
9
40
  registers under a batch that is already posting to the collect edge; it is invisible to
10
41
  enumeration and to `JSON` and never reaches the wire, so it changes no bytes either.
11
42
 
12
- One behaviour is **CDN-build-only**. Page-level telemetry sampling is switched on by a
13
- `bun build --define`, which only `infra/deploy-snippet.sh` passes when it builds the CDN
14
- snippet; neither publish lane passes it, so **an npm install is unsampled** and sends
15
- every page-level event, exactly as before.
43
+ Beside them is a **mechanism nothing switches on**: page-level telemetry sampling exists
44
+ as a `bun build --define`, and no build this release ships passes a rate. **Every
45
+ install, npm and CDN snippet alike, is unsampled** and sends every page-level event,
46
+ exactly as 0.5.0 did.
16
47
 
17
- ### Page-level telemetry sampling — CDN builds only
48
+ ### Page-level telemetry sampling — available, off everywhere
18
49
 
19
- - A build may sample the two page-level telemetry events (`sdk_init`,
50
+ - A build **may** sample the two page-level telemetry events (`sdk_init`,
20
51
  `tool_registration`) with `--define '__WEBMCP_TELEMETRY_SAMPLE_RATE__="0.1"'`. The
21
52
  decision is one coin per page load from the `sessionId`, so a page's events are kept
22
53
  or dropped together, and every kept event carries `sampleRate` for weighting.
23
- `tool_call` is never sampled. Unset, or anything outside (0, 1), sends everything,
24
- which is the npm default and the previous behaviour byte for byte. The CDN snippet
25
- builds at `0.1`: installed sites had reached five million page-level beacons a day.
54
+ `tool_call` is never sampled. Unset, `1`, or anything outside (0, 1), sends
55
+ everything the previous behaviour byte for byte.
56
+ - **Nothing opts in.** Neither publish lane passes the define, and the CDN snippet
57
+ build (`infra/deploy-snippet.sh`) defines the rate as `1`. These two events carry
58
+ the connects, registered-tool counts, refreshes and unique user agents the product
59
+ counts active users from, so they ship complete; the beacon volume gets a fast-path
60
+ ingest queue rather than a tenth of the observations. Turning sampling on for one
61
+ deploy is `SNIPPET_TELEMETRY_SAMPLE_RATE=<rate>` in front of that script, and the
62
+ kept events then say what rate they were kept at.
26
63
 
27
64
  ### `tracking.builtWith`
28
65
 
package/dist/index.d.ts CHANGED
@@ -53,7 +53,8 @@
53
53
  * `tool_registration` once per `registerTools` call, carrying each tool's
54
54
  * outcome; and `tool_call` once per settled invocation, as derived signals only —
55
55
  * no raw `input`, `response`, or error message, unlike point 5. All three go to a
56
- * separate `/v1/telemetry` endpoint, fire-and-forget, no batching; arrival order
56
+ * separate `/v1/telemetry` endpoint, fire-and-forget; the two page-level ones may
57
+ * share one request as a `batch` envelope, `tool_call` never does. Arrival order
57
58
  * is NOT guaranteed, so consumers join on `sessionId` and never on ordering. A
58
59
  * page that configures point 5's `apiKey` has it sent here as `x-api-key` too,
59
60
  * which only adds tenant attribution — an unkeyed page sends anonymously to the
package/dist/index.js CHANGED
@@ -136,7 +136,7 @@ function resolveModelContext(scope = globalThis) {
136
136
  }
137
137
 
138
138
  // src/transport.ts
139
- var INGEST_BASE = "https://ingest.agentlane.com";
139
+ var INGEST_BASE = "https://ingest.agentlane.dev";
140
140
  var DEFAULT_COLLECT_ENDPOINT = `${INGEST_BASE}/v1/collect`;
141
141
  var DEFAULT_TELEMETRY_ENDPOINT = `${INGEST_BASE}/v1/telemetry`;
142
142
  function tryFetch(scope, url, headers, json, onResponse) {
@@ -179,13 +179,14 @@ function sendToCollect(event, config, scope = globalThis) {
179
179
  tryFetch(scope, url, headers, json);
180
180
  } catch {}
181
181
  }
182
+ var TELEMETRY_CONTENT_TYPE = "text/plain";
182
183
  function sendTelemetry(event, scope = globalThis, endpoint, apiKey) {
183
184
  try {
184
185
  const json = JSON.stringify(event);
185
186
  if (json === undefined)
186
187
  return;
187
188
  const url = endpoint || DEFAULT_TELEMETRY_ENDPOINT;
188
- const headers = { "content-type": "application/json" };
189
+ const headers = { "content-type": TELEMETRY_CONTENT_TYPE };
189
190
  const authenticated = typeof apiKey === "string" && apiKey.trim().length > 0;
190
191
  if (authenticated)
191
192
  headers["x-api-key"] = apiKey;
@@ -1104,7 +1105,7 @@ function shapeMetrics(inputSchema) {
1104
1105
 
1105
1106
  // src/telemetry.ts
1106
1107
  var SDK_NAME = "@nekuda/webmcp-sdk";
1107
- var SDK_VERSION = "0.6.0";
1108
+ var SDK_VERSION = "0.7.0-dev.19.1";
1108
1109
  var INSTALL_MODES = ["npm", "cdn_snippet"];
1109
1110
  var SDK_INSTALL_MODE = INSTALL_MODES.find((mode) => mode === (typeof __WEBMCP_INSTALL_MODE__ === "string" ? __WEBMCP_INSTALL_MODE__ : "")) ?? "npm";
1110
1111
  function parseSampleRate(raw) {
@@ -1112,7 +1113,7 @@ function parseSampleRate(raw) {
1112
1113
  return Number.isFinite(rate) && rate > 0 && rate < 1 ? rate : 1;
1113
1114
  }
1114
1115
  var SDK_TELEMETRY_SAMPLE_RATE = parseSampleRate(typeof __WEBMCP_TELEMETRY_SAMPLE_RATE__ === "string" ? __WEBMCP_TELEMETRY_SAMPLE_RATE__ : undefined);
1115
- var SAMPLED_EVENTS = new Set(["sdk_init", "tool_registration"]);
1116
+ var PAGE_LEVEL_EVENTS = new Set(["sdk_init", "tool_registration"]);
1116
1117
  function pageSampled(sessionId, rate = SDK_TELEMETRY_SAMPLE_RATE) {
1117
1118
  if (rate >= 1)
1118
1119
  return true;
@@ -1460,13 +1461,90 @@ function pruneByAllowlist(event, fields = TELEMETRY_FIELDS, toolFields = TELEMET
1460
1461
  return pruned;
1461
1462
  }
1462
1463
  var defaultSinks2 = {
1463
- sendTelemetry: (event) => sendTelemetry(event, globalThis, telemetryEndpoint(), telemetryApiKey())
1464
+ sendTelemetry: (event) => queueOrSend(event, telemetryEndpoint(), telemetryApiKey())
1464
1465
  };
1466
+ var PAGE_BATCH_WINDOW_MS = 50;
1467
+ var PAGE_BATCH_MAX_BYTES = 60000;
1468
+ var PAGE_BATCH_EVENT = "batch";
1469
+ var PAGE_BATCH_EVENTS_KEY = "events";
1470
+ var PAGE_BATCH_MAX_EVENTS = 16;
1471
+ var pendingPageEvents = new Map;
1472
+ function queueOrSend(event, endpoint, apiKey) {
1473
+ const name = String(event.event);
1474
+ if (!PAGE_LEVEL_EVENTS.has(name)) {
1475
+ flushPageEventQueue();
1476
+ sendTelemetry(event, globalThis, endpoint, apiKey);
1477
+ return;
1478
+ }
1479
+ const key = `${endpoint ?? ""}
1480
+ ${typeof apiKey === "string" ? apiKey : ""}`;
1481
+ const pending = pendingPageEvents.get(key);
1482
+ if (pending) {
1483
+ pending.events.push(event);
1484
+ if (pending.events.length >= PAGE_BATCH_MAX_EVENTS)
1485
+ flushPendingBatch(key);
1486
+ return;
1487
+ }
1488
+ const entry = { events: [event], endpoint, apiKey, cancel: () => {} };
1489
+ pendingPageEvents.set(key, entry);
1490
+ const cancel = tryAfterDelay(() => flushPendingBatch(key), PAGE_BATCH_WINDOW_MS);
1491
+ if (!cancel) {
1492
+ flushPendingBatch(key);
1493
+ return;
1494
+ }
1495
+ entry.cancel = cancel;
1496
+ }
1497
+ function flushPendingBatch(key) {
1498
+ const entry = pendingPageEvents.get(key);
1499
+ if (!entry)
1500
+ return;
1501
+ pendingPageEvents.delete(key);
1502
+ entry.cancel();
1503
+ const { events, endpoint, apiKey } = entry;
1504
+ const one = events[0];
1505
+ if (events.length === 1 && one) {
1506
+ sendTelemetry(one, globalThis, endpoint, apiKey);
1507
+ return;
1508
+ }
1509
+ const batch = {
1510
+ schema: TELEMETRY_SCHEMA_VERSION,
1511
+ event: PAGE_BATCH_EVENT,
1512
+ ts: new Date().toISOString(),
1513
+ sessionId: sessionId(),
1514
+ [PAGE_BATCH_EVENTS_KEY]: events
1515
+ };
1516
+ const json = safe(() => JSON.stringify(batch));
1517
+ const bytes = json === undefined ? Number.POSITIVE_INFINITY : utf8Length(json);
1518
+ if (bytes <= PAGE_BATCH_MAX_BYTES) {
1519
+ sendTelemetry(batch, globalThis, endpoint, apiKey);
1520
+ return;
1521
+ }
1522
+ for (const event of events)
1523
+ sendTelemetry(event, globalThis, endpoint, apiKey);
1524
+ }
1525
+ function utf8Length(text) {
1526
+ const encoded = safe(() => new TextEncoder().encode(text).length);
1527
+ return typeof encoded === "number" ? encoded : text.length * 3;
1528
+ }
1529
+ function flushPageEventQueue() {
1530
+ for (const key of Array.from(pendingPageEvents.keys()))
1531
+ flushPendingBatch(key);
1532
+ }
1533
+ safe(() => {
1534
+ const g = globalThis;
1535
+ if (typeof g.addEventListener !== "function")
1536
+ return;
1537
+ g.addEventListener("pagehide", flushPageEventQueue);
1538
+ g.addEventListener("visibilitychange", () => {
1539
+ if (g.document?.visibilityState === "hidden")
1540
+ flushPageEventQueue();
1541
+ });
1542
+ });
1465
1543
  function batchTelemetrySinks(apiKey, endpoint) {
1466
1544
  const own = batchApiKey(apiKey);
1467
1545
  const ownEndpoint = telemetryEndpointFrom(endpoint);
1468
1546
  return {
1469
- sendTelemetry: (event) => sendTelemetry(event, globalThis, ownEndpoint ?? telemetryEndpoint(), own ?? telemetryApiKey())
1547
+ sendTelemetry: (event) => queueOrSend(event, ownEndpoint ?? telemetryEndpoint(), own ?? telemetryApiKey())
1470
1548
  };
1471
1549
  }
1472
1550
  function batchApiKey(apiKey) {
@@ -1484,7 +1562,7 @@ function emitTelemetry(build, sinks = defaultSinks2, fields = TELEMETRY_FIELDS,
1484
1562
  if (!telemetryEnabled())
1485
1563
  return;
1486
1564
  const event = { ...build() };
1487
- if (SAMPLED_EVENTS.has(String(event.event)) && sampleRate < 1) {
1565
+ if (PAGE_LEVEL_EVENTS.has(String(event.event)) && sampleRate < 1) {
1488
1566
  if (!pageSampled(String(event.sessionId), sampleRate))
1489
1567
  return;
1490
1568
  event.sampleRate = sampleRate;
@@ -1542,11 +1620,18 @@ function deferInitEventForBatch(sinks) {
1542
1620
  return;
1543
1621
  afterDelay(() => flushInitEvent(sinks), 0);
1544
1622
  }
1545
- function afterDelay(run, ms) {
1623
+ function tryAfterDelay(run, ms) {
1546
1624
  const schedule = safe(() => globalThis.setTimeout);
1547
1625
  if (typeof schedule !== "function")
1548
- return () => {};
1549
- const handle = safe(() => schedule.call(globalThis, () => run(), ms));
1626
+ return;
1627
+ let armed = false;
1628
+ const handle = safe(() => {
1629
+ const id = schedule.call(globalThis, () => run(), ms);
1630
+ armed = true;
1631
+ return id;
1632
+ });
1633
+ if (!armed)
1634
+ return;
1550
1635
  return () => {
1551
1636
  const clear = safe(() => globalThis.clearTimeout);
1552
1637
  if (typeof clear !== "function")
@@ -1554,6 +1639,9 @@ function afterDelay(run, ms) {
1554
1639
  safe(() => clear.call(globalThis, handle));
1555
1640
  };
1556
1641
  }
1642
+ function afterDelay(run, ms) {
1643
+ return tryAfterDelay(run, ms) ?? (() => {});
1644
+ }
1557
1645
  var INIT_FALLBACK_MS = 1000;
1558
1646
  cancelInitFallback = afterDelay(() => flushInitEvent(), INIT_FALLBACK_MS);
1559
1647
 
@@ -7,16 +7,20 @@
7
7
  * on `registerTools`, with `globalThis.__WEBMCP_TELEMETRY__ = false`, or via
8
8
  * Global Privacy Control.
9
9
  *
10
- * Event schemas, joined on the in-memory `sessionId`, no batching,
11
- * fire-and-forget: `sdk_init` (once per page load, from the one-shot flush this
12
- * module schedules at import time), `tool_registration` (once per `registerTools`
13
- * call), and `tool_call` (once per settled invocation). Events go to the separate
14
- * `/v1/telemetry` endpoint through `sendTelemetry` in `src/transport.ts`.
10
+ * Event schemas, joined on the in-memory `sessionId`, fire-and-forget: `sdk_init`
11
+ * (once per page load, from the one-shot flush this module schedules at import
12
+ * time), `tool_registration` (once per `registerTools` call), and `tool_call`
13
+ * (once per settled invocation). Events go to the separate `/v1/telemetry`
14
+ * endpoint through `sendTelemetry` in `src/transport.ts`; the two page-level ones
15
+ * may share a request as a `batch` envelope (see {@link PAGE_BATCH_WINDOW_MS}).
15
16
  *
16
17
  * No browser storage is touched anywhere in this channel — no `visitorId`, no
17
- * stored `sessionId` (the ePrivacy Art. 5(3) concern). Cross-visit stitching is
18
- * deliberately impossible here; the authenticated `tracking` channel keeps its own
19
- * storage-backed identity, and this one never reads it.
18
+ * stored `sessionId` (the ePrivacy Art. 5(3) concern). Nothing this SDK sends can
19
+ * stitch two visits together; the authenticated `tracking` channel keeps its own
20
+ * storage-backed identity, and this one never reads it. The ingest edge derives a
21
+ * bounded identifier of its own from the request it receives (a daily-salted hash,
22
+ * see *Privacy posture* in `docs/telemetry-schema.md`), which is not something the
23
+ * client participates in or can observe.
20
24
  *
21
25
  * Every field an event may carry is gated by the baked-in allowlists in
22
26
  * `src/telemetry-fields.ts` — one keyed by event path, one by per-tool field.
@@ -370,6 +374,55 @@ export declare function pruneByAllowlist(event: Record<string, unknown>, fields?
370
374
  export interface TelemetrySinks {
371
375
  sendTelemetry: (event: object) => void;
372
376
  }
377
+ /**
378
+ * One beacon per page load, where the page allows it.
379
+ *
380
+ * `sdk_init` flushes one task after import and the first `tool_registration` settles a
381
+ * few milliseconds after `registerTools`, so on most pages the two page-level events
382
+ * leave within the same few milliseconds — as two requests. Each request is billed at
383
+ * the edge, and in 2026-09 page-level beacons were the whole of a seven-million-a-day
384
+ * request bill. Both events are therefore held for {@link PAGE_BATCH_WINDOW_MS} and
385
+ * sent as one `batch` envelope when two or more are waiting; a lone event is sent as
386
+ * itself, byte for byte what it was before. `tool_call` never waits: it is one per
387
+ * invocation, carries the call's own timing, and batching it would hold a signal a
388
+ * navigation could then lose.
389
+ *
390
+ * The queue is keyed on the destination — endpoint plus key — because a batch is one
391
+ * request with one `x-api-key`, and two tenants sharing an origin must never travel
392
+ * under each other's key (see {@link batchTelemetrySinks}). The init flush a batch
393
+ * defers (`deferInitEventForBatch`) uses that batch's sinks, so the common case lands
394
+ * in one queue. A `pagehide` or a hidden tab flushes everything at once, through the
395
+ * same `keepalive` fetch. A runtime that cannot schedule — no `setTimeout`, or one
396
+ * that throws — sends immediately: a queue that could never drain would be a queue
397
+ * that loses, and the events would sit until `pagehide` or forever.
398
+ *
399
+ * The transform explodes a `batch` into its events (internal/telemetrytransform); the
400
+ * server-written attribution is applied to every one, so a batch cannot say anything
401
+ * its events could not have said alone.
402
+ */
403
+ export declare const PAGE_BATCH_WINDOW_MS = 50;
404
+ /** Under `fetch(keepalive)`'s 64 KB in-flight ceiling, with room for the wrapper. */
405
+ export declare const PAGE_BATCH_MAX_BYTES = 60000;
406
+ /**
407
+ * The batch envelope's `event` name and member-list key, and the member cap — the
408
+ * three spellings this file shares with the Go transform that explodes a batch
409
+ * (`internal/telemetrytransform`: `eventBatch`, `wireBatchEvents`, `MaxBatchEvents`).
410
+ * Nothing compiles the two together, so `analytics/telemetry_batch_contract_test.go`
411
+ * reads these three declarations out of this source and asserts they equal the Go
412
+ * constants. Exported for that gate, and used below rather than restated: a pin
413
+ * against a constant the envelope does not actually use is a tautology.
414
+ *
415
+ * The cap is the one the SERVER enforces. A seventeenth member does not make a
416
+ * larger batch, it makes an `unparseable_body` — one request in which every event is
417
+ * lost — so the queue flushes at the cap and starts a new batch instead. A page can
418
+ * reach it: `registerTools` may be called many times on an SPA entry, and each call
419
+ * emits its own `tool_registration`.
420
+ */
421
+ export declare const PAGE_BATCH_EVENT = "batch";
422
+ export declare const PAGE_BATCH_EVENTS_KEY = "events";
423
+ export declare const PAGE_BATCH_MAX_EVENTS = 16;
424
+ /** Send every waiting page-level batch now — on `pagehide`, a hidden tab, or a test. */
425
+ export declare function flushPageEventQueue(): void;
373
426
  /**
374
427
  * The sink for the two events a *batch* owns. They carry that batch's tool identity —
375
428
  * `stableKey`, `routeTemplate`, `errorSignature` — so they authenticate with that
@@ -506,10 +559,22 @@ export declare function flushInitEvent(sinks?: TelemetrySinks): void;
506
559
  */
507
560
  export declare function deferInitEventForBatch(sinks: TelemetrySinks): void;
508
561
  /**
509
- * Run `run` after `ms`, returning a canceller. Guarded end to end: a runtime with no
510
- * timers (SSR) and a hostile `setTimeout` (an extension, a fake-timer harness) both
511
- * mean the callback never runs, never a throw out of the import or out of
512
- * `registerTools`. The canceller is safe to call when nothing was scheduled.
562
+ * Run `run` after `ms`, returning a canceller or `undefined` when nothing was
563
+ * scheduled. Guarded end to end: a runtime with no timers (SSR) and a hostile
564
+ * `setTimeout` (an extension, a fake-timer harness) both mean the callback never
565
+ * runs, never a throw out of the import or out of `registerTools`.
566
+ *
567
+ * The `undefined` is the part {@link afterDelay} cannot express, and it matters to
568
+ * exactly one caller: the page-level batch holds events until its timer fires, so a
569
+ * `setTimeout` that exists and THROWS would leave them queued until `pagehide` — a
570
+ * silent loss on any page the user closes from a different tab. A caller that only
571
+ * defers work is happy with a no-op; a caller that defers DATA has to know.
572
+ */
573
+ export declare function tryAfterDelay(run: () => void, ms: number): (() => void) | undefined;
574
+ /**
575
+ * {@link tryAfterDelay} for the callers that have nothing to do when the runtime
576
+ * cannot schedule: the canceller is safe to call when nothing was scheduled, and an
577
+ * unscheduled flush is simply one that never happens.
513
578
  */
514
579
  export declare function afterDelay(run: () => void, ms: number): () => void;
515
580
  /** How long a page that never registers gets to expose a real batch configuration. */
@@ -56,6 +56,8 @@ export interface CollectConfig {
56
56
  * a no-`fetch` environment is a silent no-op.
57
57
  */
58
58
  export declare function sendToCollect(event: TrackingEvent, config: CollectConfig, scope?: object): void;
59
+ /** CORS-safelisted, so an anonymous beacon needs no preflight — see sendTelemetry. */
60
+ export declare const TELEMETRY_CONTENT_TYPE = "text/plain";
59
61
  /**
60
62
  * POST a usage-telemetry event to the telemetry endpoint via `fetch(keepalive)`.
61
63
  * The path is the same whether or not `apiKey` is given: the header only *adds*
@@ -70,6 +72,16 @@ export declare function sendToCollect(event: TrackingEvent, config: CollectConfi
70
72
  * wire. Note that adding the header makes the request non-simple under CORS, so
71
73
  * an authenticated beacon costs a preflight the anonymous one does not.
72
74
  *
75
+ * The body is sent as `text/plain`, not `application/json`, and that is a cost
76
+ * decision, not a formatting one: `text/plain` is a CORS-safelisted content type,
77
+ * so an anonymous beacon is a "simple request" and the browser sends it with no
78
+ * OPTIONS preflight. With `application/json` every page load paid two requests
79
+ * per event at the edge; at 2026-09 volume that preflight was roughly half of the
80
+ * seven million requests a day the collect API billed. The edge maps `text/plain`
81
+ * to the same template as JSON (ingestion-edge/telemetry.tf), so nothing changes
82
+ * on the wire but the header. The keyed beacon keeps `x-api-key`, which forces a
83
+ * preflight regardless of content type; that path is a small fraction of traffic.
84
+ *
73
85
  * A `401` on this path means the key that WAS sent resolved to nothing; the beacon
74
86
  * itself was still recorded, anonymously. That is reported through
75
87
  * {@link warnKeyRejected} and nothing else — no throw, no retry, no second send.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nekuda/webmcp-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-dev.19.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": ["dist", "CHANGELOG.md"],
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": ["@biomejs/biome"]
42
+ "trustedDependencies": [
43
+ "@biomejs/biome"
44
+ ]
40
45
  }