mcp-agent-trace-inspector 1.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.
package/dist/server.js ADDED
@@ -0,0 +1,673 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, CancelledNotificationSchema, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { handleTraceStart, handleTraceStep, handleTraceEnd, } from "./tools/trace.js";
4
+ import { handleGetTraceSummary, handleListTraces } from "./tools/inspect.js";
5
+ import { handleExportDashboard } from "./tools/export.js";
6
+ import { handleCompareTraces } from "./tools/compare.js";
7
+ import { handleListResources, handleReadResource } from "./resources.js";
8
+ import { handleListPrompts, handleGetPrompt } from "./prompts.js";
9
+ import { computeSummary } from "./db.js";
10
+ import { exportToOTLP, exportAllOTLP } from "./otel-exporter.js";
11
+ import { saveAlertRules, initAlertRulesTable, } from "./alerting.js";
12
+ import { applyRetentionPolicy } from "./retention.js";
13
+ import { AuditLog } from "./audit-log.js";
14
+ import { DEFAULT_PRICING, estimateCost } from "./pricing.js";
15
+ // Shared audit log instance — lives for the lifetime of the server process
16
+ const _auditLog = new AuditLog();
17
+ // Retention policy storage (in-memory; persisted across calls in the same process)
18
+ let _retentionDays = null;
19
+ // Alert channels storage
20
+ let _alertChannels = {};
21
+ // Cancellation registry — maps requestId (as string) to cancelled flag
22
+ const cancellationRegistry = new Map();
23
+ export function isRequestCancelled(requestId) {
24
+ return cancellationRegistry.get(requestId) === true;
25
+ }
26
+ export function clearCancellation(requestId) {
27
+ cancellationRegistry.delete(requestId);
28
+ }
29
+ export function createServer(options) {
30
+ const { db, noTokenCount } = options;
31
+ const server = new Server({ name: "mcp-agent-trace-inspector", version: "0.2.0" }, { capabilities: { tools: {}, resources: {}, prompts: {}, logging: {} } });
32
+ // ---- Cancellation ----
33
+ server.setNotificationHandler(CancelledNotificationSchema, async (notification) => {
34
+ const requestId = notification.params?.requestId;
35
+ if (requestId !== undefined && requestId !== null) {
36
+ cancellationRegistry.set(String(requestId), true);
37
+ }
38
+ });
39
+ // ---- Resources ----
40
+ server.setRequestHandler(ListResourcesRequestSchema, async () => handleListResources(db));
41
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
42
+ const uri = request.params.uri;
43
+ return handleReadResource(db, uri);
44
+ });
45
+ // ---- Prompts ----
46
+ server.setRequestHandler(ListPromptsRequestSchema, async () => handleListPrompts());
47
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
48
+ const { name, arguments: promptArgs } = request.params;
49
+ return handleGetPrompt(name, promptArgs);
50
+ });
51
+ // ---- Tools ----
52
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
53
+ tools: [
54
+ {
55
+ name: "trace_start",
56
+ description: 'Begin a new agent workflow trace. Returns a trace_id to use with subsequent calls. Example: { "name": "My Search Workflow" }. Pass "auto" or leave name blank for an auto-generated name.',
57
+ annotations: {
58
+ destructiveHint: false,
59
+ readOnlyHint: false,
60
+ },
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ name: {
65
+ type: "string",
66
+ description: 'Human-readable name for this trace/workflow run. Example: "Product Search Workflow 2024-01-15". Pass "auto" to auto-generate.',
67
+ },
68
+ },
69
+ required: ["name"],
70
+ additionalProperties: false,
71
+ },
72
+ },
73
+ {
74
+ name: "trace_step",
75
+ description: 'Record a single step within a trace. Captures tool name, input, output, optional token count and latency. Example: { "trace_id": "abc-123", "tool_name": "web_search", "input": {"query": "hello"}, "output": {"results": ["a","b"]}, "token_count": 50, "latency_ms": 320 }',
76
+ annotations: {
77
+ destructiveHint: false,
78
+ readOnlyHint: false,
79
+ },
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ trace_id: {
84
+ type: "string",
85
+ description: 'ID of the trace to record this step under. Example: "abc-123-def-456"',
86
+ },
87
+ tool_name: {
88
+ type: "string",
89
+ description: 'Name of the tool or action that was executed. Example: "web_search" or "llm_call"',
90
+ },
91
+ input: {
92
+ type: "object",
93
+ description: 'Input payload passed to the tool. Example: { "query": "weather in Paris" }',
94
+ },
95
+ output: {
96
+ type: "object",
97
+ description: 'Output/result returned by the tool. Example: { "result": "Sunny, 22°C" }. Add "error" field or "isError": true to flag errors.',
98
+ },
99
+ token_count: {
100
+ type: "number",
101
+ description: "Optional number of tokens consumed in this step. Example: 150",
102
+ },
103
+ latency_ms: {
104
+ type: "number",
105
+ description: "Optional wall-clock latency of this step in milliseconds. Example: 450",
106
+ },
107
+ },
108
+ required: ["trace_id", "tool_name", "input", "output"],
109
+ additionalProperties: false,
110
+ },
111
+ },
112
+ {
113
+ name: "trace_end",
114
+ description: 'Mark a trace as completed. No further steps should be added after this call. Example: { "trace_id": "abc-123" }',
115
+ annotations: {
116
+ destructiveHint: false,
117
+ readOnlyHint: false,
118
+ },
119
+ inputSchema: {
120
+ type: "object",
121
+ properties: {
122
+ trace_id: {
123
+ type: "string",
124
+ description: 'ID of the trace to complete. Example: "abc-123-def-456"',
125
+ },
126
+ },
127
+ required: ["trace_id"],
128
+ additionalProperties: false,
129
+ },
130
+ },
131
+ {
132
+ name: "get_trace_summary",
133
+ description: 'Retrieve a summary of a trace including step count, total tokens, total latency, cost estimate, and reasoning chain detection. Example: { "trace_id": "abc-123", "model": "claude-sonnet-4-6" }',
134
+ annotations: {
135
+ destructiveHint: false,
136
+ readOnlyHint: true,
137
+ },
138
+ inputSchema: {
139
+ type: "object",
140
+ properties: {
141
+ trace_id: {
142
+ type: "string",
143
+ description: 'ID of the trace to summarize. Example: "abc-123-def-456"',
144
+ },
145
+ model: {
146
+ type: "string",
147
+ description: 'Model name for cost estimation. Example: "claude-sonnet-4-6". Defaults to "claude-sonnet-4-6".',
148
+ },
149
+ },
150
+ required: ["trace_id"],
151
+ additionalProperties: false,
152
+ },
153
+ },
154
+ {
155
+ name: "list_traces",
156
+ description: 'List all stored traces with their names, statuses, and timestamps. Example: { "limit": 10 } to get the 10 most recent traces.',
157
+ annotations: {
158
+ destructiveHint: false,
159
+ readOnlyHint: true,
160
+ },
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ limit: {
165
+ type: "number",
166
+ description: "Maximum number of traces to return (most recent first). Example: 20. Omit for all.",
167
+ },
168
+ },
169
+ additionalProperties: false,
170
+ },
171
+ },
172
+ {
173
+ name: "export_dashboard",
174
+ description: 'Generate a self-contained HTML dashboard for a trace with error highlighting and latency waterfall visualization. Returns the full HTML as a string. Example: { "trace_id": "abc-123" }',
175
+ annotations: {
176
+ destructiveHint: false,
177
+ readOnlyHint: true,
178
+ },
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ trace_id: {
183
+ type: "string",
184
+ description: 'ID of the trace to export. Example: "abc-123-def-456"',
185
+ },
186
+ },
187
+ required: ["trace_id"],
188
+ additionalProperties: false,
189
+ },
190
+ },
191
+ {
192
+ name: "compare_traces",
193
+ description: 'Compare two traces side by side: step counts, tokens, latency, and step-by-step differences. Example: { "trace_id_a": "abc-123", "trace_id_b": "def-456" }',
194
+ annotations: {
195
+ destructiveHint: false,
196
+ readOnlyHint: true,
197
+ },
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ trace_id_a: {
202
+ type: "string",
203
+ description: 'ID of the first trace (baseline). Example: "abc-123"',
204
+ },
205
+ trace_id_b: {
206
+ type: "string",
207
+ description: 'ID of the second trace (to compare against). Example: "def-456"',
208
+ },
209
+ },
210
+ required: ["trace_id_a", "trace_id_b"],
211
+ additionalProperties: false,
212
+ },
213
+ },
214
+ {
215
+ name: "extract_reasoning_chain",
216
+ description: 'Extract only the reasoning/thinking steps from a trace. Returns steps whose tool_name matches reasoning patterns (reason, think, plan, reflect, analyz, consider) or whose input/output content includes the word "think". Example: { "trace_id": "abc-123" }',
217
+ annotations: {
218
+ destructiveHint: false,
219
+ readOnlyHint: true,
220
+ },
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ trace_id: {
225
+ type: "string",
226
+ description: 'ID of the trace to extract reasoning from. Example: "abc-123-def-456"',
227
+ },
228
+ },
229
+ required: ["trace_id"],
230
+ additionalProperties: false,
231
+ },
232
+ },
233
+ {
234
+ name: "export_otel",
235
+ description: 'Export trace(s) in OpenTelemetry OTLP JSON span format. Omit trace_id to export all traces. Example: { "format": "json" } or { "trace_id": "abc-123", "format": "json" }',
236
+ annotations: {
237
+ destructiveHint: false,
238
+ readOnlyHint: true,
239
+ },
240
+ inputSchema: {
241
+ type: "object",
242
+ properties: {
243
+ trace_id: {
244
+ type: "string",
245
+ description: "Optional trace ID to export. Omit to export all traces.",
246
+ },
247
+ format: {
248
+ type: "string",
249
+ enum: ["json"],
250
+ description: 'Export format. Currently only "json" is supported.',
251
+ },
252
+ },
253
+ required: ["format"],
254
+ additionalProperties: false,
255
+ },
256
+ },
257
+ {
258
+ name: "configure_alerts",
259
+ description: 'Configure alert rules for latency, error rate, or cost thresholds. Example: { "rules": [{"type":"latency","threshold":1000}], "slack_webhook": "https://hooks.slack.com/..." }',
260
+ annotations: {
261
+ destructiveHint: false,
262
+ readOnlyHint: false,
263
+ },
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ rules: {
268
+ type: "array",
269
+ items: {
270
+ type: "object",
271
+ properties: {
272
+ type: {
273
+ type: "string",
274
+ enum: ["latency", "error_rate", "cost"],
275
+ },
276
+ threshold: { type: "number" },
277
+ },
278
+ required: ["type", "threshold"],
279
+ },
280
+ description: "Array of alert rules to configure.",
281
+ },
282
+ slack_webhook: {
283
+ type: "string",
284
+ description: "Optional Slack webhook URL for Slack block notifications.",
285
+ },
286
+ webhook_url: {
287
+ type: "string",
288
+ description: "Optional generic webhook URL for JSON POST notifications.",
289
+ },
290
+ },
291
+ required: ["rules"],
292
+ additionalProperties: false,
293
+ },
294
+ },
295
+ {
296
+ name: "set_retention_policy",
297
+ description: 'Set the trace retention policy (how many days to keep traces before archiving). Example: { "retention_days": 30 }',
298
+ annotations: {
299
+ destructiveHint: false,
300
+ readOnlyHint: false,
301
+ },
302
+ inputSchema: {
303
+ type: "object",
304
+ properties: {
305
+ retention_days: {
306
+ type: "number",
307
+ description: "Number of days to retain traces before archiving. Must be > 0.",
308
+ },
309
+ },
310
+ required: ["retention_days"],
311
+ additionalProperties: false,
312
+ },
313
+ },
314
+ {
315
+ name: "apply_retention",
316
+ description: "Apply the current retention policy: archives traces older than retention_days and deletes archived traces past 2× threshold. Example: {}",
317
+ annotations: {
318
+ destructiveHint: true,
319
+ readOnlyHint: false,
320
+ },
321
+ inputSchema: {
322
+ type: "object",
323
+ properties: {},
324
+ additionalProperties: false,
325
+ },
326
+ },
327
+ {
328
+ name: "export_compliance_log",
329
+ description: 'Export the compliance audit log as JSON or CSV. Example: { "format": "json" } or { "format": "csv", "from_date": "2025-01-01", "to_date": "2025-12-31" }',
330
+ annotations: {
331
+ destructiveHint: false,
332
+ readOnlyHint: true,
333
+ },
334
+ inputSchema: {
335
+ type: "object",
336
+ properties: {
337
+ from_date: {
338
+ type: "string",
339
+ description: "Optional ISO date string to filter entries from (inclusive).",
340
+ },
341
+ to_date: {
342
+ type: "string",
343
+ description: "Optional ISO date string to filter entries to (inclusive).",
344
+ },
345
+ format: {
346
+ type: "string",
347
+ enum: ["json", "csv"],
348
+ description: 'Output format: "json" or "csv".',
349
+ },
350
+ },
351
+ required: ["format"],
352
+ additionalProperties: false,
353
+ },
354
+ },
355
+ ],
356
+ }));
357
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
358
+ const { name, arguments: args } = request.params;
359
+ const safeArgs = (args ?? {});
360
+ try {
361
+ switch (name) {
362
+ case "trace_start": {
363
+ const result = handleTraceStart(db, {
364
+ name: safeArgs.name,
365
+ });
366
+ const startData = JSON.parse(result.content[0].text);
367
+ await server.notification({
368
+ method: "notifications/message",
369
+ params: {
370
+ level: "info",
371
+ logger: "trace_start",
372
+ data: `Trace started: name="${startData.name}" trace_id=${startData.trace_id}`,
373
+ },
374
+ });
375
+ return result;
376
+ }
377
+ case "trace_step": {
378
+ const result = handleTraceStep(db, {
379
+ trace_id: safeArgs.trace_id,
380
+ tool_name: safeArgs.tool_name,
381
+ input: safeArgs.input,
382
+ output: safeArgs.output,
383
+ token_count: safeArgs.token_count !== undefined
384
+ ? safeArgs.token_count
385
+ : undefined,
386
+ latency_ms: safeArgs.latency_ms !== undefined
387
+ ? safeArgs.latency_ms
388
+ : undefined,
389
+ }, noTokenCount);
390
+ // Emit debug notification for each recorded step
391
+ const stepData = JSON.parse(result.content[0].text);
392
+ await server.notification({
393
+ method: "notifications/message",
394
+ params: {
395
+ level: "debug",
396
+ logger: "trace_step",
397
+ data: `Step recorded: tool="${stepData.tool_name}" step_id=${stepData.step_id}`,
398
+ },
399
+ });
400
+ // Record to compliance audit log
401
+ const tokenCount = safeArgs.token_count !== undefined
402
+ ? safeArgs.token_count
403
+ : 0;
404
+ const costUsd = tokenCount > 0
405
+ ? (estimateCost(tokenCount, "claude-sonnet-4-6", DEFAULT_PRICING) ?? 0)
406
+ : 0;
407
+ _auditLog.record({
408
+ timestamp: new Date().toISOString(),
409
+ trace_id: safeArgs.trace_id,
410
+ tool_name: safeArgs.tool_name,
411
+ user_id: "",
412
+ token_count: tokenCount,
413
+ cost_usd: costUsd,
414
+ });
415
+ return result;
416
+ }
417
+ case "trace_end": {
418
+ const result = handleTraceEnd(db, {
419
+ trace_id: safeArgs.trace_id,
420
+ });
421
+ const endData = JSON.parse(result.content[0].text);
422
+ await server.notification({
423
+ method: "notifications/message",
424
+ params: {
425
+ level: "info",
426
+ logger: "trace_end",
427
+ data: `Trace completed: trace_id=${endData.trace_id} duration=${endData.duration_ms}ms`,
428
+ },
429
+ });
430
+ return result;
431
+ }
432
+ case "get_trace_summary":
433
+ return handleGetTraceSummary(db, {
434
+ trace_id: safeArgs.trace_id,
435
+ model: safeArgs.model !== undefined
436
+ ? safeArgs.model
437
+ : undefined,
438
+ });
439
+ case "list_traces":
440
+ return handleListTraces(db, {
441
+ limit: safeArgs.limit !== undefined
442
+ ? safeArgs.limit
443
+ : undefined,
444
+ });
445
+ case "export_dashboard":
446
+ return handleExportDashboard(db, {
447
+ trace_id: safeArgs.trace_id,
448
+ }, server);
449
+ case "compare_traces": {
450
+ const result = handleCompareTraces(db, {
451
+ trace_id_a: safeArgs.trace_id_a,
452
+ trace_id_b: safeArgs.trace_id_b,
453
+ });
454
+ await server.notification({
455
+ method: "notifications/message",
456
+ params: {
457
+ level: "info",
458
+ logger: "compare_traces",
459
+ data: `Traces compared: a=${safeArgs.trace_id_a} b=${safeArgs.trace_id_b}`,
460
+ },
461
+ });
462
+ return result;
463
+ }
464
+ case "extract_reasoning_chain": {
465
+ const traceId = safeArgs.trace_id;
466
+ if (!traceId || typeof traceId !== "string") {
467
+ throw new McpError(ErrorCode.InvalidParams, "trace_id must be a non-empty string");
468
+ }
469
+ const summary = computeSummary(db, traceId);
470
+ if (!summary) {
471
+ throw new McpError(ErrorCode.InvalidParams, `Unknown trace_id: ${traceId}`);
472
+ }
473
+ const REASONING_PATTERNS = /reason|think|plan|reflect|analyz|consider/i;
474
+ const reasoningSteps = summary.steps.filter((s) => {
475
+ if (REASONING_PATTERNS.test(s.tool_name))
476
+ return true;
477
+ // Also check input/output content for "think"
478
+ const content = s.input_json + " " + s.output_json;
479
+ return /think/i.test(content);
480
+ });
481
+ await server.notification({
482
+ method: "notifications/message",
483
+ params: {
484
+ level: "info",
485
+ logger: "extract_reasoning_chain",
486
+ data: `Extracted ${reasoningSteps.length} reasoning step(s) from trace_id=${traceId}`,
487
+ },
488
+ });
489
+ const json = {
490
+ trace_id: traceId,
491
+ reasoning_step_count: reasoningSteps.length,
492
+ steps: reasoningSteps.map((s, i) => ({
493
+ index: i + 1,
494
+ step_id: s.id,
495
+ tool_name: s.tool_name,
496
+ input: (() => {
497
+ try {
498
+ return JSON.parse(s.input_json);
499
+ }
500
+ catch {
501
+ return s.input_json;
502
+ }
503
+ })(),
504
+ output: (() => {
505
+ try {
506
+ return JSON.parse(s.output_json);
507
+ }
508
+ catch {
509
+ return s.output_json;
510
+ }
511
+ })(),
512
+ token_count: s.token_count,
513
+ latency_ms: s.latency_ms,
514
+ created_at: s.created_at,
515
+ })),
516
+ };
517
+ const textLines = [
518
+ `Reasoning Chain for trace: ${summary.trace.name}`,
519
+ ` Trace ID: ${traceId}`,
520
+ ` Reasoning steps: ${reasoningSteps.length} / ${summary.stepCount} total`,
521
+ "",
522
+ ...reasoningSteps.map((s, i) => ` ${i + 1}. [${s.tool_name}]` +
523
+ (s.latency_ms != null ? ` ${s.latency_ms}ms` : "") +
524
+ (s.token_count != null ? ` ${s.token_count} tokens` : "")),
525
+ ];
526
+ return {
527
+ content: [
528
+ { type: "text", text: textLines.join("\n") },
529
+ {
530
+ type: "text",
531
+ text: "\n\nJSON:\n" + JSON.stringify(json, null, 2),
532
+ },
533
+ ],
534
+ };
535
+ }
536
+ case "export_otel": {
537
+ const traceId = safeArgs.trace_id !== undefined
538
+ ? safeArgs.trace_id
539
+ : undefined;
540
+ let result;
541
+ if (traceId) {
542
+ result = exportToOTLP(db, traceId);
543
+ }
544
+ else {
545
+ result = exportAllOTLP(db);
546
+ }
547
+ return {
548
+ content: [
549
+ {
550
+ type: "text",
551
+ text: JSON.stringify(result, null, 2),
552
+ },
553
+ ],
554
+ };
555
+ }
556
+ case "configure_alerts": {
557
+ const rules = safeArgs.rules;
558
+ const slackWebhook = safeArgs.slack_webhook !== undefined
559
+ ? safeArgs.slack_webhook
560
+ : undefined;
561
+ const webhookUrl = safeArgs.webhook_url !== undefined
562
+ ? safeArgs.webhook_url
563
+ : undefined;
564
+ initAlertRulesTable(db);
565
+ saveAlertRules(db, rules);
566
+ _alertChannels = {
567
+ slackWebhook,
568
+ genericWebhook: webhookUrl,
569
+ };
570
+ return {
571
+ content: [
572
+ {
573
+ type: "text",
574
+ text: JSON.stringify({
575
+ status: "ok",
576
+ rules_saved: rules.length,
577
+ channels: {
578
+ slack: !!slackWebhook,
579
+ generic: !!webhookUrl,
580
+ },
581
+ }),
582
+ },
583
+ ],
584
+ };
585
+ }
586
+ case "set_retention_policy": {
587
+ const retentionDays = safeArgs.retention_days;
588
+ if (!retentionDays || retentionDays <= 0) {
589
+ throw new McpError(ErrorCode.InvalidParams, "retention_days must be a positive number");
590
+ }
591
+ _retentionDays = retentionDays;
592
+ return {
593
+ content: [
594
+ {
595
+ type: "text",
596
+ text: JSON.stringify({
597
+ status: "ok",
598
+ retention_days: retentionDays,
599
+ }),
600
+ },
601
+ ],
602
+ };
603
+ }
604
+ case "apply_retention": {
605
+ if (_retentionDays === null) {
606
+ throw new McpError(ErrorCode.InvalidParams, "No retention policy set. Call set_retention_policy first.");
607
+ }
608
+ const retResult = applyRetentionPolicy(db, _retentionDays);
609
+ return {
610
+ content: [
611
+ {
612
+ type: "text",
613
+ text: JSON.stringify({
614
+ status: "ok",
615
+ retention_days: _retentionDays,
616
+ ...retResult,
617
+ }),
618
+ },
619
+ ],
620
+ };
621
+ }
622
+ case "export_compliance_log": {
623
+ const fromDate = safeArgs.from_date !== undefined
624
+ ? safeArgs.from_date
625
+ : undefined;
626
+ const toDate = safeArgs.to_date !== undefined
627
+ ? safeArgs.to_date
628
+ : undefined;
629
+ const format = safeArgs.format;
630
+ const entries = _auditLog.export(fromDate, toDate);
631
+ if (format === "csv") {
632
+ const header = "timestamp,trace_id,tool_name,user_id,token_count,cost_usd";
633
+ const rows = entries.map((e) => [
634
+ e.timestamp,
635
+ e.trace_id,
636
+ e.tool_name,
637
+ e.user_id,
638
+ e.token_count,
639
+ e.cost_usd,
640
+ ]
641
+ .map((v) => JSON.stringify(v))
642
+ .join(","));
643
+ return {
644
+ content: [
645
+ {
646
+ type: "text",
647
+ text: [header, ...rows].join("\n"),
648
+ },
649
+ ],
650
+ };
651
+ }
652
+ return {
653
+ content: [
654
+ {
655
+ type: "text",
656
+ text: JSON.stringify(entries, null, 2),
657
+ },
658
+ ],
659
+ };
660
+ }
661
+ default:
662
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
663
+ }
664
+ }
665
+ catch (err) {
666
+ if (err instanceof McpError)
667
+ throw err;
668
+ const message = err instanceof Error ? err.message : String(err);
669
+ throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${message}`);
670
+ }
671
+ });
672
+ return server;
673
+ }
@@ -0,0 +1,11 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ export interface CompareTracesArgs {
3
+ trace_id_a: string;
4
+ trace_id_b: string;
5
+ }
6
+ export declare function handleCompareTraces(db: DatabaseSync, args: CompareTracesArgs): {
7
+ content: Array<{
8
+ type: string;
9
+ text: string;
10
+ }>;
11
+ };