@tonyclaw/agent-inspector 2.0.29 → 2.0.31

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 (45) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-DcqxlgSQ.js → CompareDrawer-tIUf2EJm.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-BxRaXhKR.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-CBcnPgx1.js → ReplayDialog-D6v1xcuC.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DSgSqCYt.js → RequestAnatomy-D6PniTlx.js} +1 -1
  6. package/.output/public/assets/{ResponseView-CjqzBSoF.js → ResponseView-Sx8PjuvU.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-DdBBKolI.js → StreamingChunkSequence-D-Rbfnxh.js} +1 -1
  8. package/.output/public/assets/_sessionId-BhNTuZ31.js +1 -0
  9. package/.output/public/assets/index-Byk60-jA.css +1 -0
  10. package/.output/public/assets/index-Kxptlkpg.js +1 -0
  11. package/.output/public/assets/{main-B6OLZCp9.js → main-B1kdhY98.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +159 -134
  13. package/.output/server/{_sessionId-CCfKJJ19.mjs → _sessionId-BceHFKf4.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-uaPI5R6e.mjs → CompareDrawer-uSxK5Ei5.mjs} +3 -3
  15. package/.output/server/_ssr/{ProxyViewerContainer-BV2nIxXy.mjs → ProxyViewerContainer-_npQ_f2i.mjs} +566 -17
  16. package/.output/server/_ssr/{ReplayDialog-eE2Hy2Nl.mjs → ReplayDialog-BHAzSLCT.mjs} +4 -4
  17. package/.output/server/_ssr/{RequestAnatomy-B7vwWo57.mjs → RequestAnatomy-DjlZLXBd.mjs} +3 -3
  18. package/.output/server/_ssr/{ResponseView-Bm6Qp8mo.mjs → ResponseView-BuDEJ3fl.mjs} +3 -3
  19. package/.output/server/_ssr/{StreamingChunkSequence-CbAJKJQx.mjs → StreamingChunkSequence-DFcu0bD8.mjs} +3 -3
  20. package/.output/server/_ssr/{index-u14_Aj60.mjs → index-Bw6Fw33c.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-qgeGUp1k.mjs → router-D90tMCb3.mjs} +1236 -143
  23. package/.output/server/_tanstack-start-manifest_v-BVM4AUlx.mjs +4 -0
  24. package/.output/server/index.mjs +60 -60
  25. package/README.md +24 -1
  26. package/package.json +1 -1
  27. package/src/components/ProxyViewer.tsx +9 -2
  28. package/src/components/groups/GroupsDialog.tsx +732 -0
  29. package/src/lib/groupContract.ts +148 -0
  30. package/src/lib/runContract.ts +2 -0
  31. package/src/lib/useGroupEvidence.ts +33 -0
  32. package/src/lib/useGroups.ts +28 -0
  33. package/src/mcp/server.ts +282 -1
  34. package/src/mcp/toolHandlers.ts +123 -0
  35. package/src/proxy/groupEvidenceExporter.ts +354 -0
  36. package/src/proxy/groupStore.ts +259 -0
  37. package/src/routes/api/groups.$groupId.evidence.ts +74 -0
  38. package/src/routes/api/groups.$groupId.sessions.ts +50 -0
  39. package/src/routes/api/groups.$groupId.ts +50 -0
  40. package/src/routes/api/groups.ts +39 -0
  41. package/.output/public/assets/ProxyViewerContainer-D650kQlv.js +0 -115
  42. package/.output/public/assets/_sessionId-wctOoGh_.js +0 -1
  43. package/.output/public/assets/index-BhQGSdhG.js +0 -1
  44. package/.output/public/assets/index-CwlHPmgL.css +0 -1
  45. package/.output/server/_tanstack-start-manifest_v-BrfUXnre.mjs +0 -4
@@ -0,0 +1,148 @@
1
+ import { z } from "zod";
2
+ import { JsonValueSchema } from "../proxy/schemas";
3
+ import { EvidenceReportSchema, InspectorRunSchema, InspectorRunStatusSchema } from "./runContract";
4
+ import { SessionInfoSchema, SessionTokenUsageSchema } from "./sessionInfoContract";
5
+
6
+ export const InspectorGroupStatusSchema = z.enum([
7
+ "created",
8
+ "running",
9
+ "completed",
10
+ "failed",
11
+ "cancelled",
12
+ ]);
13
+
14
+ export const InspectorGroupKindSchema = z.enum(["evaluation", "batch", "manual"]);
15
+
16
+ export const InspectorGroupEvidenceSchema = z.object({
17
+ jsonPath: z.string(),
18
+ markdownPath: z.string(),
19
+ htmlPath: z.string(),
20
+ exportedAt: z.string(),
21
+ });
22
+
23
+ export const InspectorGroupMemberSchema = z.object({
24
+ id: z.string(),
25
+ groupId: z.string(),
26
+ sessionId: z.string(),
27
+ runId: z.string().nullable(),
28
+ label: z.string().nullable(),
29
+ agent: z.string().nullable(),
30
+ provider: z.string().nullable(),
31
+ model: z.string().nullable(),
32
+ status: InspectorRunStatusSchema.nullable(),
33
+ metadata: z.record(z.string(), JsonValueSchema),
34
+ addedAt: z.string(),
35
+ updatedAt: z.string(),
36
+ });
37
+
38
+ export const InspectorGroupSchema = z.object({
39
+ id: z.string(),
40
+ title: z.string(),
41
+ kind: InspectorGroupKindSchema,
42
+ task: z.string().nullable(),
43
+ project: z.string().nullable(),
44
+ status: InspectorGroupStatusSchema,
45
+ tags: z.array(z.string()),
46
+ metadata: z.record(z.string(), JsonValueSchema),
47
+ evidence: InspectorGroupEvidenceSchema.nullable(),
48
+ members: z.array(InspectorGroupMemberSchema),
49
+ createdAt: z.string(),
50
+ updatedAt: z.string(),
51
+ });
52
+
53
+ export const CreateInspectorGroupInputSchema = z
54
+ .object({
55
+ groupId: z.string().min(1).optional(),
56
+ title: z.string().min(1).optional(),
57
+ kind: InspectorGroupKindSchema.optional(),
58
+ task: z.string().nullable().optional(),
59
+ project: z.string().nullable().optional(),
60
+ status: InspectorGroupStatusSchema.optional(),
61
+ tags: z.array(z.string()).optional(),
62
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
63
+ })
64
+ .optional();
65
+
66
+ export const UpdateInspectorGroupInputSchema = z.object({
67
+ title: z.string().min(1).optional(),
68
+ kind: InspectorGroupKindSchema.optional(),
69
+ task: z.string().nullable().optional(),
70
+ project: z.string().nullable().optional(),
71
+ status: InspectorGroupStatusSchema.optional(),
72
+ tags: z.array(z.string()).optional(),
73
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
74
+ });
75
+
76
+ export const AddInspectorGroupSessionInputSchema = z.object({
77
+ memberId: z.string().min(1).optional(),
78
+ sessionId: z.string().min(1),
79
+ runId: z.string().min(1).nullable().optional(),
80
+ label: z.string().min(1).nullable().optional(),
81
+ agent: z.string().min(1).nullable().optional(),
82
+ provider: z.string().min(1).nullable().optional(),
83
+ model: z.string().min(1).nullable().optional(),
84
+ status: InspectorRunStatusSchema.nullable().optional(),
85
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
86
+ });
87
+
88
+ export const GroupEvidenceExportOptionsSchema = z
89
+ .object({
90
+ includeHistory: z.boolean().optional(),
91
+ latestLogLimit: z.number().int().positive().max(50).optional(),
92
+ })
93
+ .optional();
94
+
95
+ export const InspectorGroupExportMemberSchema = z.object({
96
+ member: InspectorGroupMemberSchema,
97
+ run: InspectorRunSchema.nullable(),
98
+ session: SessionInfoSchema.nullable(),
99
+ });
100
+
101
+ export const InspectorGroupEvidenceSummarySchema = z.object({
102
+ memberCount: z.number().int().nonnegative(),
103
+ sessionsWithData: z.number().int().nonnegative(),
104
+ missingSessions: z.number().int().nonnegative(),
105
+ requestCount: z.number().int().nonnegative(),
106
+ errorCount: z.number().int().nonnegative(),
107
+ tokenUsage: SessionTokenUsageSchema,
108
+ models: z.array(z.string()),
109
+ providers: z.array(z.string()),
110
+ completedMembers: z.number().int().nonnegative(),
111
+ failedMembers: z.number().int().nonnegative(),
112
+ runningMembers: z.number().int().nonnegative(),
113
+ });
114
+
115
+ export const GroupEvidenceReadResponseSchema = z.object({
116
+ group: InspectorGroupSchema,
117
+ evidence: InspectorGroupEvidenceSchema,
118
+ markdown: z.string(),
119
+ summary: InspectorGroupEvidenceSummarySchema.nullable(),
120
+ report: EvidenceReportSchema.nullable(),
121
+ });
122
+
123
+ export const GroupEvidenceExportResultSchema = z.object({
124
+ group: InspectorGroupSchema,
125
+ evidence: InspectorGroupEvidenceSchema,
126
+ members: z.array(InspectorGroupExportMemberSchema),
127
+ summary: InspectorGroupEvidenceSummarySchema,
128
+ jenkinsReport: EvidenceReportSchema,
129
+ });
130
+
131
+ export const InspectorGroupsListResponseSchema = z.object({
132
+ groups: z.array(InspectorGroupSchema),
133
+ });
134
+
135
+ export type InspectorGroupStatus = z.infer<typeof InspectorGroupStatusSchema>;
136
+ export type InspectorGroupKind = z.infer<typeof InspectorGroupKindSchema>;
137
+ export type InspectorGroupEvidence = z.infer<typeof InspectorGroupEvidenceSchema>;
138
+ export type InspectorGroupMember = z.infer<typeof InspectorGroupMemberSchema>;
139
+ export type InspectorGroup = z.infer<typeof InspectorGroupSchema>;
140
+ export type CreateInspectorGroupInput = z.infer<typeof CreateInspectorGroupInputSchema>;
141
+ export type UpdateInspectorGroupInput = z.infer<typeof UpdateInspectorGroupInputSchema>;
142
+ export type AddInspectorGroupSessionInput = z.infer<typeof AddInspectorGroupSessionInputSchema>;
143
+ export type GroupEvidenceExportOptions = z.infer<typeof GroupEvidenceExportOptionsSchema>;
144
+ export type InspectorGroupExportMember = z.infer<typeof InspectorGroupExportMemberSchema>;
145
+ export type InspectorGroupEvidenceSummary = z.infer<typeof InspectorGroupEvidenceSummarySchema>;
146
+ export type GroupEvidenceReadResponse = z.infer<typeof GroupEvidenceReadResponseSchema>;
147
+ export type GroupEvidenceExportResult = z.infer<typeof GroupEvidenceExportResultSchema>;
148
+ export type InspectorGroupsListResponse = z.infer<typeof InspectorGroupsListResponseSchema>;
@@ -71,6 +71,7 @@ export const JenkinsReportSchema = z.object({
71
71
  summary: z.string(),
72
72
  markdown: z.string(),
73
73
  });
74
+ export const EvidenceReportSchema = JenkinsReportSchema;
74
75
 
75
76
  export const InspectorRunSchema = z.object({
76
77
  id: z.string(),
@@ -153,6 +154,7 @@ export type RunTimelineEvent = z.infer<typeof RunTimelineEventSchema>;
153
154
  export type FailureCategory = z.infer<typeof FailureCategorySchema>;
154
155
  export type FailureClassification = z.infer<typeof FailureClassificationSchema>;
155
156
  export type JenkinsReport = z.infer<typeof JenkinsReportSchema>;
157
+ export type EvidenceReport = JenkinsReport;
156
158
  export type InspectorRun = z.infer<typeof InspectorRunSchema>;
157
159
  export type CreateInspectorRunInput = z.infer<typeof CreateInspectorRunInputSchema>;
158
160
  export type UpdateInspectorRunInput = z.infer<typeof UpdateInspectorRunInputSchema>;
@@ -0,0 +1,33 @@
1
+ import useSWR, { type SWRResponse } from "swr";
2
+ import {
3
+ GroupEvidenceReadResponseSchema,
4
+ type GroupEvidenceReadResponse,
5
+ type InspectorGroup,
6
+ } from "./groupContract";
7
+ import { fetchJson } from "./apiClient";
8
+
9
+ type UseGroupEvidenceResult = {
10
+ evidenceResponse: GroupEvidenceReadResponse | null;
11
+ isLoading: boolean;
12
+ error: Error | undefined;
13
+ mutate: SWRResponse<GroupEvidenceReadResponse, Error>["mutate"];
14
+ };
15
+
16
+ function evidenceKey(group: InspectorGroup | null): string | null {
17
+ if (group === null || group.evidence === null) return null;
18
+ return `/api/groups/${encodeURIComponent(group.id)}/evidence`;
19
+ }
20
+
21
+ export function useGroupEvidence(group: InspectorGroup | null): UseGroupEvidenceResult {
22
+ const response: SWRResponse<GroupEvidenceReadResponse, Error> = useSWR<
23
+ GroupEvidenceReadResponse,
24
+ Error
25
+ >(evidenceKey(group), (url: string) => fetchJson(url, GroupEvidenceReadResponseSchema));
26
+
27
+ return {
28
+ evidenceResponse: response.data ?? null,
29
+ isLoading: response.isLoading,
30
+ error: response.error,
31
+ mutate: response.mutate,
32
+ };
33
+ }
@@ -0,0 +1,28 @@
1
+ import useSWR, { type SWRResponse } from "swr";
2
+ import {
3
+ InspectorGroupsListResponseSchema,
4
+ type InspectorGroup,
5
+ type InspectorGroupsListResponse,
6
+ } from "./groupContract";
7
+ import { fetchJson } from "./apiClient";
8
+
9
+ type UseGroupsResult = {
10
+ groups: InspectorGroup[];
11
+ isLoading: boolean;
12
+ error: Error | undefined;
13
+ mutate: SWRResponse<InspectorGroupsListResponse, Error>["mutate"];
14
+ };
15
+
16
+ export function useGroups(): UseGroupsResult {
17
+ const response: SWRResponse<InspectorGroupsListResponse, Error> = useSWR<
18
+ InspectorGroupsListResponse,
19
+ Error
20
+ >("/api/groups", (url: string) => fetchJson(url, InspectorGroupsListResponseSchema));
21
+
22
+ return {
23
+ groups: response.data?.groups ?? [],
24
+ isLoading: response.isLoading,
25
+ error: response.error,
26
+ mutate: response.mutate,
27
+ };
28
+ }
package/src/mcp/server.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { z } from "zod";
19
+ import packageJson from "../../package.json";
19
20
  import {
20
21
  type GetPromptResult,
21
22
  type ListResourcesResult,
@@ -25,6 +26,14 @@ import {
25
26
  type Variables,
26
27
  WebStandardStreamableHTTPServerTransport,
27
28
  } from "@modelcontextprotocol/server";
29
+ import {
30
+ AddInspectorGroupSessionInputSchema,
31
+ GroupEvidenceExportOptionsSchema,
32
+ InspectorGroupSchema,
33
+ InspectorGroupStatusSchema,
34
+ InspectorGroupsListResponseSchema,
35
+ UpdateInspectorGroupInputSchema,
36
+ } from "../lib/groupContract";
28
37
  import {
29
38
  InspectorRunSchema,
30
39
  InspectorRunStatusSchema,
@@ -34,10 +43,14 @@ import { setCurrentPort } from "../lib/serverPort";
34
43
  import { JsonValueSchema } from "../proxy/schemas";
35
44
  import { callApi } from "./loopback";
36
45
  import {
46
+ addGroupSessionImpl,
37
47
  addProviderImpl,
48
+ createGroupImpl,
38
49
  createRunImpl,
39
50
  createSessionKnowledgeImpl,
40
51
  exportEvidenceImpl,
52
+ exportGroupEvidenceImpl,
53
+ getGroupImpl,
41
54
  getRecentFailuresImpl,
42
55
  getProjectContextImpl,
43
56
  getRunImpl,
@@ -45,6 +58,7 @@ import {
45
58
  getLogChunksImpl,
46
59
  getLogImpl,
47
60
  getProviderImpl,
61
+ listGroupsImpl,
48
62
  listKnowledgeCandidatesImpl,
49
63
  listLogsImpl,
50
64
  listModelsImpl,
@@ -56,6 +70,7 @@ import {
56
70
  searchLogsImpl,
57
71
  searchKnowledgeImpl,
58
72
  testProviderImpl,
73
+ updateGroupImpl,
59
74
  updateRunImpl,
60
75
  updateProviderImpl,
61
76
  } from "./toolHandlers";
@@ -66,12 +81,14 @@ import {
66
81
 
67
82
  type ServerPair = { server: McpServer; transport: WebStandardStreamableHTTPServerTransport };
68
83
 
84
+ export const MCP_SERVER_VERSION = packageJson.version;
85
+
69
86
  let initialized: ServerPair | null = null;
70
87
  let initPromise: Promise<ServerPair> | null = null;
71
88
 
72
89
  function buildServer(): ServerPair {
73
90
  const server = new McpServer(
74
- { name: "agent-inspector", version: "1.0.0" },
91
+ { name: "agent-inspector", version: MCP_SERVER_VERSION },
75
92
  { capabilities: { tools: {}, resources: {}, prompts: {} } },
76
93
  );
77
94
  registerTools(server);
@@ -172,6 +189,10 @@ const EvidenceReadResponseSchema = z.object({
172
189
  markdown: z.string(),
173
190
  });
174
191
 
192
+ const GroupEvidenceReadResponseSchema = z.object({
193
+ markdown: z.string(),
194
+ });
195
+
175
196
  type ResourceDescriptor = {
176
197
  uri: string;
177
198
  name: string;
@@ -270,6 +291,66 @@ async function listRunResources(): Promise<ListResourcesResult> {
270
291
  }
271
292
  }
272
293
 
294
+ async function listGroupResources(): Promise<ListResourcesResult> {
295
+ try {
296
+ const response = await callApi("/api/groups");
297
+ if (!response.ok) return listResult([]);
298
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
299
+ if (!parsed.success) return listResult([]);
300
+ return listResult(
301
+ parsed.data.groups.map((group) => ({
302
+ uri: `inspector://groups/${encodeURIComponent(group.id)}`,
303
+ name: `group:${group.id}`,
304
+ title: group.title,
305
+ mimeType: "application/json",
306
+ })),
307
+ );
308
+ } catch {
309
+ return listResult([]);
310
+ }
311
+ }
312
+
313
+ async function listGroupSessionResources(): Promise<ListResourcesResult> {
314
+ try {
315
+ const response = await callApi("/api/groups");
316
+ if (!response.ok) return listResult([]);
317
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
318
+ if (!parsed.success) return listResult([]);
319
+ return listResult(
320
+ parsed.data.groups.map((group) => ({
321
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/sessions`,
322
+ name: `group-sessions:${group.id}`,
323
+ title: `${group.title} sessions`,
324
+ mimeType: "application/json",
325
+ })),
326
+ );
327
+ } catch {
328
+ return listResult([]);
329
+ }
330
+ }
331
+
332
+ async function listGroupEvidenceResources(): Promise<ListResourcesResult> {
333
+ try {
334
+ const response = await callApi("/api/groups");
335
+ if (!response.ok) return listResult([]);
336
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
337
+ if (!parsed.success) return listResult([]);
338
+ return listResult(
339
+ parsed.data.groups
340
+ .filter((group) => group.evidence !== null)
341
+ .map((group) => ({
342
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/evidence`,
343
+ name: `group-evidence:${group.id}`,
344
+ title: `${group.title} evidence pack`,
345
+ mimeType: "text/markdown",
346
+ description: group.evidence?.markdownPath,
347
+ })),
348
+ );
349
+ } catch {
350
+ return listResult([]);
351
+ }
352
+ }
353
+
273
354
  async function listEvidenceResources(): Promise<ListResourcesResult> {
274
355
  try {
275
356
  const response = await callApi("/api/runs");
@@ -520,6 +601,92 @@ function registerTools(server: McpServer): void {
520
601
  (args) => safeCall(() => getRecentFailuresImpl(callApi, args)),
521
602
  );
522
603
 
604
+ server.registerTool(
605
+ "inspector_list_groups",
606
+ {
607
+ title: "List Inspector groups",
608
+ description:
609
+ "Lists declared Inspector groups. Groups aggregate multiple sessions/runs, commonly one evaluation matrix spanning several providers or models.",
610
+ inputSchema: z.object({}),
611
+ },
612
+ () => safeCall(() => listGroupsImpl(callApi)),
613
+ );
614
+
615
+ server.registerTool(
616
+ "inspector_create_group",
617
+ {
618
+ title: "Create or declare an Inspector group",
619
+ description:
620
+ "Creates an evaluation/batch group that external systems can use to aggregate multiple OpenCode/Codex sessions across models such as MiniMax, DeepSeek, or GLM.",
621
+ inputSchema: z.object({
622
+ groupId: z.string().min(1).optional().describe("Stable group id. Generated when omitted."),
623
+ title: z.string().min(1).optional().describe("Human-readable group title."),
624
+ kind: z
625
+ .enum(["evaluation", "batch", "manual"])
626
+ .optional()
627
+ .describe("Group kind. Defaults to evaluation."),
628
+ task: z.string().nullable().optional().describe("Task, benchmark, or evaluation goal."),
629
+ project: z.string().nullable().optional().describe("Project or repository under test."),
630
+ status: InspectorGroupStatusSchema.optional().describe("Initial group status."),
631
+ tags: z.array(z.string()).optional().describe("Free-form group labels."),
632
+ metadata: z.record(z.string(), JsonValueSchema).optional().describe("JSON metadata."),
633
+ }),
634
+ },
635
+ (args) => safeCall(() => createGroupImpl(callApi, args)),
636
+ );
637
+
638
+ server.registerTool(
639
+ "inspector_get_group",
640
+ {
641
+ title: "Get an Inspector group",
642
+ description:
643
+ "Returns one declared Inspector group with its attached session/run members and exported evidence paths when available.",
644
+ inputSchema: z.object({
645
+ groupId: z.string().min(1).describe("The Inspector group id."),
646
+ }),
647
+ },
648
+ (args) => safeCall(() => getGroupImpl(callApi, args)),
649
+ );
650
+
651
+ server.registerTool(
652
+ "inspector_update_group",
653
+ {
654
+ title: "Update an Inspector group",
655
+ description:
656
+ "PATCH-style update for a declared group. Use this to mark a whole evaluation running, failed, completed, or to merge CI metadata.",
657
+ inputSchema: UpdateInspectorGroupInputSchema.extend({
658
+ groupId: z.string().min(1).describe("The Inspector group id."),
659
+ }),
660
+ },
661
+ (args) => safeCall(() => updateGroupImpl(callApi, args)),
662
+ );
663
+
664
+ server.registerTool(
665
+ "inspector_add_group_session",
666
+ {
667
+ title: "Attach a session to an Inspector group",
668
+ description:
669
+ "Adds or updates one group member. Use it after each OpenCode process discovers its session id, or while an evaluation matrix is running to attach model/provider labels.",
670
+ inputSchema: AddInspectorGroupSessionInputSchema.extend({
671
+ groupId: z.string().min(1).describe("The Inspector group id."),
672
+ }),
673
+ },
674
+ (args) => safeCall(() => addGroupSessionImpl(callApi, args)),
675
+ );
676
+
677
+ server.registerTool(
678
+ "inspector_export_group_evidence",
679
+ {
680
+ title: "Export an Inspector group evidence pack",
681
+ description:
682
+ "Exports a whole group's evidence pack to local JSON, Markdown, and HTML files under <dataDir>/evidence/groups/<groupId>. The export contains a provider/model matrix and compact session summaries.",
683
+ inputSchema: GroupEvidenceExportOptionsSchema.unwrap().extend({
684
+ groupId: z.string().min(1).describe("The Inspector group id."),
685
+ }),
686
+ },
687
+ (args) => safeCall(() => exportGroupEvidenceImpl(callApi, args)),
688
+ );
689
+
523
690
  server.registerTool(
524
691
  "inspector_list_models",
525
692
  {
@@ -763,6 +930,49 @@ function registerResources(server: McpServer): void {
763
930
  (uri) => readApiJsonResource(uri, "/api/runs"),
764
931
  );
765
932
 
933
+ server.registerResource(
934
+ "inspector_groups",
935
+ "inspector://groups",
936
+ {
937
+ title: "Inspector groups",
938
+ description: "Declared Inspector groups aggregating multiple sessions or runs.",
939
+ mimeType: "application/json",
940
+ },
941
+ (uri) => readApiJsonResource(uri, "/api/groups"),
942
+ );
943
+
944
+ server.registerResource(
945
+ "inspector_group",
946
+ new ResourceTemplate("inspector://groups/{groupId}", { list: listGroupResources }),
947
+ {
948
+ title: "Inspector group",
949
+ description: "One declared Inspector group with attached session/run members.",
950
+ mimeType: "application/json",
951
+ },
952
+ (uri, variables) => {
953
+ const groupId = variableString(variables, "groupId");
954
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
955
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}`);
956
+ },
957
+ );
958
+
959
+ server.registerResource(
960
+ "inspector_group_sessions",
961
+ new ResourceTemplate("inspector://groups/{groupId}/sessions", {
962
+ list: listGroupSessionResources,
963
+ }),
964
+ {
965
+ title: "Inspector group sessions",
966
+ description: "Attached session/run members for one Inspector group.",
967
+ mimeType: "application/json",
968
+ },
969
+ (uri, variables) => {
970
+ const groupId = variableString(variables, "groupId");
971
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
972
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}/sessions`);
973
+ },
974
+ );
975
+
766
976
  server.registerResource(
767
977
  "inspector_recent_failures",
768
978
  "inspector://failures/recent",
@@ -819,6 +1029,38 @@ function registerResources(server: McpServer): void {
819
1029
  }
820
1030
  },
821
1031
  );
1032
+
1033
+ server.registerResource(
1034
+ "inspector_group_evidence",
1035
+ new ResourceTemplate("inspector://groups/{groupId}/evidence", {
1036
+ list: listGroupEvidenceResources,
1037
+ }),
1038
+ {
1039
+ title: "Inspector group evidence pack",
1040
+ description:
1041
+ "Markdown evidence pack for an Inspector group. Generate it first with inspector_export_group_evidence.",
1042
+ mimeType: "text/markdown",
1043
+ },
1044
+ async (uri, variables) => {
1045
+ const groupId = variableString(variables, "groupId");
1046
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
1047
+ const path = `/api/groups/${encodeURIComponent(groupId)}/evidence`;
1048
+ try {
1049
+ const response = await callApi(path);
1050
+ if (!response.ok) {
1051
+ return textResource(uri, "text/plain", `GET ${path} returned ${response.status}`);
1052
+ }
1053
+ const parsed = GroupEvidenceReadResponseSchema.safeParse(await response.json());
1054
+ if (!parsed.success) {
1055
+ return textResource(uri, "text/plain", "Evidence endpoint returned an unparseable body");
1056
+ }
1057
+ return textResource(uri, "text/markdown", parsed.data.markdown);
1058
+ } catch (err) {
1059
+ const message = err instanceof Error ? err.message : String(err);
1060
+ return textResource(uri, "text/plain", `Resource read failed: ${message}`);
1061
+ }
1062
+ },
1063
+ );
822
1064
  }
823
1065
 
824
1066
  function registerPrompts(server: McpServer): void {
@@ -911,6 +1153,34 @@ The report should include:
911
1153
  5. Reproducible next action for a developer`),
912
1154
  );
913
1155
 
1156
+ server.registerPrompt(
1157
+ "inspector_generate_group_report",
1158
+ {
1159
+ title: "Generate group report",
1160
+ description:
1161
+ "Generate a model/provider evaluation report from an Inspector group and group evidence pack.",
1162
+ argsSchema: z.object({
1163
+ groupId: z.string().min(1).describe("The Inspector group id."),
1164
+ }),
1165
+ },
1166
+ ({ groupId }) =>
1167
+ userPrompt(`Generate a model/provider evaluation report for this Inspector group.
1168
+
1169
+ Evidence to read:
1170
+ - Group resource: inspector://groups/${encodeURIComponent(groupId)}
1171
+ - Group session members: inspector://groups/${encodeURIComponent(groupId)}/sessions
1172
+ - Group evidence pack: inspector://groups/${encodeURIComponent(groupId)}/evidence
1173
+
1174
+ If the evidence pack resource says it has not been exported, call inspector_export_group_evidence for this group first.
1175
+
1176
+ The report should include:
1177
+ 1. Evaluation status summary
1178
+ 2. Provider/model matrix with session links
1179
+ 3. Request counts, token totals, streaming timing, and error counts
1180
+ 4. Winner/loser observations when the evidence supports them
1181
+ 5. Concrete next actions for failed or missing sessions`),
1182
+ );
1183
+
914
1184
  server.registerPrompt(
915
1185
  "inspector_extract_repro_steps",
916
1186
  {
@@ -950,6 +1220,12 @@ export const TOOL_NAMES = [
950
1220
  "inspector_update_run",
951
1221
  "inspector_export_evidence",
952
1222
  "inspector_get_recent_failures",
1223
+ "inspector_list_groups",
1224
+ "inspector_create_group",
1225
+ "inspector_get_group",
1226
+ "inspector_update_group",
1227
+ "inspector_add_group_session",
1228
+ "inspector_export_group_evidence",
953
1229
  "inspector_list_models",
954
1230
  "inspector_list_providers",
955
1231
  "inspector_get_provider",
@@ -972,11 +1248,16 @@ export const RESOURCE_NAMES = [
972
1248
  "inspector_recent_failures",
973
1249
  "inspector_run",
974
1250
  "inspector_evidence",
1251
+ "inspector_groups",
1252
+ "inspector_group",
1253
+ "inspector_group_sessions",
1254
+ "inspector_group_evidence",
975
1255
  ] as const;
976
1256
 
977
1257
  export const PROMPT_NAMES = [
978
1258
  "inspector_analyze_session_failure",
979
1259
  "inspector_triage_recent_failures",
980
1260
  "inspector_generate_jenkins_report",
1261
+ "inspector_generate_group_report",
981
1262
  "inspector_extract_repro_steps",
982
1263
  ] as const;