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/.env.example +10 -0
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/alerting.d.ts +18 -0
- package/dist/alerting.js +169 -0
- package/dist/audit-log.d.ts +15 -0
- package/dist/audit-log.js +49 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +83 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +107 -0
- package/dist/http-server.d.ts +1 -0
- package/dist/http-server.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +74 -0
- package/dist/otel-exporter.d.ts +20 -0
- package/dist/otel-exporter.js +98 -0
- package/dist/pricing.d.ts +10 -0
- package/dist/pricing.js +38 -0
- package/dist/prompts.d.ts +21 -0
- package/dist/prompts.js +66 -0
- package/dist/rate-limiter.d.ts +2 -0
- package/dist/rate-limiter.js +34 -0
- package/dist/resources.d.ts +16 -0
- package/dist/resources.js +55 -0
- package/dist/retention.d.ts +13 -0
- package/dist/retention.js +43 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +673 -0
- package/dist/tools/compare.d.ts +11 -0
- package/dist/tools/compare.js +121 -0
- package/dist/tools/export.d.ts +11 -0
- package/dist/tools/export.js +373 -0
- package/dist/tools/inspect.d.ts +20 -0
- package/dist/tools/inspect.js +149 -0
- package/dist/tools/trace.d.ts +33 -0
- package/dist/tools/trace.js +146 -0
- package/package.json +62 -0
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
|
+
};
|