Add client credentials conformance tests (JWT and Basic auth) (#55)
* Add client credentials conformance scenarios with dynamic keypair generation Implements SEP-1046 client credentials conformance tests: - auth/client-credentials-jwt: Tests private_key_jwt authentication - auth/client-credentials-basic: Tests client_secret_basic authentication Key changes: - Generate EC P-256 keypair dynamically at test start (no hardcoded keys) - Pass credentials to client via MCP_CONFORMANCE_CONTEXT environment variable - Add context field to ScenarioUrls interface for scenario-specific data - Update client runner to pass context as env var to spawned client process The MCP_CONFORMANCE_CONTEXT env var contains a JSON object with: - client_id: The expected client identifier - private_key_pem: PEM-encoded private key (for JWT scenarios) - client_secret: Client secret (for basic auth scenarios) - signing_algorithm: JWT signing algorithm (defaults to ES256) * Fix client-credentials scenarios for conformance testing - Generate EC keypair with extractable: true so private key can be exported and passed to clients via MCP_CONFORMANCE_CONTEXT - Fix client_secret_basic scenario to use authorizationHeader param instead of non-existent headers object * Fix jose type import - use CryptoKey instead of KeyLike * Add OAuth 2.1 section 4.2 spec reference to client credentials checks Add OAUTH_2_1_CLIENT_CREDENTIALS spec reference pointing to OAuth 2.1 draft section 4.2 (Client Credentials Grant) and include it in all client_credentials conformance checks for both JWT and basic auth flows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add JWT iss claim verification for client credentials Per RFC 7523, verify that the JWT issuer (iss) claim matches the expected client_id, in addition to the existing sub claim check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f0e948187d
commit
b96a29a962
1 changed files with 517 additions and 23 deletions
|
|
@ -15,7 +15,8 @@ export interface ClientExecutionResult {
|
|||
async function executeClient(
|
||||
command: string,
|
||||
serverUrl: string,
|
||||
timeout: number = 30000
|
||||
timeout: number = 30000,
|
||||
context?: Record<string, unknown>
|
||||
): Promise<ClientExecutionResult> {
|
||||
const commandParts = command.split(' ');
|
||||
const executable = commandParts[0];
|
||||
|
|
@ -25,30 +26,37 @@ async function executeClient(
|
|||
let stderr = '';
|
||||
let timedOut = false;
|
||||
|
||||
// Build environment with optional context
|
||||
const env = { ...process.env };
|
||||
if (context) {
|
||||
env.MCP_CONFORMANCE_CONTEXT = JSON.stringify(context);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const process = spawn(executable, args, {
|
||||
const childProcess = spawn(executable, args, {
|
||||
shell: true,
|
||||
stdio: 'pipe'
|
||||
stdio: 'pipe',
|
||||
env
|
||||
});
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
timedOut = true;
|
||||
process.kill();
|
||||
childProcess.kill();
|
||||
}, timeout);
|
||||
|
||||
if (process.stdout) {
|
||||
process.stdout.on('data', (data) => {
|
||||
if (childProcess.stdout) {
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
}
|
||||
|
||||
if (process.stderr) {
|
||||
process.stderr.on('data', (data) => {
|
||||
if (childProcess.stderr) {
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
}
|
||||
|
||||
process.on('close', (code) => {
|
||||
childProcess.on('close', (code) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve({
|
||||
exitCode: code || 0,
|
||||
|
|
@ -58,7 +66,7 @@ async function executeClient(
|
|||
});
|
||||
});
|
||||
|
||||
process.on('error', (error) => {
|
||||
childProcess.on('error', (error) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve({
|
||||
exitCode: -1,
|
||||
|
|
@ -90,12 +98,16 @@ export async function runConformanceTest(
|
|||
const urls = await scenario.start();
|
||||
|
||||
console.error(`Executing client: ${clientCommand} ${urls.serverUrl}`);
|
||||
if (urls.context) {
|
||||
console.error(`With context: ${JSON.stringify(urls.context)}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const clientOutput = await executeClient(
|
||||
clientCommand,
|
||||
urls.serverUrl,
|
||||
timeout
|
||||
timeout,
|
||||
urls.context
|
||||
);
|
||||
|
||||
// Print stdout/stderr if client exited with nonzero code
|
||||
|
|
|
|||
Loading…
Reference in a new issue