Files
n8n-mcp/scripts/test-sse.ts
czlonkowski 54e09c9673 feat: SSE (Server-Sent Events) support for n8n integration
- Added SSE server implementation for real-time event streaming
- Created n8n compatibility mode with strict schema validation
- Implemented session management for concurrent connections
- Added comprehensive SSE documentation and examples
- Enhanced MCP tools with async execution support
- Added Docker Compose configuration for SSE deployment
- Included test scripts and integration tests

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 08:24:44 +02:00

242 lines
7.1 KiB
TypeScript
Executable File

#!/usr/bin/env ts-node
/**
* Test script for SSE server implementation
* Tests the SSE connection and MCP protocol communication
*/
import axios from 'axios';
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000';
const AUTH_TOKEN = process.env.AUTH_TOKEN || 'test-token';
interface TestResult {
test: string;
status: 'passed' | 'failed';
message?: string;
duration?: number;
}
const results: TestResult[] = [];
function logTest(test: string, status: 'passed' | 'failed', message?: string, duration?: number) {
results.push({ test, status, message, duration });
console.log(`${status === 'passed' ? '✅' : '❌'} ${test}${message ? `: ${message}` : ''}${duration ? ` (${duration}ms)` : ''}`);
}
async function testHealthCheck() {
const start = Date.now();
try {
const response = await axios.get(`${SERVER_URL}/health`);
const duration = Date.now() - start;
if (response.data.status === 'ok' && response.data.mode === 'sse') {
logTest('Health check', 'passed', `Server is running in SSE mode`, duration);
} else {
logTest('Health check', 'failed', `Unexpected response: ${JSON.stringify(response.data)}`, duration);
}
} catch (error) {
logTest('Health check', 'failed', error instanceof Error ? error.message : 'Unknown error');
}
}
async function testSSEConnection(): Promise<string | null> {
return new Promise((resolve) => {
const start = Date.now();
let clientId: string | null = null;
let eventSource: EventSource | null = null;
try {
// Note: eventsource package doesn't support headers in constructor
// We'll need to use a different approach or library for authenticated SSE
const EventSourcePolyfill = require('eventsource');
eventSource = new EventSourcePolyfill(`${SERVER_URL}/sse`, {
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`
}
}) as EventSource;
eventSource.addEventListener('connected', (event: any) => {
const duration = Date.now() - start;
const data = JSON.parse(event.data);
clientId = data.clientId;
logTest('SSE connection', 'passed', `Connected with client ID: ${clientId}`, duration);
});
eventSource.addEventListener('mcp-response', (event: any) => {
const data = JSON.parse(event.data);
console.log(' Received MCP response:', data);
});
eventSource.addEventListener('ping', (event: any) => {
console.log(' Received ping:', event.data);
});
eventSource.onerror = (error) => {
logTest('SSE connection', 'failed', `Connection error: ${error}`);
eventSource?.close();
resolve(null);
};
// Wait for connection and initial message
setTimeout(() => {
if (eventSource) {
eventSource.close();
}
resolve(clientId);
}, 2000);
} catch (error) {
logTest('SSE connection', 'failed', error instanceof Error ? error.message : 'Unknown error');
resolve(null);
}
});
}
async function testMCPMessage(clientId: string) {
const start = Date.now();
try {
// Test initialize
const initResponse = await axios.post(
`${SERVER_URL}/mcp/message`,
{
jsonrpc: '2.0',
method: 'initialize',
id: 'test-init-1',
params: {}
},
{
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'X-Client-ID': clientId,
'Content-Type': 'application/json'
}
}
);
const duration = Date.now() - start;
if (initResponse.data.status === 'ok') {
logTest('MCP initialize message', 'passed', `Message acknowledged`, duration);
} else {
logTest('MCP initialize message', 'failed', `Unexpected response: ${JSON.stringify(initResponse.data)}`, duration);
}
} catch (error) {
logTest('MCP initialize message', 'failed', error instanceof Error ? error.message : 'Unknown error');
}
}
async function testToolsList(clientId: string) {
const start = Date.now();
try {
const response = await axios.post(
`${SERVER_URL}/mcp/message`,
{
jsonrpc: '2.0',
method: 'tools/list',
id: 'test-tools-1',
params: {}
},
{
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'X-Client-ID': clientId,
'Content-Type': 'application/json'
}
}
);
const duration = Date.now() - start;
if (response.data.status === 'ok') {
logTest('MCP tools/list message', 'passed', `Message acknowledged`, duration);
} else {
logTest('MCP tools/list message', 'failed', `Unexpected response: ${JSON.stringify(response.data)}`, duration);
}
} catch (error) {
logTest('MCP tools/list message', 'failed', error instanceof Error ? error.message : 'Unknown error');
}
}
async function testLegacyEndpoint() {
const start = Date.now();
try {
const response = await axios.post(
`${SERVER_URL}/mcp`,
{
jsonrpc: '2.0',
method: 'tools/list',
id: 'test-legacy-1',
params: {}
},
{
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
const duration = Date.now() - start;
if (response.data.result && response.data.result.tools) {
logTest('Legacy /mcp endpoint', 'passed', `Found ${response.data.result.tools.length} tools`, duration);
} else {
logTest('Legacy /mcp endpoint', 'failed', `Unexpected response: ${JSON.stringify(response.data)}`, duration);
}
} catch (error) {
logTest('Legacy /mcp endpoint', 'failed', error instanceof Error ? error.message : 'Unknown error');
}
}
async function runTests() {
console.log('🧪 Testing SSE Server Implementation');
console.log(`Server URL: ${SERVER_URL}`);
console.log(`Auth Token: ${AUTH_TOKEN.substring(0, 8)}...`);
console.log('');
// Health check
await testHealthCheck();
// SSE connection
const clientId = await testSSEConnection();
if (clientId) {
// Test MCP messages
await testMCPMessage(clientId);
await testToolsList(clientId);
}
// Test legacy endpoint
await testLegacyEndpoint();
// Summary
console.log('\n📊 Test Summary:');
const passed = results.filter(r => r.status === 'passed').length;
const failed = results.filter(r => r.status === 'failed').length;
console.log(`Total: ${results.length}, Passed: ${passed}, Failed: ${failed}`);
if (failed > 0) {
console.log('\n❌ Failed tests:');
results.filter(r => r.status === 'failed').forEach(r => {
console.log(` - ${r.test}: ${r.message}`);
});
process.exit(1);
} else {
console.log('\n✅ All tests passed!');
process.exit(0);
}
}
// Install eventsource if not available
try {
require('eventsource');
} catch {
console.log('Installing eventsource package...');
require('child_process').execSync('npm install --no-save eventsource', { stdio: 'inherit' });
}
// Run tests
runTests().catch(error => {
console.error('Test runner error:', error);
process.exit(1);
});