Skip to content

Commit de16bea

Browse files
committed
Merge remote-tracking branch 'origin/main' into pcarleton/move-metadata-around
2 parents e87b345 + f14895c commit de16bea

File tree

5 files changed

+128
-14
lines changed

5 files changed

+128
-14
lines changed

.github/workflows/pr-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424
- name: Build
2525
run: npm run build
2626
- name: Publish
27-
run: npx pkg-pr-new publish
27+
run: npx pkg-pr-new publish --bin

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"cors": "^2.8.5",
3737
"eslint": "^9.8.0",
3838
"eslint-config-prettier": "^10.1.8",
39+
"lefthook": "^2.0.2",
3940
"prettier": "3.6.2",
4041
"tsdown": "^0.15.12",
4142
"tsx": "^4.7.0",
@@ -47,7 +48,6 @@
4748
"@modelcontextprotocol/sdk": "^1.22.0",
4849
"commander": "^14.0.2",
4950
"express": "^5.1.0",
50-
"lefthook": "^2.0.2",
5151
"zod": "^3.25.76"
5252
}
5353
}

src/index.ts

Lines changed: 119 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
import {
1414
listScenarios,
1515
listClientScenarios,
16-
listActiveClientScenarios
16+
listActiveClientScenarios,
17+
listAuthScenarios
1718
} from './scenarios';
1819
import { ConformanceCheck } from './types';
1920
import { ClientOptionsSchema, ServerOptionsSchema } from './schemas';
@@ -33,34 +34,140 @@ program
3334
'Run conformance tests against a client implementation or start interactive mode'
3435
)
3536
.option('--command <command>', 'Command to run the client')
36-
.requiredOption('--scenario <scenario>', 'Scenario to test')
37+
.option('--scenario <scenario>', 'Scenario to test')
38+
.option('--suite <suite>', 'Run a suite of tests in parallel (e.g., "auth")')
3739
.option('--timeout <ms>', 'Timeout in milliseconds', '30000')
3840
.option('--verbose', 'Show verbose output')
3941
.action(async (options) => {
4042
try {
41-
// Validate options with Zod
43+
const timeout = parseInt(options.timeout, 10);
44+
const verbose = options.verbose ?? false;
45+
46+
// Handle suite mode
47+
if (options.suite) {
48+
if (!options.command) {
49+
console.error('--command is required when using --suite');
50+
process.exit(1);
51+
}
52+
53+
const suites: Record<string, () => string[]> = {
54+
auth: listAuthScenarios
55+
};
56+
57+
const suiteName = options.suite.toLowerCase();
58+
if (!suites[suiteName]) {
59+
console.error(`Unknown suite: ${suiteName}`);
60+
console.error(`Available suites: ${Object.keys(suites).join(', ')}`);
61+
process.exit(1);
62+
}
63+
64+
const scenarios = suites[suiteName]();
65+
console.log(
66+
`Running ${suiteName} suite (${scenarios.length} scenarios) in parallel...\n`
67+
);
68+
69+
const results = await Promise.all(
70+
scenarios.map(async (scenarioName) => {
71+
try {
72+
const result = await runConformanceTest(
73+
options.command,
74+
scenarioName,
75+
timeout
76+
);
77+
return {
78+
scenario: scenarioName,
79+
checks: result.checks,
80+
error: null
81+
};
82+
} catch (error) {
83+
return {
84+
scenario: scenarioName,
85+
checks: [
86+
{
87+
id: scenarioName,
88+
name: scenarioName,
89+
description: 'Failed to run scenario',
90+
status: 'FAILURE' as const,
91+
timestamp: new Date().toISOString(),
92+
errorMessage:
93+
error instanceof Error ? error.message : String(error)
94+
}
95+
],
96+
error
97+
};
98+
}
99+
})
100+
);
101+
102+
console.log('\n=== SUITE SUMMARY ===\n');
103+
104+
let totalPassed = 0;
105+
let totalFailed = 0;
106+
let totalWarnings = 0;
107+
108+
for (const result of results) {
109+
const passed = result.checks.filter(
110+
(c) => c.status === 'SUCCESS'
111+
).length;
112+
const failed = result.checks.filter(
113+
(c) => c.status === 'FAILURE'
114+
).length;
115+
const warnings = result.checks.filter(
116+
(c) => c.status === 'WARNING'
117+
).length;
118+
119+
totalPassed += passed;
120+
totalFailed += failed;
121+
totalWarnings += warnings;
122+
123+
const status = failed === 0 ? '✓' : '✗';
124+
console.log(
125+
`${status} ${result.scenario}: ${passed} passed, ${failed} failed`
126+
);
127+
128+
if (verbose && failed > 0) {
129+
result.checks
130+
.filter((c) => c.status === 'FAILURE')
131+
.forEach((c) => {
132+
console.log(
133+
` - ${c.name}: ${c.errorMessage || c.description}`
134+
);
135+
});
136+
}
137+
}
138+
139+
console.log(
140+
`\nTotal: ${totalPassed} passed, ${totalFailed} failed, ${totalWarnings} warnings`
141+
);
142+
process.exit(totalFailed > 0 ? 1 : 0);
143+
}
144+
145+
// Require either --scenario or --suite
146+
if (!options.scenario) {
147+
console.error('Either --scenario or --suite is required');
148+
console.error('\nAvailable client scenarios:');
149+
listScenarios().forEach((s) => console.error(` - ${s}`));
150+
console.error('\nAvailable suites: auth');
151+
process.exit(1);
152+
}
153+
154+
// Validate options with Zod for single scenario mode
42155
const validated = ClientOptionsSchema.parse(options);
43156

44157
// If no command provided, run in interactive mode
45158
if (!validated.command) {
46-
await runInteractiveMode(
47-
validated.scenario,
48-
validated.verbose ?? false
49-
);
159+
await runInteractiveMode(validated.scenario, verbose);
50160
process.exit(0);
51161
}
52162

53163
// Otherwise run conformance test
54164
const result = await runConformanceTest(
55165
validated.command,
56166
validated.scenario,
57-
validated.timeout ?? 30000
167+
timeout
58168
);
59169

60-
const { failed } = printClientResults(
61-
result.checks,
62-
validated.verbose ?? false
63-
);
170+
const { failed } = printClientResults(result.checks, verbose);
64171
process.exit(failed > 0 ? 1 : 0);
65172
} catch (error) {
66173
if (error instanceof ZodError) {

src/scenarios/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,7 @@ export function listClientScenarios(): string[] {
147147
export function listActiveClientScenarios(): string[] {
148148
return activeClientScenariosList.map((scenario) => scenario.name);
149149
}
150+
151+
export function listAuthScenarios(): string[] {
152+
return authScenariosList.map((scenario) => scenario.name);
153+
}

src/scenarios/server/tools.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ Implement tool \`test_tool_with_logging\` with no arguments.
388388
const connection = await connectToServer(serverUrl);
389389
const notifications = new NotificationCollector(connection.client);
390390

391+
// Set logging level to debug
392+
await connection.client.setLoggingLevel('debug');
393+
391394
await connection.client.callTool({
392395
name: 'test_tool_with_logging',
393396
arguments: {}

0 commit comments

Comments
 (0)