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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
export interface TraceStartArgs {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export interface TraceStepArgs {
|
|
6
|
+
trace_id: string;
|
|
7
|
+
tool_name: string;
|
|
8
|
+
input: object;
|
|
9
|
+
output: object;
|
|
10
|
+
token_count?: number;
|
|
11
|
+
latency_ms?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TraceEndArgs {
|
|
14
|
+
trace_id: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function handleTraceStart(db: DatabaseSync, args: TraceStartArgs): {
|
|
17
|
+
content: Array<{
|
|
18
|
+
type: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
export declare function handleTraceStep(db: DatabaseSync, args: TraceStepArgs, noTokenCount: boolean): {
|
|
23
|
+
content: Array<{
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
export declare function handleTraceEnd(db: DatabaseSync, args: TraceEndArgs): {
|
|
29
|
+
content: Array<{
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { insertTrace, endTrace, insertStep, getTrace } from "../db.js";
|
|
3
|
+
import { getEncoding } from "js-tiktoken";
|
|
4
|
+
/**
|
|
5
|
+
* Compute offline token count for a text using tiktoken (cl100k_base).
|
|
6
|
+
* Falls back to the char/4 approximation if tiktoken fails.
|
|
7
|
+
*/
|
|
8
|
+
function computeTokenCount(text) {
|
|
9
|
+
try {
|
|
10
|
+
const enc = getEncoding("cl100k_base");
|
|
11
|
+
return enc.encode(text).length;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return Math.ceil(text.length / 4);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function generateAutoName() {
|
|
18
|
+
const now = new Date();
|
|
19
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
20
|
+
const uuidPrefix = crypto.randomUUID().slice(0, 8);
|
|
21
|
+
return `trace-${ts}-${uuidPrefix}`;
|
|
22
|
+
}
|
|
23
|
+
export function handleTraceStart(db, args) {
|
|
24
|
+
const raw = args.name;
|
|
25
|
+
// Auto-generate name if empty, whitespace-only, or "auto"
|
|
26
|
+
const isAuto = !raw ||
|
|
27
|
+
typeof raw !== "string" ||
|
|
28
|
+
raw.trim() === "" ||
|
|
29
|
+
raw.trim().toLowerCase() === "auto";
|
|
30
|
+
const name = isAuto ? generateAutoName() : raw.trim();
|
|
31
|
+
try {
|
|
32
|
+
const traceId = crypto.randomUUID();
|
|
33
|
+
insertTrace(db, traceId, name);
|
|
34
|
+
console.error(`[trace_start] Started trace "${name}" with id=${traceId}`);
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: JSON.stringify({
|
|
40
|
+
trace_id: traceId,
|
|
41
|
+
name,
|
|
42
|
+
status: "running",
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50
|
+
throw new McpError(ErrorCode.InternalError, `Failed to start trace: ${message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function handleTraceStep(db, args, noTokenCount) {
|
|
54
|
+
const { trace_id, tool_name, input, output, token_count, latency_ms } = args;
|
|
55
|
+
if (!trace_id || typeof trace_id !== "string") {
|
|
56
|
+
throw new McpError(ErrorCode.InvalidParams, "trace_id must be a non-empty string");
|
|
57
|
+
}
|
|
58
|
+
if (!tool_name || typeof tool_name !== "string") {
|
|
59
|
+
throw new McpError(ErrorCode.InvalidParams, "tool_name must be a non-empty string");
|
|
60
|
+
}
|
|
61
|
+
if (typeof input !== "object" || input === null) {
|
|
62
|
+
throw new McpError(ErrorCode.InvalidParams, "input must be an object");
|
|
63
|
+
}
|
|
64
|
+
if (typeof output !== "object" || output === null) {
|
|
65
|
+
throw new McpError(ErrorCode.InvalidParams, "output must be an object");
|
|
66
|
+
}
|
|
67
|
+
const trace = getTrace(db, trace_id);
|
|
68
|
+
if (!trace) {
|
|
69
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown trace_id: ${trace_id}`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// Compute token count offline using tiktoken when not provided by caller
|
|
73
|
+
let resolvedTokenCount = null;
|
|
74
|
+
if (!noTokenCount) {
|
|
75
|
+
if (token_count !== undefined) {
|
|
76
|
+
resolvedTokenCount = token_count;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const text = JSON.stringify(input) + JSON.stringify(output);
|
|
80
|
+
resolvedTokenCount = computeTokenCount(text);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const stepId = crypto.randomUUID();
|
|
84
|
+
insertStep(db, {
|
|
85
|
+
id: stepId,
|
|
86
|
+
trace_id,
|
|
87
|
+
tool_name,
|
|
88
|
+
input_json: JSON.stringify(input),
|
|
89
|
+
output_json: JSON.stringify(output),
|
|
90
|
+
token_count: resolvedTokenCount,
|
|
91
|
+
latency_ms: latency_ms ?? null,
|
|
92
|
+
});
|
|
93
|
+
console.error(`[trace_step] Recorded step "${tool_name}" for trace ${trace_id}, step id=${stepId}`);
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
step_id: stepId,
|
|
100
|
+
trace_id,
|
|
101
|
+
tool_name,
|
|
102
|
+
token_count: resolvedTokenCount,
|
|
103
|
+
latency_ms: latency_ms ?? null,
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
+
throw new McpError(ErrorCode.InternalError, `Failed to record step: ${message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function handleTraceEnd(db, args) {
|
|
115
|
+
const { trace_id } = args;
|
|
116
|
+
if (!trace_id || typeof trace_id !== "string") {
|
|
117
|
+
throw new McpError(ErrorCode.InvalidParams, "trace_id must be a non-empty string");
|
|
118
|
+
}
|
|
119
|
+
const trace = getTrace(db, trace_id);
|
|
120
|
+
if (!trace) {
|
|
121
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown trace_id: ${trace_id}`);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
endTrace(db, trace_id);
|
|
125
|
+
const durationMs = trace.ended_at != null
|
|
126
|
+
? trace.ended_at - trace.started_at
|
|
127
|
+
: Date.now() - trace.started_at;
|
|
128
|
+
console.error(`[trace_end] Ended trace ${trace_id} (duration ~${durationMs}ms)`);
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: JSON.stringify({
|
|
134
|
+
trace_id,
|
|
135
|
+
status: "completed",
|
|
136
|
+
duration_ms: durationMs,
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
144
|
+
throw new McpError(ErrorCode.InternalError, `Failed to end trace: ${message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-agent-trace-inspector",
|
|
3
|
+
"mcpName": "io.github.dbsectrainer/mcp-agent-trace-inspector",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Step-by-step observability for MCP agent workflows",
|
|
6
|
+
"author": "dbsectrainer",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/dbsectrainer/mcp-agent-trace-inspector.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/dbsectrainer/mcp-agent-trace-inspector#readme",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"bin": {
|
|
15
|
+
"mcp-agent-trace-inspector": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
".env.example"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"mcp-server",
|
|
27
|
+
"observability",
|
|
28
|
+
"tracing"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"dev": "tsx src/index.ts",
|
|
34
|
+
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
35
|
+
"prepublishOnly": "npm run build",
|
|
36
|
+
"lint": "eslint src tests",
|
|
37
|
+
"format": "prettier --write src tests",
|
|
38
|
+
"format:check": "prettier --check src tests"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
42
|
+
"js-tiktoken": "^1.0.21"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/express": "^5.0.6",
|
|
46
|
+
"@types/node": "^24.12.0",
|
|
47
|
+
"@types/yargs": "^17.0.0",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
50
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
51
|
+
"eslint": "^10.0.3",
|
|
52
|
+
"eslint-config-prettier": "^10.1.8",
|
|
53
|
+
"prettier": "^3.8.1",
|
|
54
|
+
"tsx": "^4.0.0",
|
|
55
|
+
"typescript": "^5.0.0",
|
|
56
|
+
"vitest": "^4.1.0",
|
|
57
|
+
"yargs": "^18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22.5.0"
|
|
61
|
+
}
|
|
62
|
+
}
|