Skip to content

Commit 46017ab

Browse files
committed
basic initialize working
1 parent b858bea commit 46017ab

File tree

4 files changed

+129
-4
lines changed

4 files changed

+129
-4
lines changed

src/checks/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function createServerInitializationCheck(initializeResponse: any, expecte
2828
specReferences: [{ id: 'MCP-Lifecycle', url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' }],
2929
details: {
3030
expectedSpecVersion,
31-
...initializeResponse
31+
response: initializeResponse
3232
},
3333
errorMessage: errors.length > 0 ? errors.join('; ') : undefined,
3434
logs: errors.length > 0 ? errors : undefined

src/scenarios/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const scenarios = new Map<string, Scenario>([
99
]);
1010

1111
export const clientScenarios = new Map<string, ClientScenario>([
12-
['server-initialize', new ServerInitializeClientScenario()]
12+
['initialize', new ServerInitializeClientScenario()]
1313
]);
1414

1515
export function registerScenario(name: string, scenario: Scenario): void {

src/scenarios/server/server_initialize.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class ServerInitializeClientScenario implements ClientScenario {
1313
method: 'POST',
1414
headers: {
1515
'Content-Type': 'application/json',
16+
'Accept': 'application/json, text/event-stream',
1617
},
1718
body: JSON.stringify({
1819
jsonrpc: '2.0',
@@ -30,10 +31,29 @@ export class ServerInitializeClientScenario implements ClientScenario {
3031
});
3132

3233
if (!response.ok) {
33-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
34+
const responseBody = await response.text();
35+
throw new Error(`HTTP ${response.status}: ${response.statusText}. Response body: ${responseBody}`);
3436
}
3537

36-
const result = await response.json();
38+
const responseText = await response.text();
39+
40+
// Handle SSE format
41+
let result;
42+
if (responseText.startsWith('event:') || responseText.includes('\ndata:')) {
43+
// Parse SSE format - extract JSON from data: lines
44+
const lines = responseText.split('\n');
45+
const dataLines = lines.filter(line => line.startsWith('data: '));
46+
if (dataLines.length > 0) {
47+
const jsonData = dataLines[0].substring(6); // Remove 'data: ' prefix
48+
result = JSON.parse(jsonData);
49+
} else {
50+
throw new Error(`SSE response without data line: ${responseText}`);
51+
}
52+
} else {
53+
// Regular JSON response
54+
result = JSON.parse(responseText);
55+
}
56+
3757
const check = serverChecks.createServerInitializationCheck(result);
3858
checks.push(check);
3959
} catch (error) {
@@ -44,6 +64,10 @@ export class ServerInitializeClientScenario implements ClientScenario {
4464
status: 'FAILURE',
4565
timestamp: new Date().toISOString(),
4666
errorMessage: `Failed to send initialize request: ${error instanceof Error ? error.message : String(error)}`,
67+
details: {
68+
error: error instanceof Error ? error.message : String(error),
69+
serverUrl
70+
},
4771
specReferences: [
4872
{
4973
id: 'MCP-Initialize',

src/server-runner.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { promises as fs } from 'fs';
2+
import path from 'path';
3+
import { ConformanceCheck } from './types.js';
4+
import { getClientScenario } from './scenarios/index.js';
5+
6+
async function ensureResultsDir(): Promise<string> {
7+
const resultsDir = path.join(process.cwd(), 'results');
8+
await fs.mkdir(resultsDir, { recursive: true });
9+
return resultsDir;
10+
}
11+
12+
function createResultDir(scenario: string): string {
13+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
14+
return path.join('results', `server-${scenario}-${timestamp}`);
15+
}
16+
17+
export async function runServerConformanceTest(
18+
serverUrl: string,
19+
scenarioName: string
20+
): Promise<{
21+
checks: ConformanceCheck[];
22+
resultDir: string;
23+
}> {
24+
await ensureResultsDir();
25+
const resultDir = createResultDir(scenarioName);
26+
await fs.mkdir(resultDir, { recursive: true });
27+
28+
const scenario = getClientScenario(scenarioName);
29+
if (!scenario) {
30+
throw new Error(`Unknown client scenario: ${scenarioName}`);
31+
}
32+
33+
console.log(`Running client scenario '${scenarioName}' against server: ${serverUrl}`);
34+
35+
const checks = await scenario.run(serverUrl);
36+
37+
await fs.writeFile(path.join(resultDir, 'checks.json'), JSON.stringify(checks, null, 2));
38+
39+
console.log(`Results saved to ${resultDir}`);
40+
41+
return {
42+
checks,
43+
resultDir
44+
};
45+
}
46+
47+
async function main(): Promise<void> {
48+
const args = process.argv.slice(2);
49+
let serverUrl: string | null = null;
50+
let scenario: string | null = null;
51+
52+
for (let i = 0; i < args.length; i++) {
53+
if (args[i] === '--server-url' && i + 1 < args.length) {
54+
serverUrl = args[i + 1];
55+
i++;
56+
} else if (args[i] === '--scenario' && i + 1 < args.length) {
57+
scenario = args[i + 1];
58+
i++;
59+
}
60+
}
61+
62+
if (!serverUrl || !scenario) {
63+
console.error('Usage: server-runner --server-url <url> --scenario <scenario>');
64+
console.error('Example: server-runner --server-url http://localhost:3000 --scenario initialize');
65+
process.exit(1);
66+
}
67+
68+
try {
69+
const result = await runServerConformanceTest(serverUrl, scenario);
70+
71+
const denominator = result.checks.filter(c => c.status === 'SUCCESS' || c.status == 'FAILURE').length;
72+
const passed = result.checks.filter(c => c.status === 'SUCCESS').length;
73+
const failed = result.checks.filter(c => c.status === 'FAILURE').length;
74+
75+
console.log(`Checks:\n${JSON.stringify(result.checks, null, 2)}`);
76+
77+
console.log(`\nTest Results:`);
78+
console.log(`Passed: ${passed}/${denominator}, ${failed} failed`);
79+
80+
if (failed > 0) {
81+
console.log('\nFailed Checks:');
82+
result.checks
83+
.filter(c => c.status === 'FAILURE')
84+
.forEach(c => {
85+
console.log(` - ${c.name}: ${c.description}`);
86+
if (c.errorMessage) {
87+
console.log(` Error: ${c.errorMessage}`);
88+
}
89+
});
90+
}
91+
92+
process.exit(failed > 0 ? 1 : 0);
93+
} catch (error) {
94+
console.error('Server test runner error:', error);
95+
process.exit(1);
96+
}
97+
}
98+
99+
if (import.meta.url === `file://${process.argv[1]}`) {
100+
main();
101+
}

0 commit comments

Comments
 (0)