@inspectr/mcplab-mcp-server 1.8.2 → 1.9.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/browser-provider-library.test.d.ts +2 -0
- package/dist/browser-provider-library.test.d.ts.map +1 -0
- package/dist/browser-provider-library.test.js +129 -0
- package/dist/browser-provider-library.test.js.map +1 -0
- package/dist/http-handler.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp-run-calculations.d.ts +35 -42
- package/dist/mcp-run-calculations.d.ts.map +1 -1
- package/dist/mcp-run-calculations.js +16 -7
- package/dist/mcp-run-calculations.js.map +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/runtime.contracts.test.d.ts +1 -1
- package/dist/runtime.contracts.test.js +29 -1
- package/dist/runtime.contracts.test.js.map +1 -1
- package/dist/runtime.d.ts +15 -21
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +176 -28
- package/dist/runtime.js.map +1 -1
- package/dist/server-factory.d.ts +1 -1
- package/dist/skills/mcplab-assistant/SKILL.md +17 -2
- package/dist/skills/mcplab-assistant/references/browser-agents.md +122 -0
- package/dist/skills.d.ts +21 -29
- package/dist/skills.js.map +1 -1
- package/dist/skills.test.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/package.json +5 -5
package/dist/runtime.d.ts
CHANGED
|
@@ -2,34 +2,28 @@ import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
4
|
export type SessionRuntime = {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
transport: StreamableHTTPServerTransport;
|
|
6
|
+
server: McpServer;
|
|
7
7
|
};
|
|
8
8
|
export interface McplabMcpServerOptions {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
path: string;
|
|
12
|
+
logger?: Pick<Console, 'log' | 'error'>;
|
|
13
13
|
}
|
|
14
14
|
export interface McplabMcpServerRuntime {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
path: string;
|
|
18
|
+
close(): Promise<void>;
|
|
19
19
|
}
|
|
20
|
-
export declare function startMcplabMcpServer(
|
|
21
|
-
options: McplabMcpServerOptions
|
|
22
|
-
): Promise<McplabMcpServerRuntime>;
|
|
20
|
+
export declare function startMcplabMcpServer(options: McplabMcpServerOptions): Promise<McplabMcpServerRuntime>;
|
|
23
21
|
export declare function defaultMcplabMcpServerOptionsFromEnv(): McplabMcpServerOptions;
|
|
24
22
|
export declare function createConfiguredServer(): McpServer;
|
|
25
23
|
export declare function registerTools(server: McpServer): void;
|
|
26
24
|
export declare function registerPrompts(server: McpServer): void;
|
|
27
|
-
export declare function handleMcplabMcpHttpRequest(
|
|
28
|
-
req: IncomingMessage,
|
|
29
|
-
res: ServerResponse,
|
|
30
|
-
sessions: Map<string, SessionRuntime>,
|
|
31
|
-
options?: {
|
|
25
|
+
export declare function handleMcplabMcpHttpRequest(req: IncomingMessage, res: ServerResponse, sessions: Map<string, SessionRuntime>, options?: {
|
|
32
26
|
path?: string;
|
|
33
|
-
|
|
34
|
-
): Promise<void>;
|
|
35
|
-
//# sourceMappingURL=runtime.d.ts.map
|
|
27
|
+
logger?: Pick<Console, 'log' | 'error'>;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AA+uBnG,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,6BAA6B,CAAC;IACzC,MAAM,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAoDjC;AAED,wBAAgB,oCAAoC,IAAI,sBAAsB,CAM7E;AAED,wBAAgB,sBAAsB,IAAI,SAAS,CAclD;AA8CD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8yErD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0EvD;AAmvCD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,EACrC,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,OAAO,CAAC,CAAA;CAAE,GACnE,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
package/dist/runtime.js
CHANGED
|
@@ -5,7 +5,7 @@ import { basename, dirname, extname, resolve, join, sep, relative } from 'node:p
|
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
7
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
-
import { loadConfig, hashConfig, runAll, selectScenarios, loadOrBuildSearchIndex, indexNeedsRefresh, getResultsIndexPaths, resolveRunArtifactPath, searchDocs, getContext, applyRuntimeServerOverrides, readLibraryAgentsAndServers, resolveScenarioLibraryDir, createEvaluationConfigFile, updateEvaluationConfigFile, readEvaluationConfigFile, createTestCaseFile, readTestCaseFile, updateTestCaseFile, buildScenarioEntry } from '@inspectr/mcplab-core';
|
|
8
|
+
import { loadConfig, hashConfig, runAll, selectScenarios, loadOrBuildSearchIndex, indexNeedsRefresh, getResultsIndexPaths, resolveRunArtifactPath, searchDocs, getContext, applyRuntimeServerOverrides, readLibraryAgentsAndServers, readBrowserProviderProfiles, serializeBrowserProviderProfile, resolveScenarioLibraryDir, createEvaluationConfigFile, updateEvaluationConfigFile, readEvaluationConfigFile, createTestCaseFile, readTestCaseFile, updateTestCaseFile, buildScenarioEntry, DEFAULT_BROWSER_PROVIDER_PROFILES, mergeBrowserProviderProfiles } from '@inspectr/mcplab-core';
|
|
9
9
|
import { renderReport } from '@inspectr/mcplab-reporting';
|
|
10
10
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
11
11
|
import { z } from 'zod';
|
|
@@ -25,7 +25,7 @@ const GenericObjectSchema = z.object({}).passthrough();
|
|
|
25
25
|
const ResultsSummarySchema = z.object({
|
|
26
26
|
total_scenarios: z.number().int().nonnegative(),
|
|
27
27
|
total_runs: z.number().int().nonnegative(),
|
|
28
|
-
pass_rate: z.number(),
|
|
28
|
+
pass_rate: z.number().nullable(),
|
|
29
29
|
avg_tool_calls_per_run: z.number(),
|
|
30
30
|
avg_tool_latency_ms: z.number().nullable()
|
|
31
31
|
});
|
|
@@ -44,7 +44,8 @@ const MetricSummarySchema = z.object({
|
|
|
44
44
|
total_runs: z.number().int().nonnegative(),
|
|
45
45
|
passed_runs: z.number().int().nonnegative(),
|
|
46
46
|
failed_runs: z.number().int().nonnegative(),
|
|
47
|
-
|
|
47
|
+
stopped_runs: z.number().int().nonnegative().optional(),
|
|
48
|
+
pass_rate: z.number().nullable(),
|
|
48
49
|
avg_tool_calls_per_run: z.number(),
|
|
49
50
|
avg_tool_latency_ms: z.number().nullable()
|
|
50
51
|
});
|
|
@@ -66,7 +67,7 @@ const CompareRowSchema = z.object({
|
|
|
66
67
|
key: z.string(),
|
|
67
68
|
scenario_id: z.string(),
|
|
68
69
|
agent: z.string(),
|
|
69
|
-
classification: z.enum(['regressed', 'improved', 'unchanged', 'new', 'missing']),
|
|
70
|
+
classification: z.enum(['regressed', 'improved', 'unchanged', 'new', 'missing', 'not_evaluated']),
|
|
70
71
|
left: MetricSummarySchema.nullable(),
|
|
71
72
|
right: MetricSummarySchema.nullable(),
|
|
72
73
|
deltas: z.object({
|
|
@@ -96,6 +97,13 @@ const AgentEntrySchema = z.object({
|
|
|
96
97
|
max_tokens: z.number().int().positive().optional(),
|
|
97
98
|
system: z.string().optional()
|
|
98
99
|
});
|
|
100
|
+
const BrowserAgentEntrySchema = z.object({
|
|
101
|
+
type: z.literal('browser'),
|
|
102
|
+
name: z.string().optional(),
|
|
103
|
+
provider: z.string(),
|
|
104
|
+
url: z.string(),
|
|
105
|
+
new_conversation_between_scenarios: z.boolean().optional()
|
|
106
|
+
});
|
|
99
107
|
const LibraryServerEntryContentSchema = z
|
|
100
108
|
.object({
|
|
101
109
|
transport: z.string().optional(),
|
|
@@ -112,12 +120,17 @@ const LibraryServerEntrySchema = z.object({
|
|
|
112
120
|
});
|
|
113
121
|
const LibraryAgentEntrySchema = z.object({
|
|
114
122
|
id: z.string(),
|
|
115
|
-
entry: AgentEntrySchema.optional()
|
|
123
|
+
entry: z.union([AgentEntrySchema, BrowserAgentEntrySchema]).optional()
|
|
124
|
+
});
|
|
125
|
+
const LibraryBrowserProviderEntrySchema = z.object({
|
|
126
|
+
id: z.string(),
|
|
127
|
+
entry: GenericObjectSchema.optional()
|
|
116
128
|
});
|
|
117
129
|
const LibraryEntrySchema = z.object({
|
|
118
130
|
bundleRoot: z.string(),
|
|
119
131
|
servers: z.array(LibraryServerEntrySchema),
|
|
120
132
|
agents: z.array(LibraryAgentEntrySchema),
|
|
133
|
+
browser_providers: z.array(LibraryBrowserProviderEntrySchema),
|
|
121
134
|
test_cases: z.array(LibraryScenarioEntrySchema),
|
|
122
135
|
scenarios: z.array(LibraryScenarioEntrySchema)
|
|
123
136
|
});
|
|
@@ -615,7 +628,7 @@ export async function startMcplabMcpServer(options) {
|
|
|
615
628
|
const sessions = new Map();
|
|
616
629
|
const httpServer = createServer(async (req, res) => {
|
|
617
630
|
try {
|
|
618
|
-
await handleHttpRequest(req, res, sessions, options.path);
|
|
631
|
+
await handleHttpRequest(req, res, sessions, options.path, logger);
|
|
619
632
|
}
|
|
620
633
|
catch (error) {
|
|
621
634
|
logger.error('[mcplab-mcp] request error:', error);
|
|
@@ -680,8 +693,51 @@ export function createConfiguredServer() {
|
|
|
680
693
|
registerSkills(server);
|
|
681
694
|
registerTools(server);
|
|
682
695
|
registerPrompts(server);
|
|
696
|
+
patchToolSchemaDialect(server);
|
|
683
697
|
return server;
|
|
684
698
|
}
|
|
699
|
+
const DRAFT_07_DIALECT = 'http://json-schema.org/draft-07/schema#';
|
|
700
|
+
const DRAFT_2020_12_DIALECT = 'https://json-schema.org/draft/2020-12/schema';
|
|
701
|
+
function rewriteJsonSchemaDialect(value) {
|
|
702
|
+
if (Array.isArray(value)) {
|
|
703
|
+
for (const item of value)
|
|
704
|
+
rewriteJsonSchemaDialect(item);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (!value || typeof value !== 'object')
|
|
708
|
+
return;
|
|
709
|
+
const obj = value;
|
|
710
|
+
if (obj.$schema === DRAFT_07_DIALECT) {
|
|
711
|
+
obj.$schema = DRAFT_2020_12_DIALECT;
|
|
712
|
+
}
|
|
713
|
+
for (const key of Object.keys(obj)) {
|
|
714
|
+
rewriteJsonSchemaDialect(obj[key]);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* @modelcontextprotocol/sdk@1.30.0 always stamps tool input/output JSON Schemas
|
|
719
|
+
* converted from Zod v3 as draft-07 ($schema is hardcoded, with no way for a
|
|
720
|
+
* server author to opt into 2020-12 — see
|
|
721
|
+
* https://github.com/modelcontextprotocol/typescript-sdk/issues/2721). Clients
|
|
722
|
+
* that validate structured output strictly against 2020-12 reject every tool
|
|
723
|
+
* call outright, which is what broke every mcplab_* tool after the 1.29.0 ->
|
|
724
|
+
* 1.30.0 bump in this repo. Patch the already-installed tools/list handler to
|
|
725
|
+
* relabel the dialect after the SDK builds its response.
|
|
726
|
+
*
|
|
727
|
+
* TODO: revisit once typescript-sdk#2721 is fixed upstream and we bump past
|
|
728
|
+
* the version that carries the fix — this whole function can then be deleted.
|
|
729
|
+
*/
|
|
730
|
+
function patchToolSchemaDialect(server) {
|
|
731
|
+
const protocol = server.server;
|
|
732
|
+
const original = protocol._requestHandlers.get('tools/list');
|
|
733
|
+
if (!original)
|
|
734
|
+
return;
|
|
735
|
+
protocol._requestHandlers.set('tools/list', async (request, extra) => {
|
|
736
|
+
const result = await original(request, extra);
|
|
737
|
+
rewriteJsonSchemaDialect(result);
|
|
738
|
+
return result;
|
|
739
|
+
});
|
|
740
|
+
}
|
|
685
741
|
export function registerTools(server) {
|
|
686
742
|
const registerTool = (name, config, cb) => {
|
|
687
743
|
const resolvedTitle = resolveToolTitle(name, config.title, config.annotations?.title);
|
|
@@ -1038,7 +1094,7 @@ export function registerTools(server) {
|
|
|
1038
1094
|
outputSchema: LibraryEntrySchema,
|
|
1039
1095
|
inputSchema: {
|
|
1040
1096
|
kind: z
|
|
1041
|
-
.enum(['all', 'servers', 'agents', 'test_cases', 'scenarios'])
|
|
1097
|
+
.enum(['all', 'servers', 'agents', 'browser_providers', 'test_cases', 'scenarios'])
|
|
1042
1098
|
.optional()
|
|
1043
1099
|
.describe('Which library category to list. Defaults to all.'),
|
|
1044
1100
|
includeContent: z
|
|
@@ -1048,15 +1104,16 @@ export function registerTools(server) {
|
|
|
1048
1104
|
}
|
|
1049
1105
|
}, async ({ kind, includeContent }) => {
|
|
1050
1106
|
return withToolHandling(async () => {
|
|
1051
|
-
const root = resolveBundleRoot();
|
|
1052
|
-
const data = readLibrary(root, Boolean(includeContent));
|
|
1053
1107
|
const selectedKind = kind ?? 'all';
|
|
1108
|
+
const root = resolveBundleRoot();
|
|
1109
|
+
const data = readLibrary(root, Boolean(includeContent), selectedKind);
|
|
1054
1110
|
const structured = selectedKind === 'all'
|
|
1055
1111
|
? data
|
|
1056
1112
|
: {
|
|
1057
1113
|
bundleRoot: data.bundleRoot,
|
|
1058
1114
|
servers: selectedKind === 'servers' ? data.servers : [],
|
|
1059
1115
|
agents: selectedKind === 'agents' ? data.agents : [],
|
|
1116
|
+
browser_providers: selectedKind === 'browser_providers' ? data.browser_providers : [],
|
|
1060
1117
|
test_cases: selectedKind === 'test_cases' ? data.test_cases : [],
|
|
1061
1118
|
scenarios: selectedKind === 'scenarios' ? data.scenarios : []
|
|
1062
1119
|
};
|
|
@@ -1064,10 +1121,10 @@ export function registerTools(server) {
|
|
|
1064
1121
|
});
|
|
1065
1122
|
});
|
|
1066
1123
|
registerTool('mcplab_get_library_item', {
|
|
1067
|
-
description: 'Get a specific reusable server, agent, or scenario definition from a MCPLab library bundle and return both structured data and YAML.',
|
|
1124
|
+
description: 'Get a specific reusable server, agent, browser provider, test case, or scenario definition from a MCPLab library bundle and return both structured data and YAML.',
|
|
1068
1125
|
outputSchema: {
|
|
1069
1126
|
bundleRoot: z.string(),
|
|
1070
|
-
kind: z.enum(['servers', 'agents', 'test_cases', 'scenarios']),
|
|
1127
|
+
kind: z.enum(['servers', 'agents', 'browser_providers', 'test_cases', 'scenarios']),
|
|
1071
1128
|
id: z.string(),
|
|
1072
1129
|
file: z.string().optional(),
|
|
1073
1130
|
yaml: z.string(),
|
|
@@ -1075,7 +1132,7 @@ export function registerTools(server) {
|
|
|
1075
1132
|
},
|
|
1076
1133
|
inputSchema: {
|
|
1077
1134
|
kind: z
|
|
1078
|
-
.enum(['servers', 'agents', 'test_cases', 'scenarios'])
|
|
1135
|
+
.enum(['servers', 'agents', 'browser_providers', 'test_cases', 'scenarios'])
|
|
1079
1136
|
.describe('Library category.'),
|
|
1080
1137
|
id: z.string().describe('Entry id (for scenarios this is scenario.id, not filename).')
|
|
1081
1138
|
}
|
|
@@ -1132,6 +1189,46 @@ export function registerTools(server) {
|
|
|
1132
1189
|
});
|
|
1133
1190
|
});
|
|
1134
1191
|
});
|
|
1192
|
+
registerTool('mcplab_generate_browser_agent_entry', {
|
|
1193
|
+
description: 'Generate an agents.yaml entry for a Rover browser agent. Browser agents reference a validated browser provider profile and are executed by Rover, not by an LLM API.',
|
|
1194
|
+
outputSchema: {
|
|
1195
|
+
id: z.string(),
|
|
1196
|
+
entry: z.object({
|
|
1197
|
+
type: z.literal('browser'),
|
|
1198
|
+
name: z.string().optional(),
|
|
1199
|
+
provider: z.string(),
|
|
1200
|
+
url: z.string(),
|
|
1201
|
+
new_conversation_between_scenarios: z.boolean().optional()
|
|
1202
|
+
}),
|
|
1203
|
+
yaml: z.string()
|
|
1204
|
+
},
|
|
1205
|
+
inputSchema: {
|
|
1206
|
+
id: z.string().describe('Browser agent id key (kebab-case recommended).'),
|
|
1207
|
+
name: z.string().optional().describe('Human-readable browser agent name.'),
|
|
1208
|
+
provider: z
|
|
1209
|
+
.string()
|
|
1210
|
+
.min(1)
|
|
1211
|
+
.describe('Provider id from browser-providers/<provider-id>.yaml.'),
|
|
1212
|
+
url: z.string().url().describe('Browser origin where Rover should execute the agent.'),
|
|
1213
|
+
new_conversation_between_scenarios: z
|
|
1214
|
+
.boolean()
|
|
1215
|
+
.optional()
|
|
1216
|
+
.describe('Whether Rover should start a new conversation between scenarios.')
|
|
1217
|
+
}
|
|
1218
|
+
}, async ({ id, name, provider, url, new_conversation_between_scenarios }) => withToolHandling(async () => {
|
|
1219
|
+
const entry = removeUndefined({
|
|
1220
|
+
type: 'browser',
|
|
1221
|
+
name,
|
|
1222
|
+
provider,
|
|
1223
|
+
url,
|
|
1224
|
+
new_conversation_between_scenarios
|
|
1225
|
+
});
|
|
1226
|
+
return ok(`Generated browser agent entry '${id}'`, {
|
|
1227
|
+
id,
|
|
1228
|
+
entry,
|
|
1229
|
+
yaml: stringifyYaml({ [id]: entry }).trimEnd()
|
|
1230
|
+
});
|
|
1231
|
+
}));
|
|
1135
1232
|
registerTool('mcplab_create_test_case', {
|
|
1136
1233
|
description: 'Create one reviewed MCPLab Test Case in mcplab/test-cases/. Put evaluation checks in the eval object for full support of response, tool-input, and agent assertions. Simple shorthand fields are supported for basic cases; do not provide a shorthand field together with its equivalent eval field. This is a scoped YAML write and refuses duplicate IDs.',
|
|
1137
1234
|
outputSchema: { id: z.string(), path: z.string(), test_case: ScenarioEntrySchema },
|
|
@@ -1513,7 +1610,7 @@ export function registerTools(server) {
|
|
|
1513
1610
|
scenarios: z.array(z.object({
|
|
1514
1611
|
scenario_id: z.string(),
|
|
1515
1612
|
agent: z.string(),
|
|
1516
|
-
pass_rate: z.number(),
|
|
1613
|
+
pass_rate: z.number().nullable(),
|
|
1517
1614
|
tool_usage_frequency: z.record(z.number())
|
|
1518
1615
|
})),
|
|
1519
1616
|
effective_server_overrides: z.record(z.array(z.string())),
|
|
@@ -1620,7 +1717,7 @@ export function registerTools(server) {
|
|
|
1620
1717
|
throw new Error('since must be earlier than or equal to until');
|
|
1621
1718
|
const matching = listRunsWithFallback(resolveRunsDir(), undefined, true).filter((entry) => {
|
|
1622
1719
|
const timestamp = String(entry.metadata && typeof entry.metadata === 'object'
|
|
1623
|
-
? entry.metadata.timestamp ?? ''
|
|
1720
|
+
? (entry.metadata.timestamp ?? '')
|
|
1624
1721
|
: '');
|
|
1625
1722
|
const time = new Date(timestamp).getTime();
|
|
1626
1723
|
if (sinceMs === undefined && untilMs === undefined)
|
|
@@ -1814,7 +1911,7 @@ export function registerTools(server) {
|
|
|
1814
1911
|
left: MetricSummarySchema,
|
|
1815
1912
|
right: MetricSummarySchema,
|
|
1816
1913
|
deltas: z.object({
|
|
1817
|
-
pass_rate: z.number(),
|
|
1914
|
+
pass_rate: z.number().nullable(),
|
|
1818
1915
|
failed_runs: z.number(),
|
|
1819
1916
|
avg_tool_calls_per_run: z.number(),
|
|
1820
1917
|
avg_tool_latency_ms: z.number().nullable()
|
|
@@ -1824,7 +1921,8 @@ export function registerTools(server) {
|
|
|
1824
1921
|
improved: z.number().int().nonnegative(),
|
|
1825
1922
|
unchanged: z.number().int().nonnegative(),
|
|
1826
1923
|
new: z.number().int().nonnegative(),
|
|
1827
|
-
missing: z.number().int().nonnegative()
|
|
1924
|
+
missing: z.number().int().nonnegative(),
|
|
1925
|
+
not_evaluated: z.number().int().nonnegative()
|
|
1828
1926
|
})
|
|
1829
1927
|
}),
|
|
1830
1928
|
regressions: z.array(CompareRowSchema),
|
|
@@ -2346,7 +2444,7 @@ export function registerTools(server) {
|
|
|
2346
2444
|
.array(z.object({
|
|
2347
2445
|
scenario_id: z.string(),
|
|
2348
2446
|
agent: z.string(),
|
|
2349
|
-
pass_rate: z.number()
|
|
2447
|
+
pass_rate: z.number().nullable()
|
|
2350
2448
|
}))
|
|
2351
2449
|
.optional()
|
|
2352
2450
|
},
|
|
@@ -2762,15 +2860,18 @@ function mergeLibraryServersAndAgents(config, bundleRoot) {
|
|
|
2762
2860
|
servers: { ...libraries.servers, ...config.servers }
|
|
2763
2861
|
};
|
|
2764
2862
|
}
|
|
2765
|
-
function readLibrary(bundleRoot, includeContent) {
|
|
2863
|
+
function readLibrary(bundleRoot, includeContent, kind = 'all') {
|
|
2766
2864
|
const serversPath = join(bundleRoot, 'servers.yaml');
|
|
2767
2865
|
const agentsPath = join(bundleRoot, 'agents.yaml');
|
|
2768
2866
|
const scenariosDir = resolveScenarioLibraryDir(bundleRoot).path;
|
|
2769
2867
|
const servers = existsSync(serversPath)
|
|
2770
|
-
? parseYaml(readFileSync(serversPath, 'utf8')) ?? {}
|
|
2868
|
+
? (parseYaml(readFileSync(serversPath, 'utf8')) ?? {})
|
|
2771
2869
|
: {};
|
|
2772
2870
|
const agents = existsSync(agentsPath)
|
|
2773
|
-
? parseYaml(readFileSync(agentsPath, 'utf8')) ?? {}
|
|
2871
|
+
? (parseYaml(readFileSync(agentsPath, 'utf8')) ?? {})
|
|
2872
|
+
: {};
|
|
2873
|
+
const browserProviders = kind === 'all' || kind === 'browser_providers'
|
|
2874
|
+
? mergeBrowserProviderProfiles(readBrowserProviderProfiles(bundleRoot))
|
|
2774
2875
|
: {};
|
|
2775
2876
|
const scenarioEntries = [];
|
|
2776
2877
|
if (existsSync(scenariosDir)) {
|
|
@@ -2802,29 +2903,57 @@ function readLibrary(bundleRoot, includeContent) {
|
|
|
2802
2903
|
id,
|
|
2803
2904
|
...(includeContent ? { entry: agents[id] } : {})
|
|
2804
2905
|
})),
|
|
2906
|
+
browser_providers: Object.keys(browserProviders)
|
|
2907
|
+
.sort()
|
|
2908
|
+
.map((id) => ({
|
|
2909
|
+
id,
|
|
2910
|
+
...(includeContent
|
|
2911
|
+
? { entry: browserProviders[id] }
|
|
2912
|
+
: {})
|
|
2913
|
+
})),
|
|
2805
2914
|
test_cases: scenarioEntries,
|
|
2806
2915
|
scenarios: scenarioEntries
|
|
2807
2916
|
};
|
|
2808
2917
|
return out;
|
|
2809
2918
|
}
|
|
2810
2919
|
function getLibraryItem(bundleRoot, kind, id) {
|
|
2811
|
-
if (kind === 'servers' || kind === 'agents') {
|
|
2812
|
-
const
|
|
2920
|
+
if (kind === 'servers' || kind === 'agents' || kind === 'browser_providers') {
|
|
2921
|
+
const directory = kind === 'browser_providers' ? join(bundleRoot, 'browser-providers') : null;
|
|
2922
|
+
const builtIn = kind === 'browser_providers' && Object.hasOwn(DEFAULT_BROWSER_PROVIDER_PROFILES, id)
|
|
2923
|
+
? DEFAULT_BROWSER_PROVIDER_PROFILES[id]
|
|
2924
|
+
: undefined;
|
|
2925
|
+
const file = directory
|
|
2926
|
+
? findBrowserProviderFile(directory, id)
|
|
2927
|
+
: join(bundleRoot, `${kind}.yaml`);
|
|
2928
|
+
if (builtIn && !existsSync(file)) {
|
|
2929
|
+
const content = structuredClone(builtIn);
|
|
2930
|
+
return {
|
|
2931
|
+
bundleRoot,
|
|
2932
|
+
kind,
|
|
2933
|
+
id,
|
|
2934
|
+
yaml: serializeBrowserProviderProfile(content).trimEnd(),
|
|
2935
|
+
content: content
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2813
2938
|
if (!existsSync(file)) {
|
|
2814
2939
|
throw new Error(`Library file not found: ${file}`);
|
|
2815
2940
|
}
|
|
2816
2941
|
const raw = readFileSync(file, 'utf8');
|
|
2817
2942
|
const parsed = parseYaml(raw) ?? {};
|
|
2818
|
-
if (!(id in parsed)) {
|
|
2943
|
+
if (kind !== 'browser_providers' && !(id in parsed)) {
|
|
2819
2944
|
throw new Error(`'${id}' not found in ${file}`);
|
|
2820
2945
|
}
|
|
2821
|
-
const
|
|
2946
|
+
const normalizedEntry = kind === 'browser_providers' ? readBrowserProviderProfiles(bundleRoot)[id] : parsed[id];
|
|
2947
|
+
if (!normalizedEntry)
|
|
2948
|
+
throw new Error(`'${id}' not found in ${file}`);
|
|
2822
2949
|
return {
|
|
2823
2950
|
bundleRoot,
|
|
2824
2951
|
kind,
|
|
2825
2952
|
id,
|
|
2826
|
-
yaml:
|
|
2827
|
-
|
|
2953
|
+
yaml: kind === 'browser_providers'
|
|
2954
|
+
? raw.trimEnd()
|
|
2955
|
+
: stringifyYaml({ [id]: normalizedEntry }).trimEnd(),
|
|
2956
|
+
content: normalizedEntry
|
|
2828
2957
|
};
|
|
2829
2958
|
}
|
|
2830
2959
|
const dir = resolveScenarioLibraryDir(bundleRoot).path;
|
|
@@ -2849,6 +2978,14 @@ function getLibraryItem(bundleRoot, kind, id) {
|
|
|
2849
2978
|
}
|
|
2850
2979
|
throw new Error(`Scenario '${id}' not found in ${dir}`);
|
|
2851
2980
|
}
|
|
2981
|
+
function findBrowserProviderFile(directory, id) {
|
|
2982
|
+
if (!existsSync(directory))
|
|
2983
|
+
return join(directory, `${id}.yaml`);
|
|
2984
|
+
const file = readdirSync(directory)
|
|
2985
|
+
.filter((name) => name.endsWith('.yaml') || name.endsWith('.yml'))
|
|
2986
|
+
.find((name) => basename(name, extname(name)) === id);
|
|
2987
|
+
return join(directory, file ?? `${id}.yaml`);
|
|
2988
|
+
}
|
|
2852
2989
|
function listEvaluationConfigs(params) {
|
|
2853
2990
|
const evalsDir = join(resolveBundleRoot(), 'evals');
|
|
2854
2991
|
const files = [];
|
|
@@ -3719,9 +3856,9 @@ function truncate(text, maxChars) {
|
|
|
3719
3856
|
return `${text.slice(0, maxChars)}\n...[truncated ${text.length - maxChars} chars]`;
|
|
3720
3857
|
}
|
|
3721
3858
|
export async function handleMcplabMcpHttpRequest(req, res, sessions, options) {
|
|
3722
|
-
await handleHttpRequest(req, res, sessions, options?.path ?? DEFAULT_MCP_PATH);
|
|
3859
|
+
await handleHttpRequest(req, res, sessions, options?.path ?? DEFAULT_MCP_PATH, options?.logger ?? console);
|
|
3723
3860
|
}
|
|
3724
|
-
async function handleHttpRequest(req, res, sessions, mcpPath) {
|
|
3861
|
+
async function handleHttpRequest(req, res, sessions, mcpPath, logger = console) {
|
|
3725
3862
|
const method = req.method ?? 'GET';
|
|
3726
3863
|
const pathname = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`).pathname;
|
|
3727
3864
|
if (pathname === '/' && method === 'GET') {
|
|
@@ -3753,14 +3890,25 @@ async function handleHttpRequest(req, res, sessions, mcpPath) {
|
|
|
3753
3890
|
return;
|
|
3754
3891
|
}
|
|
3755
3892
|
if (isInitializeRequest(body)) {
|
|
3893
|
+
const clientInfo = body.params && typeof body.params === 'object' && 'clientInfo' in body.params
|
|
3894
|
+
? body.params.clientInfo
|
|
3895
|
+
: undefined;
|
|
3896
|
+
const clientName = clientInfo && typeof clientInfo === 'object' && 'name' in clientInfo
|
|
3897
|
+
? String(clientInfo.name)
|
|
3898
|
+
: 'unknown';
|
|
3899
|
+
const clientVersion = clientInfo && typeof clientInfo === 'object' && 'version' in clientInfo
|
|
3900
|
+
? String(clientInfo.version)
|
|
3901
|
+
: undefined;
|
|
3756
3902
|
let runtime;
|
|
3757
3903
|
const transport = new StreamableHTTPServerTransport({
|
|
3758
3904
|
sessionIdGenerator: () => randomUUID(),
|
|
3759
3905
|
onsessioninitialized: (sid) => {
|
|
3760
3906
|
sessions.set(sid, runtime);
|
|
3907
|
+
logger.log(`[mcplab-mcp] client connected: ${clientName}${clientVersion ? `/${clientVersion}` : ''} (session ${sid})`);
|
|
3761
3908
|
},
|
|
3762
3909
|
onsessionclosed: (sid) => {
|
|
3763
3910
|
sessions.delete(sid);
|
|
3911
|
+
logger.log(`[mcplab-mcp] client disconnected: session ${sid}`);
|
|
3764
3912
|
}
|
|
3765
3913
|
});
|
|
3766
3914
|
const mcpServer = createConfiguredServer();
|