mcp-server-tester-sse-http-stdio
Version:
MCP Server Tester with SSE support - Test MCP servers using HTTP, SSE, and STDIO transports
246 lines (245 loc) • 12.8 kB
JavaScript
/**
* Session Management Tests
* Tests session ID generation, header handling, and session lifecycle management
*/
import { DiagnosticTest } from '../DiagnosticTest.js';
import { TEST_SEVERITY } from '../types.js';
class SessionIdGenerationTest extends DiagnosticTest {
name = 'Lifecycle: Session Management - Ping';
description = 'Test session ID generation and format validation';
category = 'lifecycle';
feature = 'ping';
severity = TEST_SEVERITY.INFO;
async execute(client, _config) {
const findings = [];
const validations = [];
try {
// For STDIO transport, session management is typically handled differently
// We'll test consistency across multiple requests to infer session handling
const responses = [];
for (let i = 0; i < 3; i++) {
try {
const response = await client.sdk.listTools();
responses.push(response);
validations.push(`Request ${i + 1}: Successful response received`);
}
catch (error) {
findings.push(`Request ${i + 1}: Failed - ${error instanceof Error ? error.message : String(error)}`);
}
}
// Test that multiple requests work consistently (indicating proper session handling)
if (responses.length >= 2) {
// Compare response structures for consistency
const firstResponse = responses[0];
const lastResponse = responses[responses.length - 1];
if (typeof firstResponse === 'object' &&
typeof lastResponse === 'object' &&
firstResponse !== null &&
lastResponse !== null &&
'tools' in firstResponse &&
'tools' in lastResponse) {
const firstTools = firstResponse.tools || [];
const lastTools = lastResponse.tools || [];
if (firstTools.length === lastTools.length) {
validations.push('Consistent responses across multiple requests (session handling working)');
}
else {
findings.push('Inconsistent tool counts across requests - possible session issues');
}
}
}
// Note: For STDIO transport, we can't directly validate session IDs
// but we can test that the connection remains stable
validations.push('Connection stability indicates proper session management for STDIO transport');
const hasIssues = findings.length > 0;
const message = hasIssues
? `Session ID generation issues detected (${findings.length} issues)`
: `Session management working correctly (${validations.length} validations)`;
return this.createResult(!hasIssues, message, { findings, validations, transport: 'stdio' }, findings.length > 0
? [
'Ensure consistent session handling across requests',
'Implement proper session lifecycle management',
'Consider testing with HTTP transport for direct session validation',
]
: [
'For HTTP transport testing, verify session ID cryptographic security',
'Implement session ID validation logging',
]);
}
catch (error) {
return this.createResult(false, 'Session ID generation test failed', { error: error instanceof Error ? error.message : String(error) }, [
'Check session management implementation',
'Verify connection stability',
'Review session handling logic',
]);
}
}
}
class SessionTerminationTest extends DiagnosticTest {
name = 'Lifecycle: Session Management - Termination';
description = 'Test proper session cleanup and termination';
category = 'lifecycle';
feature = 'ping';
severity = TEST_SEVERITY.INFO;
async execute(client, _config) {
const observations = [];
const validations = [];
try {
// Test that the current session is working before testing termination
try {
await client.sdk.listTools();
validations.push('Session active and responding before termination test');
}
catch (error) {
observations.push(`Session not responding before termination test: ${error instanceof Error ? error.message : String(error)}`);
return this.createResult(false, 'Cannot test session termination - session not active', { observations }, ['Ensure session is active before testing termination']);
}
// For STDIO transport, termination is handled by process management
// We'll test that the session handles graceful shutdown scenarios
// Test multiple rapid requests to ensure session can handle load before termination
try {
const rapidRequests = await Promise.allSettled([
client.sdk.listTools(),
client.sdk.listResources(),
client.sdk.listPrompts(),
]);
const successful = rapidRequests.filter(r => r.status === 'fulfilled').length;
const failed = rapidRequests.filter(r => r.status === 'rejected').length;
validations.push(`Rapid requests handled: ${successful} success, ${failed} failed`);
if (successful > 0) {
validations.push('Session handles concurrent requests properly before termination');
}
}
catch (error) {
observations.push(`Rapid request test failed: ${error instanceof Error ? error.message : String(error)}`);
}
// Note: Actual termination testing would require creating a separate client
// and testing its lifecycle, which is beyond the scope of this test
observations.push('STDIO transport: Session termination handled by process lifecycle');
validations.push('Session remains stable during normal operation');
const hasIssues = observations.some(obs => obs.includes('failed') || obs.includes('Failed'));
const message = hasIssues
? `Session termination issues detected`
: `Session termination handling appropriate for STDIO transport (${validations.length} validations)`;
return this.createResult(!hasIssues, message, { observations, validations }, hasIssues
? [
'Ensure proper session cleanup',
'Implement graceful termination handling',
'Test session lifecycle management',
]
: [
'For HTTP transport, implement proper DELETE endpoint handling',
'Add session cleanup logging',
'Consider implementing session timeout handling',
]);
}
catch (error) {
return this.createResult(false, 'Session termination test failed', { error: error instanceof Error ? error.message : String(error) }, [
'Check session lifecycle implementation',
'Verify termination handling',
'Review cleanup procedures',
]);
}
}
}
class InvalidSessionHandlingTest extends DiagnosticTest {
name = 'Lifecycle: Session Management - Invalid Session Handling';
description = 'Test handling of invalid session scenarios';
category = 'lifecycle';
feature = 'ping';
severity = TEST_SEVERITY.INFO;
async execute(client, config) {
const observations = [];
const validations = [];
try {
// Test that the session handles various edge cases gracefully
// Test session resilience with various request patterns
const edgeCaseTests = [
{
name: 'Empty request handling',
test: async () => {
try {
// Test with potentially empty or minimal requests
const result = await client.sdk.listTools();
return result ? 'success' : 'empty_response';
}
catch (error) {
return `error: ${error instanceof Error ? error.message : String(error)}`;
}
},
},
{
name: 'Rapid successive requests',
test: async () => {
try {
// Test rapid requests that might stress session handling
const results = await Promise.all([
client.sdk.listTools(),
client.sdk.listTools(),
client.sdk.listTools(),
]);
return results.every(r => r && typeof r === 'object') ? 'success' : 'inconsistent';
}
catch (error) {
return `error: ${error instanceof Error ? error.message : String(error)}`;
}
},
},
];
for (const test of edgeCaseTests) {
try {
const result = await Promise.race([
test.test(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Test timeout')), config.timeouts.testExecution)),
]);
if (result === 'success') {
validations.push(`${test.name}: Handled correctly`);
}
else if (typeof result === 'string' && result.startsWith('error:')) {
if (result.includes('not implemented') || result.includes('not supported')) {
validations.push(`${test.name}: Properly indicates unsupported feature`);
}
else {
observations.push(`${test.name}: ${result}`);
}
}
else {
observations.push(`${test.name}: Unexpected result - ${result}`);
}
}
catch (error) {
observations.push(`${test.name}: Test execution failed - ${error instanceof Error ? error.message : String(error)}`);
}
}
// For STDIO transport, invalid session scenarios are limited
observations.push('STDIO transport: Limited invalid session scenarios (process-based)');
if (validations.length > 0) {
validations.push('Session handling robust against edge cases');
}
const hasIssues = observations.some(obs => obs.includes('failed') || obs.includes('Failed') || obs.includes('error:'));
const message = hasIssues
? `Invalid session handling issues detected (${observations.filter(obs => obs.includes('error:')).length} errors)`
: `Invalid session handling working correctly (${validations.length} validations)`;
return this.createResult(!hasIssues, message, { observations, validations }, hasIssues
? [
'Improve error handling for edge cases',
'Implement proper session validation',
'Add robust error recovery',
]
: [
'For HTTP transport, implement proper 404 responses for invalid sessions',
'Add comprehensive session validation',
'Consider implementing session recovery mechanisms',
]);
}
catch (error) {
return this.createResult(false, 'Invalid session handling test failed', { error: error instanceof Error ? error.message : String(error) }, [
'Check edge case handling',
'Verify error handling implementation',
'Review session validation logic',
]);
}
}
}
// Export test classes for registration in index.ts
export { SessionIdGenerationTest, SessionTerminationTest, InvalidSessionHandlingTest };