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:
@@ -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);
|
||||
});
|
||||
@@ -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
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user