@arizeai/phoenix-mcp 3.1.5 → 4.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/README.md +20 -2
- package/build/annotationConfigTools.js +43 -0
- package/build/client.js +22 -0
- package/build/config.js +47 -0
- package/build/constants.js +61 -0
- package/build/datasetTools.js +123 -102
- package/build/datasetUtils.js +59 -0
- package/build/experimentTools.js +59 -78
- package/build/identifiers.js +77 -0
- package/build/index.js +16 -14
- package/build/pagination.js +25 -0
- package/build/projectTools.js +57 -19
- package/build/projectUtils.js +14 -0
- package/build/promptSchemas.js +26 -14
- package/build/promptTools.js +184 -303
- package/build/responseUtils.js +16 -0
- package/build/sessionTools.js +126 -0
- package/build/spanTools.js +92 -62
- package/build/spanUtils.js +232 -0
- package/build/supportTools.js +12 -9
- package/build/toolResults.js +32 -0
- package/build/traceTools.js +141 -0
- package/build/traceUtils.js +57 -0
- package/package.json +9 -6
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { getTraces } from "@arizeai/phoenix-client/traces";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { DEFAULT_TRACE_PAGE_SIZE, MAX_SPAN_QUERY_LIMIT, MAX_TRACE_PAGE_SIZE, } from "./constants.js";
|
|
4
|
+
import { resolveProjectIdentifier } from "./projectUtils.js";
|
|
5
|
+
import { attachAnnotationsToSpans, extractSpanIds, fetchProjectSpans, fetchSpanAnnotations, resolveStartTime, } from "./spanUtils.js";
|
|
6
|
+
import { jsonResponse } from "./toolResults.js";
|
|
7
|
+
import { buildTrace, groupSpansByTrace } from "./traceUtils.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Tool descriptions
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const LIST_TRACES_DESCRIPTION = `List traces for a project.
|
|
12
|
+
|
|
13
|
+
This tool groups project spans into traces and returns the newest traces first.
|
|
14
|
+
|
|
15
|
+
Example usage:
|
|
16
|
+
Show me the last 10 traces for project "default"
|
|
17
|
+
Show me recent traces from the last 30 minutes for project "checkout"
|
|
18
|
+
|
|
19
|
+
Expected return:
|
|
20
|
+
Array of trace objects with grouped spans and summary timing information.`;
|
|
21
|
+
const GET_TRACE_DESCRIPTION = `Get a single trace by its exact trace ID within a project.
|
|
22
|
+
|
|
23
|
+
Example usage:
|
|
24
|
+
Show me trace "abc123def456" from project "default"
|
|
25
|
+
|
|
26
|
+
Expected return:
|
|
27
|
+
A trace object with all spans that belong to the trace.`;
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Helpers
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/**
|
|
32
|
+
* Fetch every span belonging to a single trace.
|
|
33
|
+
*/
|
|
34
|
+
async function fetchTraceSpans({ client, projectIdentifier, traceId, }) {
|
|
35
|
+
const response = await fetchProjectSpans({
|
|
36
|
+
client,
|
|
37
|
+
projectIdentifier,
|
|
38
|
+
filters: {
|
|
39
|
+
traceIds: [traceId],
|
|
40
|
+
limit: MAX_SPAN_QUERY_LIMIT,
|
|
41
|
+
},
|
|
42
|
+
totalLimit: undefined,
|
|
43
|
+
});
|
|
44
|
+
return response.spans;
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Tool registration
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Register trace-related MCP tools on the given server.
|
|
51
|
+
*/
|
|
52
|
+
export const initializeTraceTools = ({ client, server, defaultProject, }) => {
|
|
53
|
+
server.tool("list-traces", LIST_TRACES_DESCRIPTION, {
|
|
54
|
+
project_identifier: z.string().optional(),
|
|
55
|
+
limit: z
|
|
56
|
+
.number()
|
|
57
|
+
.min(1)
|
|
58
|
+
.max(MAX_TRACE_PAGE_SIZE)
|
|
59
|
+
.default(DEFAULT_TRACE_PAGE_SIZE),
|
|
60
|
+
since: z.string().optional(),
|
|
61
|
+
last_n_minutes: z.number().min(1).optional(),
|
|
62
|
+
include_annotations: z.boolean().default(false).optional(),
|
|
63
|
+
}, async ({ project_identifier, limit, since, last_n_minutes, include_annotations = false, }) => {
|
|
64
|
+
const startTime = resolveStartTime({
|
|
65
|
+
since,
|
|
66
|
+
lastNMinutes: last_n_minutes,
|
|
67
|
+
});
|
|
68
|
+
const normalizedProjectIdentifier = resolveProjectIdentifier({
|
|
69
|
+
projectIdentifier: project_identifier,
|
|
70
|
+
defaultProjectIdentifier: defaultProject,
|
|
71
|
+
});
|
|
72
|
+
const tracePage = await getTraces({
|
|
73
|
+
client,
|
|
74
|
+
project: { project: normalizedProjectIdentifier },
|
|
75
|
+
startTime,
|
|
76
|
+
limit,
|
|
77
|
+
sort: "start_time",
|
|
78
|
+
order: "desc",
|
|
79
|
+
});
|
|
80
|
+
const traceIds = tracePage.traces.map((trace) => trace.trace_id);
|
|
81
|
+
if (traceIds.length === 0) {
|
|
82
|
+
return jsonResponse([]);
|
|
83
|
+
}
|
|
84
|
+
const response = await fetchProjectSpans({
|
|
85
|
+
client,
|
|
86
|
+
projectIdentifier: normalizedProjectIdentifier,
|
|
87
|
+
filters: {
|
|
88
|
+
traceIds,
|
|
89
|
+
limit: MAX_SPAN_QUERY_LIMIT,
|
|
90
|
+
},
|
|
91
|
+
totalLimit: undefined,
|
|
92
|
+
});
|
|
93
|
+
let spans = response.spans;
|
|
94
|
+
if (include_annotations) {
|
|
95
|
+
const annotations = await fetchSpanAnnotations({
|
|
96
|
+
client,
|
|
97
|
+
projectIdentifier: normalizedProjectIdentifier,
|
|
98
|
+
spanIds: extractSpanIds(spans),
|
|
99
|
+
});
|
|
100
|
+
spans = attachAnnotationsToSpans({ spans, annotations });
|
|
101
|
+
}
|
|
102
|
+
const spansByTraceId = groupSpansByTrace({ spans });
|
|
103
|
+
const traces = traceIds.flatMap((traceId) => {
|
|
104
|
+
const traceSpans = spansByTraceId.get(traceId);
|
|
105
|
+
if (!traceSpans || traceSpans.length === 0) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
return [buildTrace({ spans: traceSpans })];
|
|
109
|
+
});
|
|
110
|
+
return jsonResponse(traces);
|
|
111
|
+
});
|
|
112
|
+
server.tool("get-trace", GET_TRACE_DESCRIPTION, {
|
|
113
|
+
project_identifier: z.string().optional(),
|
|
114
|
+
trace_id: z.string(),
|
|
115
|
+
include_annotations: z.boolean().default(false).optional(),
|
|
116
|
+
}, async ({ project_identifier, trace_id, include_annotations = false }) => {
|
|
117
|
+
const normalizedProjectIdentifier = resolveProjectIdentifier({
|
|
118
|
+
projectIdentifier: project_identifier,
|
|
119
|
+
defaultProjectIdentifier: defaultProject,
|
|
120
|
+
});
|
|
121
|
+
const spans = await fetchTraceSpans({
|
|
122
|
+
client,
|
|
123
|
+
projectIdentifier: normalizedProjectIdentifier,
|
|
124
|
+
traceId: trace_id,
|
|
125
|
+
});
|
|
126
|
+
if (spans.length === 0) {
|
|
127
|
+
throw new Error(`Trace not found for project "${normalizedProjectIdentifier}": ${trace_id}`);
|
|
128
|
+
}
|
|
129
|
+
if (include_annotations) {
|
|
130
|
+
const annotations = await fetchSpanAnnotations({
|
|
131
|
+
client,
|
|
132
|
+
projectIdentifier: normalizedProjectIdentifier,
|
|
133
|
+
spanIds: extractSpanIds(spans),
|
|
134
|
+
});
|
|
135
|
+
return jsonResponse(buildTrace({
|
|
136
|
+
spans: attachAnnotationsToSpans({ spans, annotations }),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
return jsonResponse(buildTrace({ spans }));
|
|
140
|
+
});
|
|
141
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group an array of spans by their `trace_id`, returning a map
|
|
3
|
+
* from trace ID to the spans belonging to that trace.
|
|
4
|
+
*/
|
|
5
|
+
export function groupSpansByTrace({ spans, }) {
|
|
6
|
+
const traces = new Map();
|
|
7
|
+
for (const span of spans) {
|
|
8
|
+
const traceId = span.context.trace_id;
|
|
9
|
+
if (!traces.has(traceId)) {
|
|
10
|
+
traces.set(traceId, []);
|
|
11
|
+
}
|
|
12
|
+
traces.get(traceId).push(span);
|
|
13
|
+
}
|
|
14
|
+
return traces;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a {@link Trace} summary from a non-empty array of spans that share
|
|
18
|
+
* the same trace ID.
|
|
19
|
+
*
|
|
20
|
+
* The summary includes the root span, overall time range, duration, and a
|
|
21
|
+
* roll-up status that is `"ERROR"` when any span has an error.
|
|
22
|
+
*
|
|
23
|
+
* @throws When the spans array is empty.
|
|
24
|
+
*/
|
|
25
|
+
export function buildTrace({ spans }) {
|
|
26
|
+
if (spans.length === 0) {
|
|
27
|
+
throw new Error("Cannot build trace from empty spans array");
|
|
28
|
+
}
|
|
29
|
+
const traceId = spans[0].context.trace_id;
|
|
30
|
+
const rootSpan = spans.find((span) => !span.parent_id);
|
|
31
|
+
const startTimes = spans
|
|
32
|
+
.map((span) => new Date(span.start_time).getTime())
|
|
33
|
+
.filter((time) => !Number.isNaN(time));
|
|
34
|
+
const endTimes = spans
|
|
35
|
+
.map((span) => new Date(span.end_time).getTime())
|
|
36
|
+
.filter((time) => !Number.isNaN(time));
|
|
37
|
+
const startTime = startTimes.length > 0
|
|
38
|
+
? new Date(Math.min(...startTimes)).toISOString()
|
|
39
|
+
: undefined;
|
|
40
|
+
const endTime = endTimes.length > 0
|
|
41
|
+
? new Date(Math.max(...endTimes)).toISOString()
|
|
42
|
+
: undefined;
|
|
43
|
+
const duration = startTime && endTime
|
|
44
|
+
? new Date(endTime).getTime() - new Date(startTime).getTime()
|
|
45
|
+
: undefined;
|
|
46
|
+
const hasErrors = spans.some((span) => span.status_code === "ERROR" ||
|
|
47
|
+
span.attributes?.error);
|
|
48
|
+
return {
|
|
49
|
+
traceId,
|
|
50
|
+
spans,
|
|
51
|
+
rootSpan,
|
|
52
|
+
startTime,
|
|
53
|
+
endTime,
|
|
54
|
+
duration,
|
|
55
|
+
status: hasErrors ? "ERROR" : "OK",
|
|
56
|
+
};
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arizeai/phoenix-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A MCP server for Arize Phoenix",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arize-phoenix",
|
|
@@ -24,22 +24,25 @@
|
|
|
24
24
|
],
|
|
25
25
|
"type": "module",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
28
28
|
"glob": "^13.0.6",
|
|
29
29
|
"minimist": "^1.2.8",
|
|
30
|
-
"zod": "^4.
|
|
31
|
-
"@arizeai/phoenix-client": "6.5.
|
|
30
|
+
"zod": "^4.3.6",
|
|
31
|
+
"@arizeai/phoenix-client": "6.5.3",
|
|
32
|
+
"@arizeai/phoenix-config": "0.1.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@types/glob": "^
|
|
35
|
+
"@types/glob": "^9.0.0",
|
|
35
36
|
"@types/minimist": "^1.2.5",
|
|
36
37
|
"@types/node": "^22.14.0",
|
|
37
|
-
"tsx": "^4.
|
|
38
|
+
"tsx": "^4.21.0",
|
|
39
|
+
"vitest": "^4.0.10"
|
|
38
40
|
},
|
|
39
41
|
"scripts": {
|
|
40
42
|
"build": "tsc && chmod 755 build/index.js",
|
|
41
43
|
"dev": "source ./.env && tsx src/index.ts -- --apiKey $PHOENIX_API_KEY --baseUrl $PHOENIX_BASE_URL",
|
|
42
44
|
"inspect": "pnpm run build && npx -y @modelcontextprotocol/inspector -- node build/index.js",
|
|
45
|
+
"test": "vitest run",
|
|
43
46
|
"typecheck": "tsc --noEmit"
|
|
44
47
|
}
|
|
45
48
|
}
|