Skip to content

Commit 05ea16a

Browse files
committed
remove old one
1 parent 4b1b489 commit 05ea16a

File tree

3 files changed

+132
-349
lines changed

3 files changed

+132
-349
lines changed

auth-compat/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
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",
15-
"test": "tsx src/cli/test-runner.ts"
14+
"cli": "tsx src/cli/index.ts"
1615
},
1716
"dependencies": {
1817
"@modelcontextprotocol/sdk": "^1.0.0",

auth-compat/src/cli/index.ts

Lines changed: 131 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,216 +1,162 @@
11
#!/usr/bin/env node
22

33
import { Command } from 'commander';
4-
import { spawn } from 'child_process';
5-
import { ValidationServer } from '../server/validation/index.js';
6-
import { ComplianceReport, HttpTrace } from '../types.js';
7-
import { displayTraces } from '../middleware/http-trace.js';
4+
import { ComplianceTestRunner, TestSuite } from '../test-framework/index.js';
85

96
const program = new Command();
107

118
program
12-
.name('mcp-auth-compat')
13-
.description('MCP Authorization Compliance Checker')
9+
.name('mcp-auth-test')
10+
.description('MCP Authorization Compliance Test Runner')
1411
.version('1.0.0');
1512

1613
program
17-
.argument('<command>', 'Command to run the client (should accept server URL as argument)')
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')
1816
.option('--timeout <ms>', 'Timeout for client execution in milliseconds', '30000')
1917
.option('--json', 'Output results as JSON', false)
2018
.option('--verbose', 'Verbose output', false)
2119
.action(async (clientCommand: string, options) => {
22-
await runComplianceTests(clientCommand, options);
20+
await runTests(clientCommand, options);
2321
});
2422

25-
async function runComplianceTests(clientCommand: string, options: any) {
26-
const verbose = options.verbose;
27-
const timeout = parseInt(options.timeout, 10);
28-
29-
console.log('Running MCP compliance tests...');
30-
31-
const allTestsPassed: boolean[] = [];
32-
33-
// Run basic test (no auth)
34-
console.log('\n[1/2] Basic compliance test');
35-
const basicPassed = await runSingleTest(clientCommand, false, timeout, options);
36-
allTestsPassed.push(basicPassed);
37-
38-
// Run auth test
39-
console.log('\n[2/2] Authorization compliance test');
40-
const authPassed = await runSingleTest(clientCommand, true, timeout, options);
41-
allTestsPassed.push(authPassed);
42-
43-
// Overall summary
44-
const overallPass = allTestsPassed.every(p => p);
45-
console.log('\n' + '='.repeat(40));
46-
if (overallPass) {
47-
console.log('✅ All tests PASSED');
48-
} else {
49-
console.log('❌ Some tests FAILED');
50-
}
51-
console.log('='.repeat(40));
52-
53-
process.exit(overallPass ? 0 : 1);
54-
}
55-
56-
async function runSingleTest(
57-
clientCommand: string,
58-
authRequired: boolean,
59-
timeout: number,
60-
options: any
61-
): Promise<boolean> {
62-
const verbose = options.verbose;
63-
64-
// Start validation server
65-
const server = new ValidationServer({ authRequired }, verbose);
66-
67-
try {
68-
const serverPort = await server.start();
69-
const serverUrl = `http://localhost:${serverPort}/mcp`;
70-
71-
if (verbose) {
72-
console.log(` Server: ${serverUrl}`);
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'
7335
}
74-
75-
// Parse the client command to separate the executable from its arguments
76-
const commandParts = clientCommand.split(' ');
77-
const executable = commandParts[0];
78-
const args = [...commandParts.slice(1), serverUrl];
79-
80-
// Capture client output when not in verbose mode (verbose mode uses 'inherit')
81-
let clientStdout = '';
82-
let clientStderr = '';
83-
84-
// Run the client
85-
const clientProcess = spawn(executable, args, {
86-
stdio: 'pipe',
87-
shell: true,
88-
timeout
89-
});
90-
91-
// Capture stdout/stderr
92-
if (clientProcess.stdout) {
93-
clientProcess.stdout.on('data', (data) => {
94-
clientStdout += data.toString();
95-
});
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'
9650
}
97-
if (clientProcess.stderr) {
98-
clientProcess.stderr.on('data', (data) => {
99-
clientStderr += data.toString();
100-
});
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
101105
}
102-
103-
// Wait for client to finish
104-
const clientExitCode = await new Promise<number>((resolve, reject) => {
105-
let timedOut = false;
106-
107-
const timeoutHandle = setTimeout(() => {
108-
timedOut = true;
109-
clientProcess.kill();
110-
reject(new Error(`Timeout (${timeout}ms)`));
111-
}, timeout);
112-
113-
clientProcess.on('exit', (code) => {
114-
clearTimeout(timeoutHandle);
115-
if (!timedOut) {
116-
resolve(code || 0);
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');
117123
}
118-
});
119-
120-
clientProcess.on('error', (error) => {
121-
clearTimeout(timeoutHandle);
122-
reject(error);
123-
});
124-
});
125-
126-
// Get validation results
127-
const results = server.getValidationResults();
128-
const behavior = server.getClientBehavior();
129-
130-
// Get auth server trace if auth was required
131-
const authServerTrace = authRequired && server.authServer ? server.authServer.getHttpTrace() : [];
132-
133-
// Generate report
134-
const report: ComplianceReport = {
135-
overall_result: results.every(r => r.result === 'PASS') && clientExitCode === 0 ? 'PASS' : 'FAIL',
136-
test_suite: authRequired ? 'authorization-compliance' : 'basic-compliance',
137-
timestamp: new Date().toISOString(),
138-
client_command: clientCommand,
139-
tests_passed: results.filter(r => r.result === 'PASS').length,
140-
tests_failed: results.filter(r => r.result === 'FAIL').length,
141-
tests: results
142-
};
143-
144-
// Output results
145-
if (options.json) {
146-
console.log(JSON.stringify(report, null, 2));
147-
} else {
148-
const clientOutput = { stdout: clientStdout, stderr: clientStderr };
149-
printCompactReport(report, verbose ? behavior : null, verbose ? authServerTrace : undefined, clientOutput);
150-
}
151-
152-
// Stop server
153-
await server.stop();
154-
155-
return report.overall_result === 'PASS';
156-
157-
} catch (error: any) {
158-
console.log(` ❌ FAIL: ${error.message || error}`);
159-
if (server) {
160-
await server.stop();
124+
if (!behavior.initialized) {
125+
errors.push('Client did not complete initialization');
126+
}
127+
return errors;
128+
}
161129
}
162-
return false;
163-
}
164-
}
130+
]
131+
};
165132

166-
function printCompactReport(report: ComplianceReport, behavior?: any, authServerTrace?: HttpTrace[], clientOutput?: { stdout: string, stderr: string }) {
167-
const passed = report.overall_result === 'PASS';
168-
const icon = passed ? '✅' : '❌';
133+
async function runTests(options: any) {
134+
const verbose = options.verbose;
135+
const runner = new ComplianceTestRunner(options.command, { verbose, json: options.json });
169136

170-
console.log(` ${icon} ${report.test_suite}: ${report.overall_result}`);
137+
console.log('Running MCP compliance tests...');
171138

172-
// Only show failures in compact mode
173-
if (!passed) {
174-
report.tests.forEach(test => {
175-
if (test.result === 'FAIL') {
176-
console.log(` ❌ ${test.name}`);
177-
if (test.errors && test.errors.length > 0) {
178-
test.errors.forEach(error => {
179-
console.log(` - ${error}`);
180-
});
181-
}
182-
}
183-
});
184-
}
139+
// Select which suites to run
140+
let suitesToRun: TestSuite[] = [];
185141

186-
// Show HTTP trace and detailed behavior in verbose mode
187-
if (behavior) {
188-
displayTraces(behavior.httpTrace, authServerTrace || [])
142+
const suiteMap: Record<string, TestSuite> = {
143+
'basic': basicSuite,
144+
'oauth': oauthSuite,
145+
'metadata': metadataLocationSuite,
146+
'behavior': behaviorSuite
147+
};
189148

190-
// Show other behavior details
191-
console.log(' Client Behavior Summary:');
192-
const summaryBehavior = { ...behavior };
193-
delete summaryBehavior.httpTrace; // Don't repeat the trace
194-
console.log(' ' + JSON.stringify(summaryBehavior, null, 2).split('\n').join('\n '));
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);
195157
}
196158

197-
// Show client output at the very end
198-
// In verbose mode, output was shown directly via 'inherit', but we still captured it for non-verbose mode
199-
if (clientOutput && (clientOutput.stdout || clientOutput.stderr)) {
200-
if (clientOutput.stdout) {
201-
console.log('\n ====== CLIENT STDOUT ======');
202-
console.log(' ' + clientOutput.stdout.split('\n').join('\n '));
203-
console.log(' ========================\n');
204-
}
205-
206-
if (clientOutput.stderr) {
207-
console.log('\n ====== CLIENT STDERR ======');
208-
console.log(' ' + clientOutput.stderr.split('\n').join('\n '));
209-
console.log(' ========================\n');
210-
}
211-
}
159+
await runner.runSuites(suitesToRun);
212160
}
213161

214-
215-
216162
program.parse();

0 commit comments

Comments
 (0)