autotel-devtools 23.1.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cli.cjs +2 -2
  2. package/dist/cli.js +2 -2
  3. package/dist/{error-aggregator-DSwUvgFF.d.cts → error-aggregator-C0at0wzF.d.cts} +1 -1
  4. package/dist/{error-aggregator-B52JI8jL.d.ts → error-aggregator-C25_JiVI.d.ts} +1 -1
  5. package/dist/{exporter-OoWkSMCR.d.cts → exporter-AkgWRDpc.d.cts} +25 -2
  6. package/dist/{exporter-C88W22vY.d.ts → exporter-yo7lLDIs.d.ts} +25 -2
  7. package/dist/fullpage.global.js +37 -30
  8. package/dist/genai/index.d.cts +1 -1
  9. package/dist/genai/index.d.ts +1 -1
  10. package/dist/{grpc-D0B3P9sI.cjs → grpc-CSW1Evnt.cjs} +1 -1
  11. package/dist/{grpc-DY-C1jSU.js → grpc-D1tt8UPi.js} +1 -1
  12. package/dist/{http-1afd_01N.cjs → http-CahjEnpE.cjs} +237 -0
  13. package/dist/{http-gk1xapnA.js → http-z8UbJsqk.js} +238 -1
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.d.cts +4 -4
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/server/exporter.d.cts +1 -1
  19. package/dist/server/exporter.d.ts +1 -1
  20. package/dist/server/index.cjs +2 -2
  21. package/dist/server/index.d.cts +4 -4
  22. package/dist/server/index.d.ts +4 -4
  23. package/dist/server/index.js +2 -2
  24. package/dist/types-B86Qkqg5.d.cts +120 -0
  25. package/dist/types-B86Qkqg5.d.ts +120 -0
  26. package/dist/{types-DM8y4A9Z.d.ts → types-BhbHVeiy.d.ts} +1 -1
  27. package/dist/{types-B0tjwFqj.d.cts → types-C-ORUrT2.d.cts} +1 -1
  28. package/dist/widget.global.js +14 -14
  29. package/dist/wire/index.d.cts +1 -1
  30. package/dist/wire/index.d.ts +1 -1
  31. package/package.json +2 -2
  32. package/dist/types-DHDvZnnn.d.cts +0 -47
  33. package/dist/types-DHDvZnnn.d.ts +0 -47
@@ -1,4 +1,4 @@
1
- import { r as SpanData } from "../types-DHDvZnnn.cjs";
1
+ import { r as SpanData } from "../types-B86Qkqg5.cjs";
2
2
  //#region src/widget/genai/detect.d.ts
3
3
  declare function isGenAiSpan(span: SpanData): boolean;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { r as SpanData } from "../types-DHDvZnnn.js";
1
+ import { r as SpanData } from "../types-B86Qkqg5.js";
2
2
  //#region src/widget/genai/detect.d.ts
3
3
  declare function isGenAiSpan(span: SpanData): boolean;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- const require_http = require('./http-1afd_01N.cjs');
1
+ const require_http = require('./http-CahjEnpE.cjs');
2
2
  let _grpc_grpc_js = require("@grpc/grpc-js");
3
3
 
4
4
  //#region src/server/grpc.ts
@@ -1,4 +1,4 @@
1
- import { c as decodeOtlpTraceRequest, o as decodeOtlpLogsRequest, s as decodeOtlpMetricsRequest } from "./http-gk1xapnA.js";
1
+ import { c as decodeOtlpTraceRequest, o as decodeOtlpLogsRequest, s as decodeOtlpMetricsRequest } from "./http-z8UbJsqk.js";
2
2
  import { Server, ServerCredentials, status } from "@grpc/grpc-js";
3
3
 
4
4
  //#region src/server/grpc.ts
@@ -316,6 +316,181 @@ var ErrorAggregator = class {
316
316
  }
317
317
  };
318
318
 
319
+ //#endregion
320
+ //#region src/server/webmcp-aggregator.ts
321
+ /** Span names this fold consumes. Everything else in the trace is ignored. */
322
+ const INSTALL = "webmcp.install";
323
+ const REGISTER = "webmcp.tool.register";
324
+ const EXECUTE = "webmcp.tool.execute";
325
+ const WITHDRAW = "webmcp.tool.withdraw";
326
+ /** How many recent executions a tool carries. Enough to see a pattern, not a log. */
327
+ const RECENT_CALLS = 5;
328
+ /**
329
+ * `{"content":[{"type":"text","text":` + `}]}` and the quoting around it.
330
+ *
331
+ * Chrome does not unwrap the MCP envelope, so an enveloped result spends these
332
+ * bytes of the agent's context on structure carrying no information. Measured
333
+ * rather than derived: 45 bytes wrapped versus 13 plain for the same text.
334
+ */
335
+ const ENVELOPE_OVERHEAD_BYTES = 32;
336
+ const median = (values) => {
337
+ if (values.length === 0) return 0;
338
+ const sorted = [...values].sort((a, b) => a - b);
339
+ const mid = Math.floor(sorted.length / 2);
340
+ return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
341
+ };
342
+ const csv = (value) => {
343
+ const raw = require_json_fields.asString(value);
344
+ if (!raw) return [];
345
+ return raw.split(",").map((part) => part.trim()).filter(Boolean);
346
+ };
347
+ /**
348
+ * Spans with no installation id — emitted by an `autotel-webmcp` older than the
349
+ * lifecycle release. Grouping them under one key per service keeps them
350
+ * readable as a single legacy installation rather than scattering one
351
+ * installation per tool.
352
+ */
353
+ const UNKNOWN_INSTALLATION = "unknown";
354
+ const isWebMcpSpan = (span) => span.name === INSTALL || span.name === REGISTER || span.name === EXECUTE || span.name === WITHDRAW;
355
+ function foldWebMcpTools(traces, activityWindow) {
356
+ const tools = /* @__PURE__ */ new Map();
357
+ /** Installations seen, and whether each one ever registered anything. */
358
+ const installations = /* @__PURE__ */ new Map();
359
+ const latestInstallation = /* @__PURE__ */ new Map();
360
+ const spans = traces.flatMap((trace) => trace.spans.map((span) => ({
361
+ span,
362
+ service: trace.service
363
+ }))).sort((a, b) => a.span.startTime - b.span.startTime);
364
+ for (const { span, service } of spans) {
365
+ if (!isWebMcpSpan(span)) continue;
366
+ if (activityWindow && span.startTime > activityWindow.end) continue;
367
+ const attrs = span.attributes ?? {};
368
+ const installationId = require_json_fields.asString(attrs["webmcp.installation.id"]) ?? UNKNOWN_INSTALLATION;
369
+ const installationKey = `${service}${installationId}`;
370
+ if (!installations.has(installationKey)) installations.set(installationKey, false);
371
+ if (span.name === INSTALL) {
372
+ latestInstallation.set(service, installationId);
373
+ continue;
374
+ }
375
+ const name = require_json_fields.asString(attrs["webmcp.tool.name"]) ?? require_json_fields.asString(attrs["gen_ai.tool.name"]);
376
+ if (!name) continue;
377
+ const key = `${service}${installationId}${name}`;
378
+ let tool = tools.get(key);
379
+ if (!tool) {
380
+ tool = {
381
+ name,
382
+ installationId,
383
+ service,
384
+ sessionId: require_json_fields.asString(attrs["session.id"]),
385
+ observedAtRegistration: false,
386
+ offered: false,
387
+ firstSeen: span.startTime,
388
+ lastSeen: span.startTime,
389
+ annotationsSent: [],
390
+ annotationsDropped: [],
391
+ calls: 0,
392
+ errors: 0,
393
+ envelopeCalls: 0,
394
+ envelopeBytes: 0,
395
+ substitutedCalls: 0,
396
+ resultBytes: 0,
397
+ medianResultBytes: 0,
398
+ resultSizes: [],
399
+ recentCalls: [],
400
+ traceId: span.traceId,
401
+ spanId: span.spanId
402
+ };
403
+ tools.set(key, tool);
404
+ }
405
+ tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
406
+ tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
407
+ tool.sessionId ??= require_json_fields.asString(attrs["session.id"]);
408
+ switch (span.name) {
409
+ case REGISTER:
410
+ installations.set(installationKey, true);
411
+ tool.observedAtRegistration = true;
412
+ tool.offered = true;
413
+ tool.descriptionLength = require_json_fields.asNumber(attrs["webmcp.tool.description.length"]);
414
+ tool.hasInputSchema = require_json_fields.asBoolean(attrs["webmcp.tool.has_input_schema"]);
415
+ tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
416
+ tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
417
+ tool.traceId = span.traceId;
418
+ tool.spanId = span.spanId;
419
+ break;
420
+ case WITHDRAW:
421
+ tool.offered = false;
422
+ break;
423
+ case EXECUTE: {
424
+ if (activityWindow && span.startTime < activityWindow.start) break;
425
+ tool.calls += 1;
426
+ const bytes = require_json_fields.asNumber(attrs["webmcp.result.bytes"]) ?? 0;
427
+ tool.resultBytes += bytes;
428
+ tool.resultSizes.push(bytes);
429
+ if (require_json_fields.asBoolean(attrs["webmcp.result.envelope"])) {
430
+ tool.envelopeCalls += 1;
431
+ tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
432
+ }
433
+ if (require_json_fields.asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
434
+ const failed = span.status.code === "ERROR" || (require_json_fields.asBoolean(attrs["webmcp.result.error"]) ?? false) || require_json_fields.asString(attrs["error.type"]) !== void 0;
435
+ if (failed) tool.errors += 1;
436
+ tool.recentCalls.push({
437
+ timestamp: span.startTime,
438
+ durationMs: span.duration,
439
+ resultBytes: bytes,
440
+ resultType: require_json_fields.asString(attrs["webmcp.result.type"]),
441
+ envelope: require_json_fields.asBoolean(attrs["webmcp.result.envelope"]) ?? false,
442
+ substituted: require_json_fields.asBoolean(attrs["webmcp.result.substituted"]) ?? false,
443
+ error: failed,
444
+ input: require_json_fields.asString(attrs["webmcp.input"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.arguments"]),
445
+ result: require_json_fields.asString(attrs["webmcp.result"]) ?? require_json_fields.asString(attrs["gen_ai.tool.call.result"]),
446
+ traceId: span.traceId,
447
+ spanId: span.spanId
448
+ });
449
+ break;
450
+ }
451
+ }
452
+ }
453
+ const list = [...tools.values()].map((tool) => {
454
+ const { resultSizes, ...rest } = tool;
455
+ return {
456
+ ...rest,
457
+ offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
458
+ medianResultBytes: median(resultSizes),
459
+ recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
460
+ };
461
+ });
462
+ list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
463
+ return {
464
+ tools: list,
465
+ summary: summarize(list, installations)
466
+ };
467
+ }
468
+ function summarize(tools, installations) {
469
+ const summary = {
470
+ installations: installations.size,
471
+ emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
472
+ toolsOffered: 0,
473
+ toolsWithdrawn: 0,
474
+ calls: 0,
475
+ errors: 0,
476
+ resultBytes: 0,
477
+ envelopeBytes: 0,
478
+ toolsWithDroppedAnnotations: 0,
479
+ toolsWithoutInputSchema: 0
480
+ };
481
+ for (const tool of tools) {
482
+ if (tool.offered) summary.toolsOffered += 1;
483
+ else summary.toolsWithdrawn += 1;
484
+ summary.calls += tool.calls;
485
+ summary.errors += tool.errors;
486
+ summary.resultBytes += tool.resultBytes;
487
+ summary.envelopeBytes += tool.envelopeBytes;
488
+ if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
489
+ if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
490
+ }
491
+ return summary;
492
+ }
493
+
319
494
  //#endregion
320
495
  //#region src/server/telemetry-limits.ts
321
496
  const defaultLimit = 100;
@@ -2754,6 +2929,44 @@ var DevtoolsServer = class {
2754
2929
  } while (cursor);
2755
2930
  return { errors: aggregator.getErrorGroups() };
2756
2931
  }
2932
+ /**
2933
+ * Fold WebMCP lifecycle history through the window end into the tool surface
2934
+ * an agent is offered, while counting executions only inside the window.
2935
+ *
2936
+ * Drains every page rather than folding the first one: an inventory built
2937
+ * from a page of results does not fail, it *under-reports* — "2 tools dropped
2938
+ * annotations" when the answer is 6 — and gets more wrong the more traffic
2939
+ * there is, which is backwards for an observability answer.
2940
+ *
2941
+ * The span-name filter is composed here rather than accepted from the client:
2942
+ * this endpoint answers one question, and a caller-supplied predicate could
2943
+ * only narrow it into a wrong answer.
2944
+ */
2945
+ queryWebMcp(args) {
2946
+ const traces = [];
2947
+ const seenCursors = /* @__PURE__ */ new Set();
2948
+ let cursor;
2949
+ do {
2950
+ const result = this.store.queryTraces({
2951
+ query: "name ^ \"webmcp.\"",
2952
+ window: args.window ? {
2953
+ start: 0,
2954
+ end: args.window.end
2955
+ } : void 0,
2956
+ limit: args.limit,
2957
+ cursor
2958
+ });
2959
+ if (result.errors) return {
2960
+ webmcp: foldWebMcpTools([]),
2961
+ errors_parse: result.errors
2962
+ };
2963
+ traces.push(...result.traces);
2964
+ cursor = result.nextCursor ?? void 0;
2965
+ if (cursor && seenCursors.has(cursor)) break;
2966
+ if (cursor) seenCursors.add(cursor);
2967
+ } while (cursor);
2968
+ return { webmcp: foldWebMcpTools(traces, args.window) };
2969
+ }
2757
2970
  /** Run a log query against the durable store. */
2758
2971
  queryLogs(args) {
2759
2972
  return this.store.queryLogs(args);
@@ -3757,6 +3970,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
3757
3970
  }
3758
3971
  return;
3759
3972
  }
3973
+ if (req.method === "POST" && url === "/api/query/webmcp") {
3974
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
3975
+ sendJson(res, 403, { error: "Forbidden" });
3976
+ return;
3977
+ }
3978
+ try {
3979
+ const body = await readJsonBody(req);
3980
+ const result = devtools.queryWebMcp({
3981
+ window: body.window,
3982
+ limit: body.limit
3983
+ });
3984
+ if (result.errors_parse) {
3985
+ sendJson(res, 400, { errors: result.errors_parse });
3986
+ return;
3987
+ }
3988
+ sendJson(res, 200, { webmcp: result.webmcp });
3989
+ } catch (e) {
3990
+ sendJson(res, 400, {
3991
+ error: "Invalid query request",
3992
+ message: e instanceof Error ? e.message : String(e)
3993
+ });
3994
+ }
3995
+ return;
3996
+ }
3760
3997
  if (req.method === "GET" && url === "/api/metrics") {
3761
3998
  if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
3762
3999
  sendJson(res, 403, { error: "Forbidden" });
@@ -1,5 +1,5 @@
1
1
  import { i as encodeTraces } from "./wire-2Rmfg6IT.js";
2
- import { i as asString, o as stringAttr, t as asObject } from "./json-fields-CPjKZ2WH.js";
2
+ import { i as asString, n as asBoolean, o as stringAttr, r as asNumber, t as asObject } from "./json-fields-CPjKZ2WH.js";
3
3
  import { t as pickRoot } from "./trace-root-EHnvuA7f.js";
4
4
  import { a as compileWhere, t as parse } from "./parse-D_RmPPQs.js";
5
5
  import { t as getResourceName } from "./resource-utils-B4UVvfnH.js";
@@ -287,6 +287,181 @@ var ErrorAggregator = class {
287
287
  }
288
288
  };
289
289
 
290
+ //#endregion
291
+ //#region src/server/webmcp-aggregator.ts
292
+ /** Span names this fold consumes. Everything else in the trace is ignored. */
293
+ const INSTALL = "webmcp.install";
294
+ const REGISTER = "webmcp.tool.register";
295
+ const EXECUTE = "webmcp.tool.execute";
296
+ const WITHDRAW = "webmcp.tool.withdraw";
297
+ /** How many recent executions a tool carries. Enough to see a pattern, not a log. */
298
+ const RECENT_CALLS = 5;
299
+ /**
300
+ * `{"content":[{"type":"text","text":` + `}]}` and the quoting around it.
301
+ *
302
+ * Chrome does not unwrap the MCP envelope, so an enveloped result spends these
303
+ * bytes of the agent's context on structure carrying no information. Measured
304
+ * rather than derived: 45 bytes wrapped versus 13 plain for the same text.
305
+ */
306
+ const ENVELOPE_OVERHEAD_BYTES = 32;
307
+ const median = (values) => {
308
+ if (values.length === 0) return 0;
309
+ const sorted = [...values].sort((a, b) => a - b);
310
+ const mid = Math.floor(sorted.length / 2);
311
+ return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
312
+ };
313
+ const csv = (value) => {
314
+ const raw = asString(value);
315
+ if (!raw) return [];
316
+ return raw.split(",").map((part) => part.trim()).filter(Boolean);
317
+ };
318
+ /**
319
+ * Spans with no installation id — emitted by an `autotel-webmcp` older than the
320
+ * lifecycle release. Grouping them under one key per service keeps them
321
+ * readable as a single legacy installation rather than scattering one
322
+ * installation per tool.
323
+ */
324
+ const UNKNOWN_INSTALLATION = "unknown";
325
+ const isWebMcpSpan = (span) => span.name === INSTALL || span.name === REGISTER || span.name === EXECUTE || span.name === WITHDRAW;
326
+ function foldWebMcpTools(traces, activityWindow) {
327
+ const tools = /* @__PURE__ */ new Map();
328
+ /** Installations seen, and whether each one ever registered anything. */
329
+ const installations = /* @__PURE__ */ new Map();
330
+ const latestInstallation = /* @__PURE__ */ new Map();
331
+ const spans = traces.flatMap((trace) => trace.spans.map((span) => ({
332
+ span,
333
+ service: trace.service
334
+ }))).sort((a, b) => a.span.startTime - b.span.startTime);
335
+ for (const { span, service } of spans) {
336
+ if (!isWebMcpSpan(span)) continue;
337
+ if (activityWindow && span.startTime > activityWindow.end) continue;
338
+ const attrs = span.attributes ?? {};
339
+ const installationId = asString(attrs["webmcp.installation.id"]) ?? UNKNOWN_INSTALLATION;
340
+ const installationKey = `${service}${installationId}`;
341
+ if (!installations.has(installationKey)) installations.set(installationKey, false);
342
+ if (span.name === INSTALL) {
343
+ latestInstallation.set(service, installationId);
344
+ continue;
345
+ }
346
+ const name = asString(attrs["webmcp.tool.name"]) ?? asString(attrs["gen_ai.tool.name"]);
347
+ if (!name) continue;
348
+ const key = `${service}${installationId}${name}`;
349
+ let tool = tools.get(key);
350
+ if (!tool) {
351
+ tool = {
352
+ name,
353
+ installationId,
354
+ service,
355
+ sessionId: asString(attrs["session.id"]),
356
+ observedAtRegistration: false,
357
+ offered: false,
358
+ firstSeen: span.startTime,
359
+ lastSeen: span.startTime,
360
+ annotationsSent: [],
361
+ annotationsDropped: [],
362
+ calls: 0,
363
+ errors: 0,
364
+ envelopeCalls: 0,
365
+ envelopeBytes: 0,
366
+ substitutedCalls: 0,
367
+ resultBytes: 0,
368
+ medianResultBytes: 0,
369
+ resultSizes: [],
370
+ recentCalls: [],
371
+ traceId: span.traceId,
372
+ spanId: span.spanId
373
+ };
374
+ tools.set(key, tool);
375
+ }
376
+ tool.firstSeen = Math.min(tool.firstSeen, span.startTime);
377
+ tool.lastSeen = Math.max(tool.lastSeen, span.startTime);
378
+ tool.sessionId ??= asString(attrs["session.id"]);
379
+ switch (span.name) {
380
+ case REGISTER:
381
+ installations.set(installationKey, true);
382
+ tool.observedAtRegistration = true;
383
+ tool.offered = true;
384
+ tool.descriptionLength = asNumber(attrs["webmcp.tool.description.length"]);
385
+ tool.hasInputSchema = asBoolean(attrs["webmcp.tool.has_input_schema"]);
386
+ tool.annotationsSent = csv(attrs["webmcp.annotations.sent"]);
387
+ tool.annotationsDropped = csv(attrs["webmcp.annotations.dropped"]);
388
+ tool.traceId = span.traceId;
389
+ tool.spanId = span.spanId;
390
+ break;
391
+ case WITHDRAW:
392
+ tool.offered = false;
393
+ break;
394
+ case EXECUTE: {
395
+ if (activityWindow && span.startTime < activityWindow.start) break;
396
+ tool.calls += 1;
397
+ const bytes = asNumber(attrs["webmcp.result.bytes"]) ?? 0;
398
+ tool.resultBytes += bytes;
399
+ tool.resultSizes.push(bytes);
400
+ if (asBoolean(attrs["webmcp.result.envelope"])) {
401
+ tool.envelopeCalls += 1;
402
+ tool.envelopeBytes += ENVELOPE_OVERHEAD_BYTES;
403
+ }
404
+ if (asBoolean(attrs["webmcp.result.substituted"])) tool.substitutedCalls += 1;
405
+ const failed = span.status.code === "ERROR" || (asBoolean(attrs["webmcp.result.error"]) ?? false) || asString(attrs["error.type"]) !== void 0;
406
+ if (failed) tool.errors += 1;
407
+ tool.recentCalls.push({
408
+ timestamp: span.startTime,
409
+ durationMs: span.duration,
410
+ resultBytes: bytes,
411
+ resultType: asString(attrs["webmcp.result.type"]),
412
+ envelope: asBoolean(attrs["webmcp.result.envelope"]) ?? false,
413
+ substituted: asBoolean(attrs["webmcp.result.substituted"]) ?? false,
414
+ error: failed,
415
+ input: asString(attrs["webmcp.input"]) ?? asString(attrs["gen_ai.tool.call.arguments"]),
416
+ result: asString(attrs["webmcp.result"]) ?? asString(attrs["gen_ai.tool.call.result"]),
417
+ traceId: span.traceId,
418
+ spanId: span.spanId
419
+ });
420
+ break;
421
+ }
422
+ }
423
+ }
424
+ const list = [...tools.values()].map((tool) => {
425
+ const { resultSizes, ...rest } = tool;
426
+ return {
427
+ ...rest,
428
+ offered: rest.offered && (latestInstallation.get(rest.service) ?? rest.installationId) === rest.installationId,
429
+ medianResultBytes: median(resultSizes),
430
+ recentCalls: [...tool.recentCalls].sort((a, b) => b.timestamp - a.timestamp).slice(0, RECENT_CALLS)
431
+ };
432
+ });
433
+ list.sort((a, b) => Number(b.offered) - Number(a.offered) || b.calls - a.calls || a.name.localeCompare(b.name));
434
+ return {
435
+ tools: list,
436
+ summary: summarize(list, installations)
437
+ };
438
+ }
439
+ function summarize(tools, installations) {
440
+ const summary = {
441
+ installations: installations.size,
442
+ emptyInstallations: [...installations.values()].filter((saw) => !saw).length,
443
+ toolsOffered: 0,
444
+ toolsWithdrawn: 0,
445
+ calls: 0,
446
+ errors: 0,
447
+ resultBytes: 0,
448
+ envelopeBytes: 0,
449
+ toolsWithDroppedAnnotations: 0,
450
+ toolsWithoutInputSchema: 0
451
+ };
452
+ for (const tool of tools) {
453
+ if (tool.offered) summary.toolsOffered += 1;
454
+ else summary.toolsWithdrawn += 1;
455
+ summary.calls += tool.calls;
456
+ summary.errors += tool.errors;
457
+ summary.resultBytes += tool.resultBytes;
458
+ summary.envelopeBytes += tool.envelopeBytes;
459
+ if (tool.annotationsDropped.length > 0) summary.toolsWithDroppedAnnotations += 1;
460
+ if (tool.observedAtRegistration && tool.hasInputSchema === false) summary.toolsWithoutInputSchema += 1;
461
+ }
462
+ return summary;
463
+ }
464
+
290
465
  //#endregion
291
466
  //#region src/server/telemetry-limits.ts
292
467
  const defaultLimit = 100;
@@ -2725,6 +2900,44 @@ var DevtoolsServer = class {
2725
2900
  } while (cursor);
2726
2901
  return { errors: aggregator.getErrorGroups() };
2727
2902
  }
2903
+ /**
2904
+ * Fold WebMCP lifecycle history through the window end into the tool surface
2905
+ * an agent is offered, while counting executions only inside the window.
2906
+ *
2907
+ * Drains every page rather than folding the first one: an inventory built
2908
+ * from a page of results does not fail, it *under-reports* — "2 tools dropped
2909
+ * annotations" when the answer is 6 — and gets more wrong the more traffic
2910
+ * there is, which is backwards for an observability answer.
2911
+ *
2912
+ * The span-name filter is composed here rather than accepted from the client:
2913
+ * this endpoint answers one question, and a caller-supplied predicate could
2914
+ * only narrow it into a wrong answer.
2915
+ */
2916
+ queryWebMcp(args) {
2917
+ const traces = [];
2918
+ const seenCursors = /* @__PURE__ */ new Set();
2919
+ let cursor;
2920
+ do {
2921
+ const result = this.store.queryTraces({
2922
+ query: "name ^ \"webmcp.\"",
2923
+ window: args.window ? {
2924
+ start: 0,
2925
+ end: args.window.end
2926
+ } : void 0,
2927
+ limit: args.limit,
2928
+ cursor
2929
+ });
2930
+ if (result.errors) return {
2931
+ webmcp: foldWebMcpTools([]),
2932
+ errors_parse: result.errors
2933
+ };
2934
+ traces.push(...result.traces);
2935
+ cursor = result.nextCursor ?? void 0;
2936
+ if (cursor && seenCursors.has(cursor)) break;
2937
+ if (cursor) seenCursors.add(cursor);
2938
+ } while (cursor);
2939
+ return { webmcp: foldWebMcpTools(traces, args.window) };
2940
+ }
2728
2941
  /** Run a log query against the durable store. */
2729
2942
  queryLogs(args) {
2730
2943
  return this.store.queryLogs(args);
@@ -3728,6 +3941,30 @@ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
3728
3941
  }
3729
3942
  return;
3730
3943
  }
3944
+ if (req.method === "POST" && url === "/api/query/webmcp") {
3945
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
3946
+ sendJson(res, 403, { error: "Forbidden" });
3947
+ return;
3948
+ }
3949
+ try {
3950
+ const body = await readJsonBody(req);
3951
+ const result = devtools.queryWebMcp({
3952
+ window: body.window,
3953
+ limit: body.limit
3954
+ });
3955
+ if (result.errors_parse) {
3956
+ sendJson(res, 400, { errors: result.errors_parse });
3957
+ return;
3958
+ }
3959
+ sendJson(res, 200, { webmcp: result.webmcp });
3960
+ } catch (e) {
3961
+ sendJson(res, 400, {
3962
+ error: "Invalid query request",
3963
+ message: e instanceof Error ? e.message : String(e)
3964
+ });
3965
+ }
3966
+ return;
3967
+ }
3731
3968
  if (req.method === "GET" && url === "/api/metrics") {
3732
3969
  if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
3733
3970
  sendJson(res, 403, { error: "Forbidden" });
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_http = require('./http-1afd_01N.cjs');
2
+ const require_http = require('./http-CahjEnpE.cjs');
3
3
  const require_listen = require('./listen-l09RRHht.cjs');
4
4
  const require_server_exporter = require('./server/exporter.cjs');
5
5
  const require_server_log_exporter = require('./server/log-exporter.cjs');
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { n as SpanAttributes, t as AttributeValue } from "./types-DHDvZnnn.cjs";
2
- import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-B0tjwFqj.cjs";
3
- import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-OoWkSMCR.cjs";
1
+ import { n as SpanAttributes, t as AttributeValue } from "./types-B86Qkqg5.cjs";
2
+ import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-C-ORUrT2.cjs";
3
+ import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-AkgWRDpc.cjs";
4
4
  import { DevtoolsLogExporter } from "./server/log-exporter.cjs";
5
5
  import { DevtoolsRemoteExporter } from "./server/remote-exporter.cjs";
6
- import { t as ErrorAggregator } from "./error-aggregator-DSwUvgFF.cjs";
6
+ import { t as ErrorAggregator } from "./error-aggregator-C0at0wzF.cjs";
7
7
  import { Server } from "node:http";
8
8
  //#region src/index.d.ts
9
9
  interface CreateDevtoolsOptions {
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { n as SpanAttributes, t as AttributeValue } from "./types-DHDvZnnn.js";
2
- import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-DM8y4A9Z.js";
3
- import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-C88W22vY.js";
1
+ import { n as SpanAttributes, t as AttributeValue } from "./types-B86Qkqg5.js";
2
+ import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, t as DevtoolsData } from "./types-BhbHVeiy.js";
3
+ import { n as DevtoolsServer, t as DevtoolsSpanExporter } from "./exporter-yo7lLDIs.js";
4
4
  import { DevtoolsLogExporter } from "./server/log-exporter.js";
5
5
  import { DevtoolsRemoteExporter } from "./server/remote-exporter.js";
6
- import { t as ErrorAggregator } from "./error-aggregator-B52JI8jL.js";
6
+ import { t as ErrorAggregator } from "./error-aggregator-C25_JiVI.js";
7
7
  import { Server } from "node:http";
8
8
  //#region src/index.d.ts
9
9
  interface CreateDevtoolsOptions {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ErrorAggregator, g as hostHeaderIsLoopback, l as DevtoolsServer, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-gk1xapnA.js";
1
+ import { C as ErrorAggregator, g as hostHeaderIsLoopback, l as DevtoolsServer, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-z8UbJsqk.js";
2
2
  import { t as listenLoopbackDualStack } from "./listen-D-lLgfro.js";
3
3
  import { DevtoolsSpanExporter } from "./server/exporter.js";
4
4
  import { DevtoolsLogExporter } from "./server/log-exporter.js";
@@ -1,2 +1,2 @@
1
- import { t as DevtoolsSpanExporter } from "../exporter-OoWkSMCR.cjs";
1
+ import { t as DevtoolsSpanExporter } from "../exporter-AkgWRDpc.cjs";
2
2
  export { DevtoolsSpanExporter };
@@ -1,2 +1,2 @@
1
- import { t as DevtoolsSpanExporter } from "../exporter-C88W22vY.js";
1
+ import { t as DevtoolsSpanExporter } from "../exporter-yo7lLDIs.js";
2
2
  export { DevtoolsSpanExporter };
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_http = require('../http-1afd_01N.cjs');
2
+ const require_http = require('../http-CahjEnpE.cjs');
3
3
  const require_server_exporter = require('./exporter.cjs');
4
4
  const require_server_log_exporter = require('./log-exporter.cjs');
5
5
  const require_server_remote_exporter = require('./remote-exporter.cjs');
6
- const require_grpc = require('../grpc-D0B3P9sI.cjs');
6
+ const require_grpc = require('../grpc-CSW1Evnt.cjs');
7
7
 
8
8
  exports.DEVTOOLS_IDENTITY = require_http.DEVTOOLS_IDENTITY;
9
9
  exports.DevtoolsLogExporter = require_server_log_exporter.DevtoolsLogExporter;
@@ -1,9 +1,9 @@
1
- import "../types-DHDvZnnn.cjs";
2
- import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-B0tjwFqj.cjs";
3
- import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-OoWkSMCR.cjs";
1
+ import "../types-B86Qkqg5.cjs";
2
+ import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-C-ORUrT2.cjs";
3
+ import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-AkgWRDpc.cjs";
4
4
  import { DevtoolsLogExporter } from "./log-exporter.cjs";
5
5
  import { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions } from "./remote-exporter.cjs";
6
- import { t as ErrorAggregator } from "../error-aggregator-DSwUvgFF.cjs";
6
+ import { t as ErrorAggregator } from "../error-aggregator-C0at0wzF.cjs";
7
7
  import { AgentRawEvent, OtelMetricRecord } from "autotel-agents";
8
8
  import { Server } from "node:http";
9
9
  //#region src/server/http.d.ts
@@ -1,9 +1,9 @@
1
- import "../types-DHDvZnnn.js";
2
- import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-DM8y4A9Z.js";
3
- import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-C88W22vY.js";
1
+ import "../types-B86Qkqg5.js";
2
+ import { a as SpanData, i as LogData, n as ErrorGroup, o as TraceData, r as ErrorOccurrence, t as DevtoolsData } from "../types-BhbHVeiy.js";
3
+ import { a as DevtoolsStoreOptions, c as QueryLogsArgs, d as QueryTracesArgs, f as QueryTracesResult, i as DevtoolsStore, l as QueryLogsResult, m as TimeWindow, n as DevtoolsServer, o as MetricCatalogEntry, p as SPAN_SCHEMA, r as DevtoolsServerOptions, s as MetricSeries, t as DevtoolsSpanExporter, u as QueryMetricSeriesArgs } from "../exporter-yo7lLDIs.js";
4
4
  import { DevtoolsLogExporter } from "./log-exporter.js";
5
5
  import { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions } from "./remote-exporter.js";
6
- import { t as ErrorAggregator } from "../error-aggregator-B52JI8jL.js";
6
+ import { t as ErrorAggregator } from "../error-aggregator-C25_JiVI.js";
7
7
  import { Server } from "node:http";
8
8
  import { AgentRawEvent, OtelMetricRecord } from "autotel-agents";
9
9
  //#region src/server/http.d.ts
@@ -1,7 +1,7 @@
1
- import { C as ErrorAggregator, S as resolveTelemetryLimits, _ as isLoopbackHostname, a as probePortHolder, b as appendWithLimit, c as decodeOtlpTraceRequest, d as parseOtlpLogs, f as parseOtlpTraces, g as hostHeaderIsLoopback, h as allowSensitiveRequest, i as DEVTOOLS_IDENTITY, l as DevtoolsServer, m as SPAN_SCHEMA, n as createDevtoolsHttpServer, o as decodeOtlpLogsRequest, p as DevtoolsStore, s as decodeOtlpMetricsRequest, t as attachDevtoolsRoutes, u as isProtobufContentType, v as originIsLoopback, x as applyTelemetryLimits, y as appendManyWithLimit } from "../http-gk1xapnA.js";
1
+ import { C as ErrorAggregator, S as resolveTelemetryLimits, _ as isLoopbackHostname, a as probePortHolder, b as appendWithLimit, c as decodeOtlpTraceRequest, d as parseOtlpLogs, f as parseOtlpTraces, g as hostHeaderIsLoopback, h as allowSensitiveRequest, i as DEVTOOLS_IDENTITY, l as DevtoolsServer, m as SPAN_SCHEMA, n as createDevtoolsHttpServer, o as decodeOtlpLogsRequest, p as DevtoolsStore, s as decodeOtlpMetricsRequest, t as attachDevtoolsRoutes, u as isProtobufContentType, v as originIsLoopback, x as applyTelemetryLimits, y as appendManyWithLimit } from "../http-z8UbJsqk.js";
2
2
  import { DevtoolsSpanExporter } from "./exporter.js";
3
3
  import { DevtoolsLogExporter } from "./log-exporter.js";
4
4
  import { DevtoolsRemoteExporter } from "./remote-exporter.js";
5
- import { t as startOtlpGrpcReceiver } from "../grpc-DY-C1jSU.js";
5
+ import { t as startOtlpGrpcReceiver } from "../grpc-D1tt8UPi.js";
6
6
 
7
7
  export { DEVTOOLS_IDENTITY, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, DevtoolsStore, ErrorAggregator, SPAN_SCHEMA, allowSensitiveRequest, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, hostHeaderIsLoopback, isLoopbackHostname, isProtobufContentType, originIsLoopback, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits, startOtlpGrpcReceiver };