Skip to content

Commit f4526ff

Browse files
committed
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)
1 parent f0e9481 commit f4526ff

File tree

10 files changed

+467
-23
lines changed

10 files changed

+467
-23
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"commander": "^14.0.2",
5050
"eventsource-parser": "^3.0.6",
5151
"express": "^5.1.0",
52+
"jose": "^6.1.2",
5253
"zod": "^3.25.76"
5354
}
5455
}

src/runner/client.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export interface ClientExecutionResult {
1515
async function executeClient(
1616
command: string,
1717
serverUrl: string,
18-
timeout: number = 30000
18+
timeout: number = 30000,
19+
context?: Record<string, unknown>
1920
): Promise<ClientExecutionResult> {
2021
const commandParts = command.split(' ');
2122
const executable = commandParts[0];
@@ -25,30 +26,37 @@ async function executeClient(
2526
let stderr = '';
2627
let timedOut = false;
2728

29+
// Build environment with optional context
30+
const env = { ...process.env };
31+
if (context) {
32+
env.MCP_CONFORMANCE_CONTEXT = JSON.stringify(context);
33+
}
34+
2835
return new Promise((resolve) => {
29-
const process = spawn(executable, args, {
36+
const childProcess = spawn(executable, args, {
3037
shell: true,
31-
stdio: 'pipe'
38+
stdio: 'pipe',
39+
env
3240
});
3341

3442
const timeoutHandle = setTimeout(() => {
3543
timedOut = true;
36-
process.kill();
44+
childProcess.kill();
3745
}, timeout);
3846

39-
if (process.stdout) {
40-
process.stdout.on('data', (data) => {
47+
if (childProcess.stdout) {
48+
childProcess.stdout.on('data', (data) => {
4149
stdout += data.toString();
4250
});
4351
}
4452

45-
if (process.stderr) {
46-
process.stderr.on('data', (data) => {
53+
if (childProcess.stderr) {
54+
childProcess.stderr.on('data', (data) => {
4755
stderr += data.toString();
4856
});
4957
}
5058

51-
process.on('close', (code) => {
59+
childProcess.on('close', (code) => {
5260
clearTimeout(timeoutHandle);
5361
resolve({
5462
exitCode: code || 0,
@@ -58,7 +66,7 @@ async function executeClient(
5866
});
5967
});
6068

61-
process.on('error', (error) => {
69+
childProcess.on('error', (error) => {
6270
clearTimeout(timeoutHandle);
6371
resolve({
6472
exitCode: -1,
@@ -90,12 +98,16 @@ export async function runConformanceTest(
9098
const urls = await scenario.start();
9199

92100
console.error(`Executing client: ${clientCommand} ${urls.serverUrl}`);
101+
if (urls.context) {
102+
console.error(`With context: ${JSON.stringify(urls.context)}`);
103+
}
93104

94105
try {
95106
const clientOutput = await executeClient(
96107
clientCommand,
97108
urls.serverUrl,
98-
timeout
109+
timeout,
110+
urls.context
99111
);
100112

101113
// Print stdout/stderr if client exited with nonzero code

0 commit comments

Comments
 (0)