Skip to content

Commit 2c1cea2

Browse files
committed
readme
1 parent fec2c90 commit 2c1cea2

File tree

5 files changed

+139
-40
lines changed

5 files changed

+139
-40
lines changed

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# MCP Conformance Test Framework
2+
3+
A framework for testing MCP (Model Context Protocol) client implementations against the specification.
4+
5+
## Quick Start
6+
7+
```bash
8+
npm install
9+
npm run start -- --command "tsx examples/clients/typescript/test1.ts" --scenario initialize
10+
```
11+
12+
## Overview
13+
14+
The conformance test framework validates MCP client implementations by:
15+
1. Starting a test server for the specified scenario
16+
2. Running the client implementation with the test server URL
17+
3. Capturing MCP protocol interactions
18+
4. Running conformance checks against the specification
19+
5. Generating detailed test results
20+
21+
## Usage
22+
23+
```bash
24+
npm run start -- --command "<client-command>" --scenario <scenario-name>
25+
```
26+
27+
- `--command` - The command to run your MCP client (can include flags)
28+
- `--scenario` - The test scenario to run (e.g., "initialize")
29+
30+
The framework appends the server URL as the final argument to your command.
31+
32+
## Test Results
33+
34+
Results are saved to `results/<scenario>-<timestamp>/`:
35+
- `checks.json` - Array of conformance check results with pass/fail status
36+
- `stdout.txt` - Client stdout output
37+
- `stderr.txt` - Client stderr output
38+
39+
## Example Clients
40+
41+
- `examples/clients/typescript/test1.ts` - Valid MCP client (passes all checks)
42+
- `examples/clients/typescript/test-broken.ts` - Invalid client missing required fields (fails checks)
43+
44+
## Available Scenarios
45+
46+
- **initialize** - Tests MCP client initialization handshake
47+
- Validates protocol version
48+
- Validates clientInfo (name and version)
49+
- Validates server response handling
50+
51+
## Architecture
52+
53+
See `src/runner/DESIGN.md` for detailed architecture documentation.
54+
55+
### Key Components
56+
57+
- **Runner** (`src/runner/`) - Orchestrates test execution and result generation
58+
- **Scenarios** (`src/scenarios/`) - Test scenarios with custom server implementations
59+
- **Checks** (`src/checks.ts`) - Conformance validation functions
60+
- **Types** (`src/types.ts`) - Shared type definitions
61+
62+
## Adding New Scenarios
63+
64+
1. Create a new directory in `src/scenarios/<scenario-name>/`
65+
2. Implement the `Scenario` interface with `start()`, `stop()`, and `getChecks()`
66+
3. Register the scenario in `src/scenarios/index.ts`
67+
68+
See `src/scenarios/initialize/` for a reference implementation.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env node
2+
3+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4+
5+
async function main(): Promise<void> {
6+
const serverUrl = process.argv[2];
7+
8+
if (!serverUrl) {
9+
console.error('Usage: test-broken-client <server-url>');
10+
process.exit(1);
11+
}
12+
13+
console.log(`Connecting to MCP server at: ${serverUrl}`);
14+
15+
try {
16+
const transport = new StreamableHTTPClientTransport(
17+
new URL(serverUrl)
18+
);
19+
20+
await transport.start();
21+
22+
const initRequest = {
23+
jsonrpc: '2.0',
24+
id: 1,
25+
method: 'initialize',
26+
params: {
27+
protocolVersion: '2025-06-18',
28+
clientInfo: {
29+
version: '1.0.0'
30+
},
31+
capabilities: {}
32+
}
33+
};
34+
35+
const response = await fetch(serverUrl, {
36+
method: 'POST',
37+
headers: {
38+
'Content-Type': 'application/json'
39+
},
40+
body: JSON.stringify(initRequest)
41+
});
42+
43+
const result = await response.json();
44+
console.log('✅ Received response:', JSON.stringify(result, null, 2));
45+
46+
await transport.close();
47+
console.log('✅ Connection closed');
48+
49+
process.exit(0);
50+
} catch (error) {
51+
console.error('❌ Error:', error);
52+
process.exit(1);
53+
}
54+
}
55+
56+
main().catch((error) => {
57+
console.error('Unhandled error:', error);
58+
process.exit(1);
59+
});

src/scenarios/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Scenario } from '../types.js';
2-
import { initializeScenario } from './initialize/index.js';
2+
import { InitializeScenario } from './initialize.js';
33

44
export const scenarios = new Map<string, Scenario>([
5-
['initialize', initializeScenario],
5+
['initialize', new InitializeScenario()],
66
]);
77

88
export function registerScenario(name: string, scenario: Scenario): void {
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import http from 'http';
2-
import { ConformanceCheck } from '../../types.js';
3-
import { createClientInitializationCheck, createServerInfoCheck } from '../../checks.js';
2+
import { Scenario, ScenarioUrls, ConformanceCheck } from '../types.js';
3+
import { createClientInitializationCheck, createServerInfoCheck } from '../checks.js';
4+
5+
export class InitializeScenario implements Scenario {
6+
name = 'initialize';
7+
description = 'Tests MCP client initialization handshake';
48

5-
export class InitializeTestServer {
69
private server: http.Server | null = null;
710
private checks: ConformanceCheck[] = [];
811
private port: number = 0;
912

10-
async start(): Promise<number> {
13+
async start(): Promise<ScenarioUrls> {
1114
return new Promise((resolve, reject) => {
1215
this.server = http.createServer((req, res) => {
1316
this.handleRequest(req, res);
@@ -19,7 +22,9 @@ export class InitializeTestServer {
1922
const address = this.server!.address();
2023
if (address && typeof address === 'object') {
2124
this.port = address.port;
22-
resolve(this.port);
25+
resolve({
26+
serverUrl: `http://localhost:${this.port}`
27+
});
2328
} else {
2429
reject(new Error('Failed to get server address'));
2530
}

src/scenarios/initialize/index.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)