docs: update n8n deployment guide and remove outdated test scripts

- Update N8N_DEPLOYMENT.md to recommend test-n8n-integration.sh
- Remove outdated test-n8n-mode.sh and related files
- The integration test script properly tests full n8n integration with correct protocol version (2024-11-05)
- Removed scripts: test-n8n-mode.sh, test-n8n-mode.ts, debug-n8n-mode.js

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-08-06 21:24:10 +02:00
parent b944afa1bb
commit 72d90a2584
6 changed files with 137 additions and 1026 deletions

View File

@@ -1,327 +0,0 @@
#!/usr/bin/env node
/**
* Debug script for n8n integration issues
* Tests MCP protocol compliance and identifies schema validation problems
*/
const http = require('http');
const crypto = require('crypto');
const MCP_PORT = process.env.MCP_PORT || 3001;
const AUTH_TOKEN = process.env.AUTH_TOKEN || 'test-token-for-n8n-testing-minimum-32-chars';
console.log('🔍 Debugging n8n MCP Integration Issues');
console.log('=====================================\n');
// Test data for different MCP protocol calls
const testCases = [
{
name: 'MCP Initialize',
path: '/mcp',
method: 'POST',
data: {
jsonrpc: '2.0',
method: 'initialize',
params: {
protocolVersion: '2025-03-26',
capabilities: {
tools: {}
},
clientInfo: {
name: 'n8n-debug-test',
version: '1.0.0'
}
},
id: 1
}
},
{
name: 'Tools List',
path: '/mcp',
method: 'POST',
sessionId: null, // Will be set after initialize
data: {
jsonrpc: '2.0',
method: 'tools/list',
params: {},
id: 2
}
},
{
name: 'Tools Call - tools_documentation',
path: '/mcp',
method: 'POST',
sessionId: null, // Will be set after initialize
data: {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'tools_documentation',
arguments: {}
},
id: 3
}
},
{
name: 'Tools Call - get_node_essentials',
path: '/mcp',
method: 'POST',
sessionId: null, // Will be set after initialize
data: {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'get_node_essentials',
arguments: {
nodeType: 'nodes-base.httpRequest'
}
},
id: 4
}
}
];
async function makeRequest(testCase) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(testCase.data);
const options = {
hostname: 'localhost',
port: MCP_PORT,
path: testCase.path,
method: testCase.method,
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data),
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Accept': 'application/json, text/event-stream' // Fix for StreamableHTTPServerTransport
}
};
// Add session ID header if available
if (testCase.sessionId) {
options.headers['Mcp-Session-Id'] = testCase.sessionId;
}
console.log(`📤 Making request: ${testCase.name}`);
console.log(` Method: ${testCase.method} ${testCase.path}`);
if (testCase.sessionId) {
console.log(` Session-ID: ${testCase.sessionId}`);
}
console.log(` Data: ${data}`);
const req = http.request(options, (res) => {
let responseData = '';
console.log(`📥 Response Status: ${res.statusCode}`);
console.log(` Headers:`, res.headers);
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
try {
let parsed;
// Handle SSE format response
if (responseData.startsWith('event: message\ndata: ')) {
const dataLine = responseData.split('\n').find(line => line.startsWith('data: '));
if (dataLine) {
const jsonData = dataLine.substring(6); // Remove 'data: '
parsed = JSON.parse(jsonData);
} else {
throw new Error('Could not extract JSON from SSE response');
}
} else {
parsed = JSON.parse(responseData);
}
resolve({
statusCode: res.statusCode,
headers: res.headers,
data: parsed,
raw: responseData
});
} catch (e) {
resolve({
statusCode: res.statusCode,
headers: res.headers,
data: null,
raw: responseData,
parseError: e.message
});
}
});
});
req.on('error', (err) => {
reject(err);
});
req.write(data);
req.end();
});
}
async function validateMCPResponse(testCase, response) {
console.log(`✅ Validating response for: ${testCase.name}`);
const issues = [];
// Check HTTP status
if (response.statusCode !== 200) {
issues.push(`❌ Expected HTTP 200, got ${response.statusCode}`);
}
// Check JSON-RPC structure
if (!response.data) {
issues.push(`❌ Response is not valid JSON: ${response.parseError}`);
return issues;
}
if (response.data.jsonrpc !== '2.0') {
issues.push(`❌ Missing or invalid jsonrpc field: ${response.data.jsonrpc}`);
}
if (response.data.id !== testCase.data.id) {
issues.push(`❌ ID mismatch: expected ${testCase.data.id}, got ${response.data.id}`);
}
// Method-specific validation
if (testCase.data.method === 'initialize') {
if (!response.data.result) {
issues.push(`❌ Initialize response missing result field`);
} else {
if (!response.data.result.protocolVersion) {
issues.push(`❌ Initialize response missing protocolVersion`);
} else if (response.data.result.protocolVersion !== '2025-03-26') {
issues.push(`❌ Protocol version mismatch: expected 2025-03-26, got ${response.data.result.protocolVersion}`);
}
if (!response.data.result.capabilities) {
issues.push(`❌ Initialize response missing capabilities`);
}
if (!response.data.result.serverInfo) {
issues.push(`❌ Initialize response missing serverInfo`);
}
}
// Extract session ID for subsequent requests
if (response.headers['mcp-session-id']) {
console.log(`📋 Session ID: ${response.headers['mcp-session-id']}`);
return { issues, sessionId: response.headers['mcp-session-id'] };
} else {
issues.push(`❌ Initialize response missing Mcp-Session-Id header`);
}
}
if (testCase.data.method === 'tools/list') {
if (!response.data.result || !response.data.result.tools) {
issues.push(`❌ Tools list response missing tools array`);
} else {
console.log(`📋 Found ${response.data.result.tools.length} tools`);
}
}
if (testCase.data.method === 'tools/call') {
if (!response.data.result) {
issues.push(`❌ Tool call response missing result field`);
} else if (!response.data.result.content) {
issues.push(`❌ Tool call response missing content array`);
} else if (!Array.isArray(response.data.result.content)) {
issues.push(`❌ Tool call response content is not an array`);
} else {
// Validate content structure
for (let i = 0; i < response.data.result.content.length; i++) {
const content = response.data.result.content[i];
if (!content.type) {
issues.push(`❌ Content item ${i} missing type field`);
}
if (content.type === 'text' && !content.text) {
issues.push(`❌ Text content item ${i} missing text field`);
}
}
}
}
if (issues.length === 0) {
console.log(`${testCase.name} validation passed`);
} else {
console.log(`${testCase.name} validation failed:`);
issues.forEach(issue => console.log(` ${issue}`));
}
return { issues };
}
async function runTests() {
console.log('Starting MCP protocol compliance tests...\n');
let sessionId = null;
let allIssues = [];
for (const testCase of testCases) {
try {
// Set session ID from previous test
if (sessionId && testCase.name !== 'MCP Initialize') {
testCase.sessionId = sessionId;
}
const response = await makeRequest(testCase);
console.log(`📄 Raw Response: ${response.raw}\n`);
const validation = await validateMCPResponse(testCase, response);
if (validation.sessionId) {
sessionId = validation.sessionId;
}
allIssues.push(...validation.issues);
console.log('─'.repeat(50));
} catch (error) {
console.error(`❌ Request failed for ${testCase.name}:`, error.message);
allIssues.push(`Request failed for ${testCase.name}: ${error.message}`);
}
}
// Summary
console.log('\n📊 SUMMARY');
console.log('==========');
if (allIssues.length === 0) {
console.log('🎉 All tests passed! MCP protocol compliance looks good.');
} else {
console.log(`❌ Found ${allIssues.length} issues:`);
allIssues.forEach((issue, i) => {
console.log(` ${i + 1}. ${issue}`);
});
}
console.log('\n🔍 Recommendations:');
console.log('1. Check MCP server logs at /tmp/mcp-server.log');
console.log('2. Verify protocol version consistency (should be 2025-03-26)');
console.log('3. Ensure tool schemas match MCP specification exactly');
console.log('4. Test with actual n8n MCP Client Tool node');
}
// Check if MCP server is running
console.log(`Checking if MCP server is running at localhost:${MCP_PORT}...`);
const healthCheck = http.get(`http://localhost:${MCP_PORT}/health`, (res) => {
if (res.statusCode === 200) {
console.log('✅ MCP server is running\n');
runTests().catch(console.error);
} else {
console.error('❌ MCP server health check failed:', res.statusCode);
process.exit(1);
}
}).on('error', (err) => {
console.error('❌ MCP server is not running. Please start it first:', err.message);
console.error('Use: npm run start:n8n');
process.exit(1);
});

View File

@@ -1,95 +0,0 @@
#!/bin/bash
# Test script for n8n MCP integration fixes
set -e
echo "🔧 Testing n8n MCP Integration Fixes"
echo "===================================="
# Configuration
MCP_PORT=${MCP_PORT:-3001}
AUTH_TOKEN=${AUTH_TOKEN:-"test-token-for-n8n-testing-minimum-32-chars"}
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Cleanup function
cleanup() {
echo -e "\n${YELLOW}🧹 Cleaning up...${NC}"
if [ -n "$MCP_PID" ] && kill -0 $MCP_PID 2>/dev/null; then
echo "Stopping MCP server..."
kill $MCP_PID 2>/dev/null || true
wait $MCP_PID 2>/dev/null || true
fi
echo -e "${GREEN}✅ Cleanup complete${NC}"
}
trap cleanup EXIT INT TERM
# Check if we're in the right directory
if [ ! -f "package.json" ] || [ ! -d "dist" ]; then
echo -e "${RED}❌ Error: Must run from n8n-mcp directory${NC}"
exit 1
fi
# Build the project (our fixes)
echo -e "${YELLOW}📦 Building project with fixes...${NC}"
npm run build
# Start MCP server in n8n mode
echo -e "\n${GREEN}🚀 Starting MCP server in n8n mode...${NC}"
N8N_MODE=true \
MCP_MODE=http \
AUTH_TOKEN="${AUTH_TOKEN}" \
PORT=${MCP_PORT} \
DEBUG_MCP=true \
node dist/mcp/index.js > /tmp/mcp-n8n-test.log 2>&1 &
MCP_PID=$!
echo -e "${YELLOW}📄 MCP server logs: /tmp/mcp-n8n-test.log${NC}"
# Wait for server to start
echo -e "${YELLOW}⏳ Waiting for MCP server to start...${NC}"
for i in {1..15}; do
if curl -s http://localhost:${MCP_PORT}/health >/dev/null 2>&1; then
echo -e "${GREEN}✅ MCP server is ready!${NC}"
break
fi
if [ $i -eq 15 ]; then
echo -e "${RED}❌ MCP server failed to start${NC}"
echo "Server logs:"
cat /tmp/mcp-n8n-test.log
exit 1
fi
sleep 1
done
# Test the protocol fixes
echo -e "\n${BLUE}🧪 Testing protocol fixes...${NC}"
# Run our debug script
echo -e "${YELLOW}Running comprehensive MCP protocol tests...${NC}"
node scripts/debug-n8n-mode.js
echo -e "\n${GREEN}🎉 Test complete!${NC}"
echo -e "\n📋 Summary of fixes applied:"
echo -e " ✅ Fixed protocol version mismatch (now using 2025-03-26)"
echo -e " ✅ Enhanced tool response formatting and size validation"
echo -e " ✅ Added comprehensive parameter validation"
echo -e " ✅ Improved error handling and logging"
echo -e " ✅ Added initialization request debugging"
echo -e "\n📝 Next steps:"
echo -e " 1. If tests pass, the n8n schema validation errors should be resolved"
echo -e " 2. Test with actual n8n MCP Client Tool node"
echo -e " 3. Monitor logs at /tmp/mcp-n8n-test.log for any remaining issues"
echo -e "\n${YELLOW}Press any key to view recent server logs, or Ctrl+C to exit...${NC}"
read -n 1
echo -e "\n${BLUE}📄 Recent server logs:${NC}"
tail -50 /tmp/mcp-n8n-test.log

View File

@@ -1,428 +0,0 @@
#!/usr/bin/env ts-node
/**
* TypeScript test script for n8n MCP integration fixes
* Tests the protocol changes and identifies any remaining issues
*/
import http from 'http';
import { spawn, ChildProcess } from 'child_process';
import path from 'path';
interface TestResult {
name: string;
passed: boolean;
error?: string;
response?: any;
}
class N8nMcpTester {
private mcpProcess: ChildProcess | null = null;
private readonly mcpPort = 3001;
private readonly authToken = 'test-token-for-n8n-testing-minimum-32-chars';
private sessionId: string | null = null;
async start(): Promise<void> {
console.log('🔧 Testing n8n MCP Integration Fixes');
console.log('====================================\n');
try {
await this.startMcpServer();
await this.runTests();
} finally {
await this.cleanup();
}
}
private async startMcpServer(): Promise<void> {
console.log('📦 Starting MCP server in n8n mode...');
const projectRoot = path.resolve(__dirname, '..');
this.mcpProcess = spawn('node', ['dist/mcp/index.js'], {
cwd: projectRoot,
env: {
...process.env,
N8N_MODE: 'true',
MCP_MODE: 'http',
AUTH_TOKEN: this.authToken,
PORT: this.mcpPort.toString(),
DEBUG_MCP: 'true'
},
stdio: ['ignore', 'pipe', 'pipe']
});
// Log server output
this.mcpProcess.stdout?.on('data', (data) => {
console.log(`[MCP] ${data.toString().trim()}`);
});
this.mcpProcess.stderr?.on('data', (data) => {
console.error(`[MCP ERROR] ${data.toString().trim()}`);
});
// Wait for server to be ready
await this.waitForServer();
}
private async waitForServer(): Promise<void> {
console.log('⏳ Waiting for MCP server to be ready...');
for (let i = 0; i < 30; i++) {
try {
await this.makeHealthCheck();
console.log('✅ MCP server is ready!\n');
return;
} catch (error) {
if (i === 29) {
throw new Error('MCP server failed to start within 30 seconds');
}
await this.sleep(1000);
}
}
}
private makeHealthCheck(): Promise<void> {
return new Promise((resolve, reject) => {
const req = http.get(`http://localhost:${this.mcpPort}/health`, (res) => {
if (res.statusCode === 200) {
resolve();
} else {
reject(new Error(`Health check failed: ${res.statusCode}`));
}
});
req.on('error', reject);
req.setTimeout(5000, () => {
req.destroy();
reject(new Error('Health check timeout'));
});
});
}
private async runTests(): Promise<void> {
const tests: TestResult[] = [];
// Test 1: Initialize with correct protocol version
tests.push(await this.testInitialize());
// Test 2: List tools
tests.push(await this.testListTools());
// Test 3: Call tools_documentation
tests.push(await this.testToolCall('tools_documentation', {}));
// Test 4: Call get_node_essentials with parameters
tests.push(await this.testToolCall('get_node_essentials', {
nodeType: 'nodes-base.httpRequest'
}));
// Test 5: Call with invalid parameters (should handle gracefully)
tests.push(await this.testToolCallInvalid());
this.printResults(tests);
}
private async testInitialize(): Promise<TestResult> {
console.log('🧪 Testing MCP Initialize...');
try {
const response = await this.makeRequest('POST', '/mcp', {
jsonrpc: '2.0',
method: 'initialize',
params: {
protocolVersion: '2025-03-26',
capabilities: { tools: {} },
clientInfo: { name: 'n8n-test', version: '1.0.0' }
},
id: 1
});
if (response.statusCode !== 200) {
return {
name: 'Initialize',
passed: false,
error: `HTTP ${response.statusCode}`
};
}
const data = JSON.parse(response.body);
// Extract session ID
this.sessionId = response.headers['mcp-session-id'] as string;
if (data.result?.protocolVersion === '2025-03-26') {
return {
name: 'Initialize',
passed: true,
response: data
};
} else {
return {
name: 'Initialize',
passed: false,
error: `Wrong protocol version: ${data.result?.protocolVersion}`,
response: data
};
}
} catch (error) {
return {
name: 'Initialize',
passed: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
private async testListTools(): Promise<TestResult> {
console.log('🧪 Testing Tools List...');
try {
const response = await this.makeRequest('POST', '/mcp', {
jsonrpc: '2.0',
method: 'tools/list',
params: {},
id: 2
}, this.sessionId);
if (response.statusCode !== 200) {
return {
name: 'List Tools',
passed: false,
error: `HTTP ${response.statusCode}`
};
}
const data = JSON.parse(response.body);
if (data.result?.tools && Array.isArray(data.result.tools)) {
return {
name: 'List Tools',
passed: true,
response: { toolCount: data.result.tools.length }
};
} else {
return {
name: 'List Tools',
passed: false,
error: 'Missing or invalid tools array',
response: data
};
}
} catch (error) {
return {
name: 'List Tools',
passed: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
private async testToolCall(toolName: string, args: any): Promise<TestResult> {
console.log(`🧪 Testing Tool Call: ${toolName}...`);
try {
const response = await this.makeRequest('POST', '/mcp', {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: toolName,
arguments: args
},
id: 3
}, this.sessionId);
if (response.statusCode !== 200) {
return {
name: `Tool Call: ${toolName}`,
passed: false,
error: `HTTP ${response.statusCode}`
};
}
const data = JSON.parse(response.body);
if (data.result?.content && Array.isArray(data.result.content)) {
return {
name: `Tool Call: ${toolName}`,
passed: true,
response: { contentItems: data.result.content.length }
};
} else {
return {
name: `Tool Call: ${toolName}`,
passed: false,
error: 'Missing or invalid content array',
response: data
};
}
} catch (error) {
return {
name: `Tool Call: ${toolName}`,
passed: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
private async testToolCallInvalid(): Promise<TestResult> {
console.log('🧪 Testing Tool Call with invalid parameters...');
try {
const response = await this.makeRequest('POST', '/mcp', {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'get_node_essentials',
arguments: {} // Missing required nodeType parameter
},
id: 4
}, this.sessionId);
if (response.statusCode !== 200) {
return {
name: 'Tool Call: Invalid Params',
passed: false,
error: `HTTP ${response.statusCode}`
};
}
const data = JSON.parse(response.body);
// Should either return an error response or handle gracefully
if (data.error || (data.result?.isError && data.result?.content)) {
return {
name: 'Tool Call: Invalid Params',
passed: true,
response: { handledGracefully: true }
};
} else {
return {
name: 'Tool Call: Invalid Params',
passed: false,
error: 'Did not handle invalid parameters properly',
response: data
};
}
} catch (error) {
return {
name: 'Tool Call: Invalid Params',
passed: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
private makeRequest(method: string, path: string, data?: any, sessionId?: string | null): Promise<{
statusCode: number;
headers: http.IncomingHttpHeaders;
body: string;
}> {
return new Promise((resolve, reject) => {
const postData = data ? JSON.stringify(data) : '';
const options: http.RequestOptions = {
hostname: 'localhost',
port: this.mcpPort,
path,
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.authToken}`,
...(postData && { 'Content-Length': Buffer.byteLength(postData) }),
...(sessionId && { 'Mcp-Session-Id': sessionId })
}
};
const req = http.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
resolve({
statusCode: res.statusCode || 0,
headers: res.headers,
body
});
});
});
req.on('error', reject);
req.setTimeout(10000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
if (postData) {
req.write(postData);
}
req.end();
});
}
private printResults(tests: TestResult[]): void {
console.log('\n📊 TEST RESULTS');
console.log('================');
const passed = tests.filter(t => t.passed).length;
const total = tests.length;
tests.forEach(test => {
const status = test.passed ? '✅' : '❌';
console.log(`${status} ${test.name}`);
if (!test.passed && test.error) {
console.log(` Error: ${test.error}`);
}
if (test.response) {
console.log(` Response: ${JSON.stringify(test.response, null, 2)}`);
}
});
console.log(`\n📈 Summary: ${passed}/${total} tests passed`);
if (passed === total) {
console.log('🎉 All tests passed! The n8n integration fixes should resolve the schema validation errors.');
} else {
console.log('❌ Some tests failed. Please review the errors above.');
}
}
private async cleanup(): Promise<void> {
console.log('\n🧹 Cleaning up...');
if (this.mcpProcess) {
this.mcpProcess.kill('SIGTERM');
// Wait for graceful shutdown
await new Promise<void>((resolve) => {
if (!this.mcpProcess) {
resolve();
return;
}
const timeout = setTimeout(() => {
this.mcpProcess?.kill('SIGKILL');
resolve();
}, 5000);
this.mcpProcess.on('exit', () => {
clearTimeout(timeout);
resolve();
});
});
}
console.log('✅ Cleanup complete');
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Run the tests
if (require.main === module) {
const tester = new N8nMcpTester();
tester.start().catch(console.error);
}
export { N8nMcpTester };