Skip to content

Commit 4b1b489

Browse files
committed
new runner
1 parent 28e4547 commit 4b1b489

File tree

5 files changed

+750
-2
lines changed

5 files changed

+750
-2
lines changed

auth-compat/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"build": "tsc",
1212
"dev": "tsx src/cli/index.ts",
1313
"test-validation-server": "tsx src/test-validation-server.ts",
14-
"cli": "tsx src/cli/index.ts"
14+
"cli": "tsx src/cli/index.ts",
15+
"test": "tsx src/cli/test-runner.ts"
1516
},
1617
"dependencies": {
1718
"@modelcontextprotocol/sdk": "^1.0.0",
@@ -25,4 +26,4 @@
2526
"tsx": "^4.0.0",
2627
"typescript": "^5.0.0"
2728
}
28-
}
29+
}

auth-compat/src/cli/test-runner.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env node
2+
3+
import { Command } from 'commander';
4+
import { ComplianceTestRunner, TestSuite } from '../test-framework/index.js';
5+
6+
const program = new Command();
7+
8+
program
9+
.name('mcp-auth-test')
10+
.description('MCP Authorization Compliance Test Runner')
11+
.version('1.0.0');
12+
13+
program
14+
.requiredOption('--command <command>', 'Command to run the client (should accept server URL as argument)')
15+
.option('--suite <name>', 'Run specific test suite (basic, oauth, metadata, behavior, all)', 'all')
16+
.option('--timeout <ms>', 'Timeout for client execution in milliseconds', '30000')
17+
.option('--json', 'Output results as JSON', false)
18+
.option('--verbose', 'Verbose output', false)
19+
.action(async (clientCommand: string, options) => {
20+
await runTests(clientCommand, options);
21+
});
22+
23+
// Test suite definitions
24+
const basicSuite: TestSuite = {
25+
name: 'Basic Compliance',
26+
description: 'Tests basic MCP protocol compliance without authentication',
27+
scenarios: [
28+
{
29+
name: 'Basic MCP Connection',
30+
description: 'Client can connect and list tools without auth',
31+
serverConfig: {
32+
authRequired: false
33+
},
34+
expectedResult: 'PASS'
35+
}
36+
]
37+
};
38+
39+
const oauthSuite: TestSuite = {
40+
name: 'OAuth Compliance',
41+
description: 'Tests OAuth2/OIDC authorization flow',
42+
scenarios: [
43+
{
44+
name: 'Standard OAuth Flow',
45+
description: 'Client completes OAuth flow with default settings',
46+
serverConfig: {
47+
authRequired: true
48+
},
49+
expectedResult: 'PASS'
50+
}
51+
]
52+
};
53+
54+
const metadataLocationSuite: TestSuite = {
55+
name: 'Metadata Location Tests',
56+
description: 'Tests different OAuth protected resource metadata locations',
57+
scenarios: [
58+
{
59+
name: 'Standard location with WWW-Authenticate',
60+
serverConfig: {
61+
authRequired: true,
62+
metadataLocation: '/.well-known/oauth-protected-resource',
63+
includeWwwAuthenticate: true
64+
},
65+
expectedResult: 'PASS'
66+
},
67+
{
68+
name: 'Non-standard location with WWW-Authenticate',
69+
description: 'Custom metadata path advertised via WWW-Authenticate header',
70+
serverConfig: {
71+
authRequired: true,
72+
metadataLocation: '/custom/oauth/metadata',
73+
includeWwwAuthenticate: true
74+
},
75+
expectedResult: 'PASS'
76+
},
77+
{
78+
name: 'Nested well-known path with WWW-Authenticate',
79+
serverConfig: {
80+
authRequired: true,
81+
metadataLocation: '/.well-known/oauth-protected-resource/mcp',
82+
includeWwwAuthenticate: true
83+
},
84+
expectedResult: 'PASS'
85+
},
86+
{
87+
name: 'Standard location without WWW-Authenticate',
88+
description: 'Client should find metadata at standard location',
89+
serverConfig: {
90+
authRequired: true,
91+
metadataLocation: '/.well-known/oauth-protected-resource',
92+
includeWwwAuthenticate: false
93+
},
94+
expectedResult: 'PASS'
95+
},
96+
{
97+
name: 'Non-standard location without WWW-Authenticate',
98+
description: 'Client cannot find metadata without header hint',
99+
serverConfig: {
100+
authRequired: true,
101+
metadataLocation: '/custom/oauth/metadata',
102+
includeWwwAuthenticate: false
103+
},
104+
expectedResult: 'FAIL' // Should fail - client won't find non-standard location
105+
}
106+
]
107+
};
108+
109+
const behaviorSuite: TestSuite = {
110+
name: 'Client Behavior Validation',
111+
description: 'Tests specific client behaviors',
112+
scenarios: [
113+
{
114+
name: 'Client requests metadata',
115+
serverConfig: {
116+
authRequired: true
117+
},
118+
expectedResult: 'PASS',
119+
validateBehavior: (behavior) => {
120+
const errors = [];
121+
if (!behavior.authMetadataRequested) {
122+
errors.push('Client did not request OAuth metadata');
123+
}
124+
if (!behavior.initialized) {
125+
errors.push('Client did not complete initialization');
126+
}
127+
return errors;
128+
}
129+
}
130+
]
131+
};
132+
133+
async function runTests(options: any) {
134+
const verbose = options.verbose;
135+
const runner = new ComplianceTestRunner(options.command, { verbose, json: options.json });
136+
137+
console.log('Running MCP compliance tests...');
138+
139+
// Select which suites to run
140+
let suitesToRun: TestSuite[] = [];
141+
142+
const suiteMap: Record<string, TestSuite> = {
143+
'basic': basicSuite,
144+
'oauth': oauthSuite,
145+
'metadata': metadataLocationSuite,
146+
'behavior': behaviorSuite
147+
};
148+
149+
if (options.suite === 'all') {
150+
suitesToRun = [basicSuite, oauthSuite, metadataLocationSuite, behaviorSuite];
151+
} else if (suiteMap[options.suite]) {
152+
suitesToRun = [suiteMap[options.suite]];
153+
} else {
154+
console.error(`Unknown suite: ${options.suite}`);
155+
console.error(`Available suites: ${Object.keys(suiteMap).join(', ')}, all`);
156+
process.exit(1);
157+
}
158+
159+
await runner.runSuites(suitesToRun);
160+
}
161+
162+
program.parse();

0 commit comments

Comments
 (0)