@inspectr/mcplab-mcp-server 1.8.3 → 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 +21 -0
- 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 +133 -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.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);
|
|
@@ -1081,7 +1094,7 @@ export function registerTools(server) {
|
|
|
1081
1094
|
outputSchema: LibraryEntrySchema,
|
|
1082
1095
|
inputSchema: {
|
|
1083
1096
|
kind: z
|
|
1084
|
-
.enum(['all', 'servers', 'agents', 'test_cases', 'scenarios'])
|
|
1097
|
+
.enum(['all', 'servers', 'agents', 'browser_providers', 'test_cases', 'scenarios'])
|
|
1085
1098
|
.optional()
|
|
1086
1099
|
.describe('Which library category to list. Defaults to all.'),
|
|
1087
1100
|
includeContent: z
|
|
@@ -1091,15 +1104,16 @@ export function registerTools(server) {
|
|
|
1091
1104
|
}
|
|
1092
1105
|
}, async ({ kind, includeContent }) => {
|
|
1093
1106
|
return withToolHandling(async () => {
|
|
1094
|
-
const root = resolveBundleRoot();
|
|
1095
|
-
const data = readLibrary(root, Boolean(includeContent));
|
|
1096
1107
|
const selectedKind = kind ?? 'all';
|
|
1108
|
+
const root = resolveBundleRoot();
|
|
1109
|
+
const data = readLibrary(root, Boolean(includeContent), selectedKind);
|
|
1097
1110
|
const structured = selectedKind === 'all'
|
|
1098
1111
|
? data
|
|
1099
1112
|
: {
|
|
1100
1113
|
bundleRoot: data.bundleRoot,
|
|
1101
1114
|
servers: selectedKind === 'servers' ? data.servers : [],
|
|
1102
1115
|
agents: selectedKind === 'agents' ? data.agents : [],
|
|
1116
|
+
browser_providers: selectedKind === 'browser_providers' ? data.browser_providers : [],
|
|
1103
1117
|
test_cases: selectedKind === 'test_cases' ? data.test_cases : [],
|
|
1104
1118
|
scenarios: selectedKind === 'scenarios' ? data.scenarios : []
|
|
1105
1119
|
};
|
|
@@ -1107,10 +1121,10 @@ export function registerTools(server) {
|
|
|
1107
1121
|
});
|
|
1108
1122
|
});
|
|
1109
1123
|
registerTool('mcplab_get_library_item', {
|
|
1110
|
-
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.',
|
|
1111
1125
|
outputSchema: {
|
|
1112
1126
|
bundleRoot: z.string(),
|
|
1113
|
-
kind: z.enum(['servers', 'agents', 'test_cases', 'scenarios']),
|
|
1127
|
+
kind: z.enum(['servers', 'agents', 'browser_providers', 'test_cases', 'scenarios']),
|
|
1114
1128
|
id: z.string(),
|
|
1115
1129
|
file: z.string().optional(),
|
|
1116
1130
|
yaml: z.string(),
|
|
@@ -1118,7 +1132,7 @@ export function registerTools(server) {
|
|
|
1118
1132
|
},
|
|
1119
1133
|
inputSchema: {
|
|
1120
1134
|
kind: z
|
|
1121
|
-
.enum(['servers', 'agents', 'test_cases', 'scenarios'])
|
|
1135
|
+
.enum(['servers', 'agents', 'browser_providers', 'test_cases', 'scenarios'])
|
|
1122
1136
|
.describe('Library category.'),
|
|
1123
1137
|
id: z.string().describe('Entry id (for scenarios this is scenario.id, not filename).')
|
|
1124
1138
|
}
|
|
@@ -1175,6 +1189,46 @@ export function registerTools(server) {
|
|
|
1175
1189
|
});
|
|
1176
1190
|
});
|
|
1177
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
|
+
}));
|
|
1178
1232
|
registerTool('mcplab_create_test_case', {
|
|
1179
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.',
|
|
1180
1234
|
outputSchema: { id: z.string(), path: z.string(), test_case: ScenarioEntrySchema },
|
|
@@ -1556,7 +1610,7 @@ export function registerTools(server) {
|
|
|
1556
1610
|
scenarios: z.array(z.object({
|
|
1557
1611
|
scenario_id: z.string(),
|
|
1558
1612
|
agent: z.string(),
|
|
1559
|
-
pass_rate: z.number(),
|
|
1613
|
+
pass_rate: z.number().nullable(),
|
|
1560
1614
|
tool_usage_frequency: z.record(z.number())
|
|
1561
1615
|
})),
|
|
1562
1616
|
effective_server_overrides: z.record(z.array(z.string())),
|
|
@@ -1663,7 +1717,7 @@ export function registerTools(server) {
|
|
|
1663
1717
|
throw new Error('since must be earlier than or equal to until');
|
|
1664
1718
|
const matching = listRunsWithFallback(resolveRunsDir(), undefined, true).filter((entry) => {
|
|
1665
1719
|
const timestamp = String(entry.metadata && typeof entry.metadata === 'object'
|
|
1666
|
-
? entry.metadata.timestamp ?? ''
|
|
1720
|
+
? (entry.metadata.timestamp ?? '')
|
|
1667
1721
|
: '');
|
|
1668
1722
|
const time = new Date(timestamp).getTime();
|
|
1669
1723
|
if (sinceMs === undefined && untilMs === undefined)
|
|
@@ -1857,7 +1911,7 @@ export function registerTools(server) {
|
|
|
1857
1911
|
left: MetricSummarySchema,
|
|
1858
1912
|
right: MetricSummarySchema,
|
|
1859
1913
|
deltas: z.object({
|
|
1860
|
-
pass_rate: z.number(),
|
|
1914
|
+
pass_rate: z.number().nullable(),
|
|
1861
1915
|
failed_runs: z.number(),
|
|
1862
1916
|
avg_tool_calls_per_run: z.number(),
|
|
1863
1917
|
avg_tool_latency_ms: z.number().nullable()
|
|
@@ -1867,7 +1921,8 @@ export function registerTools(server) {
|
|
|
1867
1921
|
improved: z.number().int().nonnegative(),
|
|
1868
1922
|
unchanged: z.number().int().nonnegative(),
|
|
1869
1923
|
new: z.number().int().nonnegative(),
|
|
1870
|
-
missing: z.number().int().nonnegative()
|
|
1924
|
+
missing: z.number().int().nonnegative(),
|
|
1925
|
+
not_evaluated: z.number().int().nonnegative()
|
|
1871
1926
|
})
|
|
1872
1927
|
}),
|
|
1873
1928
|
regressions: z.array(CompareRowSchema),
|
|
@@ -2389,7 +2444,7 @@ export function registerTools(server) {
|
|
|
2389
2444
|
.array(z.object({
|
|
2390
2445
|
scenario_id: z.string(),
|
|
2391
2446
|
agent: z.string(),
|
|
2392
|
-
pass_rate: z.number()
|
|
2447
|
+
pass_rate: z.number().nullable()
|
|
2393
2448
|
}))
|
|
2394
2449
|
.optional()
|
|
2395
2450
|
},
|
|
@@ -2805,15 +2860,18 @@ function mergeLibraryServersAndAgents(config, bundleRoot) {
|
|
|
2805
2860
|
servers: { ...libraries.servers, ...config.servers }
|
|
2806
2861
|
};
|
|
2807
2862
|
}
|
|
2808
|
-
function readLibrary(bundleRoot, includeContent) {
|
|
2863
|
+
function readLibrary(bundleRoot, includeContent, kind = 'all') {
|
|
2809
2864
|
const serversPath = join(bundleRoot, 'servers.yaml');
|
|
2810
2865
|
const agentsPath = join(bundleRoot, 'agents.yaml');
|
|
2811
2866
|
const scenariosDir = resolveScenarioLibraryDir(bundleRoot).path;
|
|
2812
2867
|
const servers = existsSync(serversPath)
|
|
2813
|
-
? parseYaml(readFileSync(serversPath, 'utf8')) ?? {}
|
|
2868
|
+
? (parseYaml(readFileSync(serversPath, 'utf8')) ?? {})
|
|
2814
2869
|
: {};
|
|
2815
2870
|
const agents = existsSync(agentsPath)
|
|
2816
|
-
? parseYaml(readFileSync(agentsPath, 'utf8')) ?? {}
|
|
2871
|
+
? (parseYaml(readFileSync(agentsPath, 'utf8')) ?? {})
|
|
2872
|
+
: {};
|
|
2873
|
+
const browserProviders = kind === 'all' || kind === 'browser_providers'
|
|
2874
|
+
? mergeBrowserProviderProfiles(readBrowserProviderProfiles(bundleRoot))
|
|
2817
2875
|
: {};
|
|
2818
2876
|
const scenarioEntries = [];
|
|
2819
2877
|
if (existsSync(scenariosDir)) {
|
|
@@ -2845,29 +2903,57 @@ function readLibrary(bundleRoot, includeContent) {
|
|
|
2845
2903
|
id,
|
|
2846
2904
|
...(includeContent ? { entry: agents[id] } : {})
|
|
2847
2905
|
})),
|
|
2906
|
+
browser_providers: Object.keys(browserProviders)
|
|
2907
|
+
.sort()
|
|
2908
|
+
.map((id) => ({
|
|
2909
|
+
id,
|
|
2910
|
+
...(includeContent
|
|
2911
|
+
? { entry: browserProviders[id] }
|
|
2912
|
+
: {})
|
|
2913
|
+
})),
|
|
2848
2914
|
test_cases: scenarioEntries,
|
|
2849
2915
|
scenarios: scenarioEntries
|
|
2850
2916
|
};
|
|
2851
2917
|
return out;
|
|
2852
2918
|
}
|
|
2853
2919
|
function getLibraryItem(bundleRoot, kind, id) {
|
|
2854
|
-
if (kind === 'servers' || kind === 'agents') {
|
|
2855
|
-
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
|
+
}
|
|
2856
2938
|
if (!existsSync(file)) {
|
|
2857
2939
|
throw new Error(`Library file not found: ${file}`);
|
|
2858
2940
|
}
|
|
2859
2941
|
const raw = readFileSync(file, 'utf8');
|
|
2860
2942
|
const parsed = parseYaml(raw) ?? {};
|
|
2861
|
-
if (!(id in parsed)) {
|
|
2943
|
+
if (kind !== 'browser_providers' && !(id in parsed)) {
|
|
2862
2944
|
throw new Error(`'${id}' not found in ${file}`);
|
|
2863
2945
|
}
|
|
2864
|
-
const
|
|
2946
|
+
const normalizedEntry = kind === 'browser_providers' ? readBrowserProviderProfiles(bundleRoot)[id] : parsed[id];
|
|
2947
|
+
if (!normalizedEntry)
|
|
2948
|
+
throw new Error(`'${id}' not found in ${file}`);
|
|
2865
2949
|
return {
|
|
2866
2950
|
bundleRoot,
|
|
2867
2951
|
kind,
|
|
2868
2952
|
id,
|
|
2869
|
-
yaml:
|
|
2870
|
-
|
|
2953
|
+
yaml: kind === 'browser_providers'
|
|
2954
|
+
? raw.trimEnd()
|
|
2955
|
+
: stringifyYaml({ [id]: normalizedEntry }).trimEnd(),
|
|
2956
|
+
content: normalizedEntry
|
|
2871
2957
|
};
|
|
2872
2958
|
}
|
|
2873
2959
|
const dir = resolveScenarioLibraryDir(bundleRoot).path;
|
|
@@ -2892,6 +2978,14 @@ function getLibraryItem(bundleRoot, kind, id) {
|
|
|
2892
2978
|
}
|
|
2893
2979
|
throw new Error(`Scenario '${id}' not found in ${dir}`);
|
|
2894
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
|
+
}
|
|
2895
2989
|
function listEvaluationConfigs(params) {
|
|
2896
2990
|
const evalsDir = join(resolveBundleRoot(), 'evals');
|
|
2897
2991
|
const files = [];
|
|
@@ -3762,9 +3856,9 @@ function truncate(text, maxChars) {
|
|
|
3762
3856
|
return `${text.slice(0, maxChars)}\n...[truncated ${text.length - maxChars} chars]`;
|
|
3763
3857
|
}
|
|
3764
3858
|
export async function handleMcplabMcpHttpRequest(req, res, sessions, options) {
|
|
3765
|
-
await handleHttpRequest(req, res, sessions, options?.path ?? DEFAULT_MCP_PATH);
|
|
3859
|
+
await handleHttpRequest(req, res, sessions, options?.path ?? DEFAULT_MCP_PATH, options?.logger ?? console);
|
|
3766
3860
|
}
|
|
3767
|
-
async function handleHttpRequest(req, res, sessions, mcpPath) {
|
|
3861
|
+
async function handleHttpRequest(req, res, sessions, mcpPath, logger = console) {
|
|
3768
3862
|
const method = req.method ?? 'GET';
|
|
3769
3863
|
const pathname = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`).pathname;
|
|
3770
3864
|
if (pathname === '/' && method === 'GET') {
|
|
@@ -3796,14 +3890,25 @@ async function handleHttpRequest(req, res, sessions, mcpPath) {
|
|
|
3796
3890
|
return;
|
|
3797
3891
|
}
|
|
3798
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;
|
|
3799
3902
|
let runtime;
|
|
3800
3903
|
const transport = new StreamableHTTPServerTransport({
|
|
3801
3904
|
sessionIdGenerator: () => randomUUID(),
|
|
3802
3905
|
onsessioninitialized: (sid) => {
|
|
3803
3906
|
sessions.set(sid, runtime);
|
|
3907
|
+
logger.log(`[mcplab-mcp] client connected: ${clientName}${clientVersion ? `/${clientVersion}` : ''} (session ${sid})`);
|
|
3804
3908
|
},
|
|
3805
3909
|
onsessionclosed: (sid) => {
|
|
3806
3910
|
sessions.delete(sid);
|
|
3911
|
+
logger.log(`[mcplab-mcp] client disconnected: session ${sid}`);
|
|
3807
3912
|
}
|
|
3808
3913
|
});
|
|
3809
3914
|
const mcpServer = createConfiguredServer();
|