@thotischner/observability-mcp 1.8.1 → 3.0.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.
Files changed (204) hide show
  1. package/dist/analysis/history.d.ts +70 -0
  2. package/dist/analysis/history.js +170 -0
  3. package/dist/analysis/history.test.d.ts +1 -0
  4. package/dist/analysis/history.test.js +141 -0
  5. package/dist/audit/log.d.ts +9 -0
  6. package/dist/audit/log.js +20 -0
  7. package/dist/audit/redaction-bypass.d.ts +67 -0
  8. package/dist/audit/redaction-bypass.js +64 -0
  9. package/dist/audit/redaction-bypass.test.d.ts +1 -0
  10. package/dist/audit/redaction-bypass.test.js +72 -0
  11. package/dist/audit/sinks/s3.d.ts +61 -0
  12. package/dist/audit/sinks/s3.js +179 -0
  13. package/dist/audit/sinks/s3.test.d.ts +1 -0
  14. package/dist/audit/sinks/s3.test.js +175 -0
  15. package/dist/audit/sinks/types.d.ts +18 -0
  16. package/dist/audit/sinks/types.js +1 -0
  17. package/dist/audit/sinks/webhook.d.ts +45 -0
  18. package/dist/audit/sinks/webhook.js +111 -0
  19. package/dist/audit/sinks/webhook.test.d.ts +1 -0
  20. package/dist/audit/sinks/webhook.test.js +162 -0
  21. package/dist/auth/credentials.d.ts +11 -0
  22. package/dist/auth/credentials.js +27 -0
  23. package/dist/auth/credentials.test.js +21 -1
  24. package/dist/auth/csrf.d.ts +26 -0
  25. package/dist/auth/csrf.js +128 -0
  26. package/dist/auth/csrf.test.d.ts +1 -0
  27. package/dist/auth/csrf.test.js +143 -0
  28. package/dist/auth/local-users.d.ts +6 -0
  29. package/dist/auth/local-users.js +11 -0
  30. package/dist/auth/local-users.test.js +41 -0
  31. package/dist/auth/middleware.d.ts +7 -6
  32. package/dist/auth/oidc/dcr.d.ts +70 -0
  33. package/dist/auth/oidc/dcr.js +160 -0
  34. package/dist/auth/oidc/dcr.test.d.ts +1 -0
  35. package/dist/auth/oidc/dcr.test.js +109 -0
  36. package/dist/auth/oidc/endpoints.js +44 -0
  37. package/dist/auth/oidc/profiles.d.ts +22 -0
  38. package/dist/auth/oidc/profiles.js +95 -0
  39. package/dist/auth/oidc/profiles.test.d.ts +1 -0
  40. package/dist/auth/oidc/profiles.test.js +51 -0
  41. package/dist/auth/oidc/runtime.d.ts +3 -0
  42. package/dist/auth/oidc/runtime.js +16 -3
  43. package/dist/auth/oidc/runtime.test.js +1 -0
  44. package/dist/auth/policy/batch-dry-run.d.ts +56 -0
  45. package/dist/auth/policy/batch-dry-run.js +144 -0
  46. package/dist/auth/policy/batch-dry-run.test.d.ts +1 -0
  47. package/dist/auth/policy/batch-dry-run.test.js +140 -0
  48. package/dist/auth/policy/engine.d.ts +20 -4
  49. package/dist/auth/policy/engine.js +16 -2
  50. package/dist/auth/policy/loader.d.ts +11 -1
  51. package/dist/auth/policy/loader.js +37 -0
  52. package/dist/auth/policy/loader.test.d.ts +1 -0
  53. package/dist/auth/policy/loader.test.js +86 -0
  54. package/dist/auth/policy/opa.d.ts +5 -5
  55. package/dist/auth/policy/opa.js +25 -14
  56. package/dist/auth/policy/opa.test.js +48 -0
  57. package/dist/auth/rbac.d.ts +23 -1
  58. package/dist/auth/rbac.js +43 -1
  59. package/dist/auth/rbac.test.js +62 -0
  60. package/dist/cli/index.js +3 -0
  61. package/dist/cli/inspector-config.d.ts +9 -0
  62. package/dist/cli/inspector-config.js +28 -0
  63. package/dist/cli/inspector-config.test.d.ts +1 -0
  64. package/dist/cli/inspector-config.test.js +33 -0
  65. package/dist/cli/lib.d.ts +1 -1
  66. package/dist/cli/lib.js +1 -0
  67. package/dist/conformance/mcp-2025-11-25.test.d.ts +1 -0
  68. package/dist/conformance/mcp-2025-11-25.test.js +206 -0
  69. package/dist/connectors/interface.d.ts +5 -1
  70. package/dist/connectors/loader.d.ts +8 -0
  71. package/dist/connectors/loader.js +55 -4
  72. package/dist/connectors/loader.test.d.ts +1 -0
  73. package/dist/connectors/loader.test.js +78 -0
  74. package/dist/connectors/manifest-hooks.test.d.ts +1 -0
  75. package/dist/connectors/manifest-hooks.test.js +206 -0
  76. package/dist/connectors/prometheus.test.js +31 -13
  77. package/dist/connectors/registry.d.ts +13 -0
  78. package/dist/connectors/registry.js +30 -0
  79. package/dist/connectors/registry.test.js +56 -2
  80. package/dist/context.d.ts +32 -0
  81. package/dist/context.js +35 -0
  82. package/dist/context.test.d.ts +1 -0
  83. package/dist/context.test.js +58 -0
  84. package/dist/federation/registry.d.ts +54 -0
  85. package/dist/federation/registry.js +122 -0
  86. package/dist/federation/registry.test.d.ts +1 -0
  87. package/dist/federation/registry.test.js +206 -0
  88. package/dist/federation/upstream.d.ts +86 -0
  89. package/dist/federation/upstream.js +162 -0
  90. package/dist/federation/upstream.test.d.ts +1 -0
  91. package/dist/federation/upstream.test.js +118 -0
  92. package/dist/index.js +1435 -126
  93. package/dist/metrics/self.d.ts +1 -0
  94. package/dist/metrics/self.js +8 -0
  95. package/dist/middleware/ssrfGuard.d.ts +15 -0
  96. package/dist/middleware/ssrfGuard.js +103 -0
  97. package/dist/middleware/ssrfGuard.test.d.ts +1 -0
  98. package/dist/middleware/ssrfGuard.test.js +81 -0
  99. package/dist/observability/otel.d.ts +20 -0
  100. package/dist/observability/otel.js +118 -0
  101. package/dist/observability/otel.test.d.ts +1 -0
  102. package/dist/observability/otel.test.js +56 -0
  103. package/dist/openapi.js +215 -7
  104. package/dist/openapi.test.js +34 -0
  105. package/dist/policy/redact.js +1 -1
  106. package/dist/postmortem/store.d.ts +34 -0
  107. package/dist/postmortem/store.js +113 -0
  108. package/dist/postmortem/store.test.d.ts +1 -0
  109. package/dist/postmortem/store.test.js +118 -0
  110. package/dist/postmortem/synthesizer.d.ts +83 -0
  111. package/dist/postmortem/synthesizer.js +205 -0
  112. package/dist/postmortem/synthesizer.test.d.ts +1 -0
  113. package/dist/postmortem/synthesizer.test.js +141 -0
  114. package/dist/products/loader.d.ts +31 -3
  115. package/dist/products/loader.js +77 -4
  116. package/dist/products/loader.test.js +90 -1
  117. package/dist/quota/charge.d.ts +28 -0
  118. package/dist/quota/charge.js +30 -0
  119. package/dist/quota/charge.test.d.ts +1 -0
  120. package/dist/quota/charge.test.js +83 -0
  121. package/dist/quota/limiter.d.ts +29 -4
  122. package/dist/quota/limiter.js +64 -8
  123. package/dist/quota/limiter.test.js +86 -0
  124. package/dist/scim/compliance.test.d.ts +1 -0
  125. package/dist/scim/compliance.test.js +169 -0
  126. package/dist/scim/factory.test.d.ts +1 -0
  127. package/dist/scim/factory.test.js +54 -0
  128. package/dist/scim/group-role-map.d.ts +4 -0
  129. package/dist/scim/group-role-map.js +33 -0
  130. package/dist/scim/group-role-map.test.d.ts +1 -0
  131. package/dist/scim/group-role-map.test.js +33 -0
  132. package/dist/scim/patch-ops.test.d.ts +1 -0
  133. package/dist/scim/patch-ops.test.js +100 -0
  134. package/dist/scim/redis-store.d.ts +38 -0
  135. package/dist/scim/redis-store.js +178 -0
  136. package/dist/scim/redis-store.test.d.ts +1 -0
  137. package/dist/scim/redis-store.test.js +138 -0
  138. package/dist/scim/routes.d.ts +40 -0
  139. package/dist/scim/routes.js +395 -0
  140. package/dist/scim/store.d.ts +76 -0
  141. package/dist/scim/store.js +196 -0
  142. package/dist/scim/store.test.d.ts +1 -0
  143. package/dist/scim/store.test.js +121 -0
  144. package/dist/scim/types.d.ts +73 -0
  145. package/dist/scim/types.js +29 -0
  146. package/dist/sdk/hook-wrappers.d.ts +39 -0
  147. package/dist/sdk/hook-wrappers.js +113 -0
  148. package/dist/sdk/hook-wrappers.test.d.ts +1 -0
  149. package/dist/sdk/hook-wrappers.test.js +204 -0
  150. package/dist/sdk/hooks.d.ts +77 -0
  151. package/dist/sdk/hooks.js +72 -0
  152. package/dist/sdk/hooks.test.d.ts +1 -0
  153. package/dist/sdk/hooks.test.js +159 -0
  154. package/dist/sdk/index.d.ts +15 -0
  155. package/dist/sdk/index.js +1 -0
  156. package/dist/sdk/manifest-schema.d.ts +17 -0
  157. package/dist/sdk/manifest-schema.js +21 -0
  158. package/dist/tools/context-seam.test.js +6 -1
  159. package/dist/tools/detect-anomalies.d.ts +12 -1
  160. package/dist/tools/detect-anomalies.js +26 -5
  161. package/dist/tools/generate-postmortem.d.ts +35 -0
  162. package/dist/tools/generate-postmortem.js +191 -0
  163. package/dist/tools/get-anomaly-history.d.ts +35 -0
  164. package/dist/tools/get-anomaly-history.js +126 -0
  165. package/dist/tools/get-service-health.d.ts +1 -1
  166. package/dist/tools/get-service-health.js +4 -3
  167. package/dist/tools/list-services.d.ts +1 -1
  168. package/dist/tools/list-services.js +3 -2
  169. package/dist/tools/list-sources.d.ts +1 -1
  170. package/dist/tools/list-sources.js +6 -2
  171. package/dist/tools/query-logs.d.ts +1 -1
  172. package/dist/tools/query-logs.js +2 -2
  173. package/dist/tools/query-metrics.d.ts +1 -1
  174. package/dist/tools/query-metrics.js +19 -6
  175. package/dist/tools/query-traces.d.ts +47 -0
  176. package/dist/tools/query-traces.js +145 -0
  177. package/dist/tools/query-traces.test.d.ts +1 -0
  178. package/dist/tools/query-traces.test.js +110 -0
  179. package/dist/tools/registry-names.d.ts +35 -0
  180. package/dist/tools/registry-names.js +54 -0
  181. package/dist/tools/registry-names.test.d.ts +1 -0
  182. package/dist/tools/registry-names.test.js +61 -0
  183. package/dist/tools/topology.d.ts +3 -3
  184. package/dist/tools/topology.js +33 -11
  185. package/dist/tools/topology.test.js +45 -0
  186. package/dist/topology/merge.d.ts +22 -0
  187. package/dist/topology/merge.js +178 -0
  188. package/dist/topology/merge.test.d.ts +1 -0
  189. package/dist/topology/merge.test.js +110 -0
  190. package/dist/transport/sessionStore.d.ts +66 -0
  191. package/dist/transport/sessionStore.js +138 -0
  192. package/dist/transport/sessionStore.test.d.ts +1 -0
  193. package/dist/transport/sessionStore.test.js +118 -0
  194. package/dist/transport/transportSessionMap.d.ts +70 -0
  195. package/dist/transport/transportSessionMap.js +128 -0
  196. package/dist/transport/transportSessionMap.test.d.ts +1 -0
  197. package/dist/transport/transportSessionMap.test.js +111 -0
  198. package/dist/transport/websocket.d.ts +35 -0
  199. package/dist/transport/websocket.js +133 -0
  200. package/dist/transport/websocket.test.d.ts +1 -0
  201. package/dist/transport/websocket.test.js +124 -0
  202. package/dist/types.d.ts +51 -0
  203. package/dist/ui/index.html +2529 -145
  204. package/package.json +13 -3
@@ -0,0 +1,133 @@
1
+ // WebSocket transport for MCP.
2
+ //
3
+ // Implements the @modelcontextprotocol/sdk Transport interface so the
4
+ // existing McpServer can speak JSON-RPC over a single WS connection
5
+ // without duplicating any tool registration code.
6
+ //
7
+ // Wire-format: one JSON-RPC message per WS frame (text). No batching,
8
+ // no framing tricks — the same shape an HTTP POST body carries today.
9
+ //
10
+ // Lifecycle:
11
+ // - The HTTP upgrade handler authenticates the connection, creates
12
+ // a WebSocketServerTransport wrapping the accepted socket, then
13
+ // calls McpServer.connect(transport).
14
+ // - The transport surfaces incoming messages via onmessage, sends
15
+ // outgoing messages via ws.send(), and reports closure / errors
16
+ // through onclose / onerror.
17
+ // - A heartbeat ping is sent every PING_INTERVAL_MS; if no pong
18
+ // arrives within PING_TIMEOUT_MS the connection is closed and the
19
+ // session is reaped (same shape as a normal close).
20
+ import { randomUUID } from "node:crypto";
21
+ export const PING_INTERVAL_MS = 30_000;
22
+ export const PING_TIMEOUT_MS = 90_000;
23
+ /**
24
+ * Per-WS-connection MCP transport. One instance per accepted socket.
25
+ */
26
+ export class WebSocketServerTransport {
27
+ sessionId;
28
+ onclose;
29
+ onerror;
30
+ onmessage;
31
+ ws;
32
+ pingTimer;
33
+ lastPongAt = Date.now();
34
+ pingIntervalMs;
35
+ pingTimeoutMs;
36
+ closed = false;
37
+ constructor(ws, opts = {}) {
38
+ this.ws = ws;
39
+ this.sessionId = opts.sessionId ?? randomUUID();
40
+ this.pingIntervalMs = opts.pingIntervalMs ?? PING_INTERVAL_MS;
41
+ this.pingTimeoutMs = opts.pingTimeoutMs ?? PING_TIMEOUT_MS;
42
+ }
43
+ async start() {
44
+ this.ws.on("message", (data, isBinary) => {
45
+ if (isBinary) {
46
+ // MCP frames are JSON text. A binary frame is a client bug —
47
+ // report it but keep the socket open: the SDK contract says
48
+ // errors are non-fatal unless we explicitly close.
49
+ this.onerror?.(new Error("WebSocket binary frame rejected (MCP expects text JSON)"));
50
+ return;
51
+ }
52
+ let payload;
53
+ try {
54
+ payload = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
55
+ }
56
+ catch (err) {
57
+ this.onerror?.(err instanceof Error
58
+ ? err
59
+ : new Error(`WebSocket frame parse failed: ${String(err)}`));
60
+ return;
61
+ }
62
+ // Batched frame -> dispatch each entry; matches Streamable HTTP semantics.
63
+ const items = Array.isArray(payload) ? payload : [payload];
64
+ for (const item of items) {
65
+ this.onmessage?.(item);
66
+ }
67
+ });
68
+ this.ws.on("pong", () => {
69
+ this.lastPongAt = Date.now();
70
+ });
71
+ this.ws.on("close", () => this.handleClose());
72
+ this.ws.on("error", (err) => this.onerror?.(err));
73
+ // Heartbeat: drives close-on-stale via lack of pongs.
74
+ this.pingTimer = setInterval(() => {
75
+ if (this.closed)
76
+ return;
77
+ const idleMs = Date.now() - this.lastPongAt;
78
+ if (idleMs > this.pingTimeoutMs) {
79
+ // 1001 = going away; matches the "stale connection" semantics
80
+ // some collectors special-case.
81
+ try {
82
+ this.ws.close(1001, "heartbeat timeout");
83
+ }
84
+ catch {
85
+ /* socket already gone */
86
+ }
87
+ return;
88
+ }
89
+ try {
90
+ this.ws.ping();
91
+ }
92
+ catch {
93
+ /* socket already gone */
94
+ }
95
+ }, this.pingIntervalMs).unref();
96
+ }
97
+ async send(message, _options) {
98
+ if (this.closed)
99
+ return;
100
+ const text = JSON.stringify(message);
101
+ await new Promise((resolve, reject) => {
102
+ this.ws.send(text, (err) => {
103
+ if (err)
104
+ reject(err);
105
+ else
106
+ resolve();
107
+ });
108
+ });
109
+ }
110
+ async close() {
111
+ if (this.closed)
112
+ return;
113
+ this.closed = true;
114
+ if (this.pingTimer)
115
+ clearInterval(this.pingTimer);
116
+ try {
117
+ // 1000 = normal closure.
118
+ this.ws.close(1000, "server closing");
119
+ }
120
+ catch {
121
+ /* already gone */
122
+ }
123
+ this.onclose?.();
124
+ }
125
+ handleClose() {
126
+ if (this.closed)
127
+ return;
128
+ this.closed = true;
129
+ if (this.pingTimer)
130
+ clearInterval(this.pingTimer);
131
+ this.onclose?.();
132
+ }
133
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { EventEmitter } from "node:events";
4
+ import { WebSocketServerTransport } from "./websocket.js";
5
+ // Minimal fake ws that satisfies the methods + events
6
+ // WebSocketServerTransport actually touches. Avoids pulling the real
7
+ // `ws` module into the unit-test runner (which is npm install + a TCP
8
+ // socket pair away).
9
+ class FakeWS extends EventEmitter {
10
+ sent = [];
11
+ closed;
12
+ pingCount = 0;
13
+ send(text, cb) {
14
+ this.sent.push(text);
15
+ cb();
16
+ }
17
+ close(code, reason) {
18
+ this.closed = { code, reason };
19
+ this.emit("close");
20
+ }
21
+ ping() {
22
+ this.pingCount++;
23
+ }
24
+ }
25
+ function rpc(method, id) {
26
+ return { jsonrpc: "2.0", id, method };
27
+ }
28
+ test("WebSocketServerTransport: parses one JSON message per text frame", async () => {
29
+ const ws = new FakeWS();
30
+ const t = new WebSocketServerTransport(ws);
31
+ const received = [];
32
+ t.onmessage = (m) => received.push(m);
33
+ await t.start();
34
+ ws.emit("message", JSON.stringify(rpc("tools/list", 1)), false);
35
+ ws.emit("message", JSON.stringify(rpc("tools/call", 2)), false);
36
+ assert.equal(received.length, 2);
37
+ assert.equal(received[0].method, "tools/list");
38
+ assert.equal(received[1].method, "tools/call");
39
+ await t.close();
40
+ });
41
+ test("WebSocketServerTransport: batched JSON arrays fan out", async () => {
42
+ const ws = new FakeWS();
43
+ const t = new WebSocketServerTransport(ws);
44
+ const received = [];
45
+ t.onmessage = (m) => received.push(m);
46
+ await t.start();
47
+ ws.emit("message", JSON.stringify([rpc("a", 1), rpc("b", 2), rpc("c", 3)]), false);
48
+ assert.equal(received.length, 3);
49
+ await t.close();
50
+ });
51
+ test("WebSocketServerTransport: malformed JSON surfaces onerror, socket stays open", async () => {
52
+ const ws = new FakeWS();
53
+ const t = new WebSocketServerTransport(ws);
54
+ const errs = [];
55
+ t.onerror = (e) => errs.push(e);
56
+ await t.start();
57
+ ws.emit("message", "not-json{", false);
58
+ assert.equal(errs.length, 1);
59
+ assert.equal(ws.closed, undefined, "socket must NOT be closed on parse error");
60
+ await t.close();
61
+ });
62
+ test("WebSocketServerTransport: binary frame rejected with onerror, socket stays open", async () => {
63
+ const ws = new FakeWS();
64
+ const t = new WebSocketServerTransport(ws);
65
+ const errs = [];
66
+ t.onerror = (e) => errs.push(e);
67
+ await t.start();
68
+ ws.emit("message", Buffer.from([0x01, 0x02]), true);
69
+ assert.equal(errs.length, 1);
70
+ assert.match(errs[0]?.message ?? "", /binary frame/i);
71
+ assert.equal(ws.closed, undefined);
72
+ await t.close();
73
+ });
74
+ test("WebSocketServerTransport: send() writes serialized message", async () => {
75
+ const ws = new FakeWS();
76
+ const t = new WebSocketServerTransport(ws);
77
+ await t.start();
78
+ await t.send({ jsonrpc: "2.0", id: 1, result: { ok: true } });
79
+ assert.equal(ws.sent.length, 1);
80
+ assert.match(ws.sent[0] ?? "", /"result":\{"ok":true\}/);
81
+ await t.close();
82
+ });
83
+ test("WebSocketServerTransport: ws-close triggers onclose", async () => {
84
+ const ws = new FakeWS();
85
+ const t = new WebSocketServerTransport(ws);
86
+ let closed = false;
87
+ t.onclose = () => {
88
+ closed = true;
89
+ };
90
+ await t.start();
91
+ ws.emit("close");
92
+ assert.equal(closed, true);
93
+ // Subsequent send must be a no-op (not throw).
94
+ await t.send({ jsonrpc: "2.0", id: 99, result: {} });
95
+ });
96
+ test("WebSocketServerTransport: heartbeat pings the socket on its interval", async () => {
97
+ const ws = new FakeWS();
98
+ const t = new WebSocketServerTransport(ws, { pingIntervalMs: 20, pingTimeoutMs: 10_000 });
99
+ await t.start();
100
+ await new Promise((r) => setTimeout(r, 65));
101
+ assert.ok(ws.pingCount >= 2, `expected >=2 pings, got ${ws.pingCount}`);
102
+ await t.close();
103
+ });
104
+ test("WebSocketServerTransport: stale connection (no pong) gets closed with 1001", async () => {
105
+ const ws = new FakeWS();
106
+ const t = new WebSocketServerTransport(ws, { pingIntervalMs: 10, pingTimeoutMs: 25 });
107
+ await t.start();
108
+ await new Promise((r) => setTimeout(r, 60));
109
+ assert.equal(ws.closed?.code, 1001);
110
+ assert.match(ws.closed?.reason ?? "", /heartbeat/);
111
+ });
112
+ test("WebSocketServerTransport: generates a sessionId by default", () => {
113
+ const ws = new FakeWS();
114
+ const t = new WebSocketServerTransport(ws);
115
+ assert.ok(t.sessionId, "sessionId must be set");
116
+ assert.match(t.sessionId, /^[0-9a-f-]{36}$/);
117
+ });
118
+ test("WebSocketServerTransport: sessionId override is honored", () => {
119
+ const ws = new FakeWS();
120
+ const t = new WebSocketServerTransport(ws, {
121
+ sessionId: "test-session",
122
+ });
123
+ assert.equal(t.sessionId, "test-session");
124
+ });
package/dist/types.d.ts CHANGED
@@ -31,6 +31,12 @@ export interface SourceConfig {
31
31
  /** @deprecated Use tls.skipVerify instead */
32
32
  tlsSkipVerify?: boolean;
33
33
  metrics?: MetricDefinition[];
34
+ /** Tenant this source belongs to. When set, only requests in that
35
+ * tenant see / can target the source — cross-tenant access returns
36
+ * the same not-found posture as the rest of the tenancy layer.
37
+ * Unset = global source, available to every tenant (preserves
38
+ * pre-E7 single-tenant behaviour as the default). */
39
+ tenant?: string;
34
40
  }
35
41
  export interface GeneralSettings {
36
42
  checkIntervalMs: number;
@@ -151,6 +157,51 @@ export interface LogResult {
151
157
  entries: LogEntry[];
152
158
  summary: LogSummary;
153
159
  }
160
+ export interface TraceQuery {
161
+ /** Service name to filter by — required. */
162
+ service: string;
163
+ /** Rolling time window, same shape as MetricQuery.duration ("5m", "1h", "24h"). */
164
+ duration: string;
165
+ /** Free-form filter pattern interpreted by the backend (TraceQL,
166
+ * Jaeger tag query, etc.). Optional. */
167
+ filter?: string;
168
+ /** Soft cap on the number of trace summaries returned. Default 50. */
169
+ limit?: number;
170
+ /** Only return spans tagged as errors (`status: error` / span status 2). */
171
+ errorsOnly?: boolean;
172
+ }
173
+ export interface TraceSpanSummary {
174
+ /** Stable trace id (hex). */
175
+ traceId: string;
176
+ /** Root or first span name. */
177
+ rootName: string;
178
+ /** Service that emitted the root span. */
179
+ rootService: string;
180
+ /** Total trace duration in milliseconds. */
181
+ durationMs: number;
182
+ /** Span count. */
183
+ spanCount: number;
184
+ /** Whether any span in this trace has an error status. */
185
+ hasError: boolean;
186
+ /** Span start timestamp (RFC-3339). */
187
+ startTs: string;
188
+ /** Optional backend-specific link to the trace view. */
189
+ url?: string;
190
+ }
191
+ export interface TraceSummary {
192
+ total: number;
193
+ errorCount: number;
194
+ /** Median trace duration (ms) across the returned set. */
195
+ p50DurationMs: number;
196
+ /** 95th-percentile trace duration (ms). */
197
+ p95DurationMs: number;
198
+ }
199
+ export interface TraceResult {
200
+ source: string;
201
+ service: string;
202
+ traces: TraceSpanSummary[];
203
+ summary: TraceSummary;
204
+ }
154
205
  /**
155
206
  * A discrete infrastructure entity discovered by a topology-aware connector.
156
207
  *