diff --git a/README.md b/README.md index 0a7798b..dcdf0d2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub stars](https://img.shields.io/github/stars/czlonkowski/n8n-mcp?style=social)](https://github.com/czlonkowski/n8n-mcp) -[![Version](https://img.shields.io/badge/version-2.10.1-blue.svg)](https://github.com/czlonkowski/n8n-mcp) +[![Version](https://img.shields.io/badge/version-2.10.2-blue.svg)](https://github.com/czlonkowski/n8n-mcp) [![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp) [![codecov](https://codecov.io/gh/czlonkowski/n8n-mcp/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/czlonkowski/n8n-mcp) [![Tests](https://img.shields.io/badge/tests-1356%20passing-brightgreen.svg)](https://github.com/czlonkowski/n8n-mcp/actions) diff --git a/data/nodes.db b/data/nodes.db index f05debc..3dba632 100644 Binary files a/data/nodes.db and b/data/nodes.db differ diff --git a/docs/N8N_DEPLOYMENT.md b/docs/N8N_DEPLOYMENT.md index 0a1f459..a26cbec 100644 --- a/docs/N8N_DEPLOYMENT.md +++ b/docs/N8N_DEPLOYMENT.md @@ -1,6 +1,6 @@ # n8n-MCP Deployment Guide -This guide covers how to deploy n8n-MCP and connect it to AI Agent nodes with the standard MCP Client Tool. Whether you're testing locally or deploying to production, we'll show you how to set it up. +This guide covers how to deploy n8n-MCP and connect it to your n8n instance. Whether you're testing locally or deploying to production, we'll show you how to set up n8n-MCP for use with n8n's MCP Client Tool node. ## Table of Contents - [Overview](#overview) @@ -35,15 +35,15 @@ cd n8n-mcp npm install npm run build -# Run the test script -./scripts/test-n8n-mode.sh +# Run the integration test script +./scripts/test-n8n-integration.sh ``` This script will: -1. Start n8n-MCP in n8n mode on port 3001 -2. Enable debug logging for troubleshooting -3. Run comprehensive protocol tests -4. Display results and any issues found +1. Start a real n8n instance in Docker +2. Start n8n-MCP server configured for n8n +3. Guide you through API key setup for workflow management +4. Test the complete integration between n8n and n8n-MCP ### Manual Local Setup @@ -86,8 +86,8 @@ curl http://localhost:3001/mcp | `MCP_MODE` | Yes | Enables HTTP mode for n8n MCP Client | `http` | | `N8N_API_URL` | Yes* | URL of your n8n instance | `http://localhost:5678` | | `N8N_API_KEY` | Yes* | n8n API key for workflow management | `n8n_api_xxx...` | -| `MCP_AUTH_TOKEN` | Yes | Authentication token for MCP requests | `secure-random-32-char-token` | -| `AUTH_TOKEN` | Yes | Must match MCP_AUTH_TOKEN | `secure-random-32-char-token` | +| `MCP_AUTH_TOKEN` | Yes | Authentication token for MCP requests (min 32 chars) | `secure-random-32-char-token` | +| `AUTH_TOKEN` | Yes | **MUST match MCP_AUTH_TOKEN exactly** | `secure-random-32-char-token` | | `PORT` | No | Port for the HTTP server | `3000` (default) | | `LOG_LEVEL` | No | Logging verbosity | `info`, `debug`, `error` | @@ -103,13 +103,48 @@ Starting with version 2.9.2, we use a single optimized Dockerfile for all deploy ## Production Deployment +> **⚠️ Critical**: Docker caches images locally. Always run `docker pull ghcr.io/czlonkowski/n8n-mcp:latest` before deploying to ensure you have the latest version. This simple step prevents most deployment issues. + ### Same Server as n8n If you're running n8n-MCP on the same server as your n8n instance: -### Building from Source (Recommended) +### Using Pre-built Image (Recommended) -For the latest features and bug fixes, build from source: +The pre-built images are automatically updated with each release and are the easiest way to get started. + +**IMPORTANT**: Always pull the latest image to avoid using cached versions: + +```bash +# ALWAYS pull the latest image first +docker pull ghcr.io/czlonkowski/n8n-mcp:latest + +# Generate a secure token (save this!) +AUTH_TOKEN=$(openssl rand -hex 32) +echo "Your AUTH_TOKEN: $AUTH_TOKEN" + +# Create a Docker network if n8n uses one +docker network create n8n-net + +# Run n8n-MCP container +docker run -d \ + --name n8n-mcp \ + --network n8n-net \ + -p 3000:3000 \ + -e N8N_MODE=true \ + -e MCP_MODE=http \ + -e N8N_API_URL=http://n8n:5678 \ + -e N8N_API_KEY=your-n8n-api-key \ + -e MCP_AUTH_TOKEN=$AUTH_TOKEN \ + -e AUTH_TOKEN=$AUTH_TOKEN \ + -e LOG_LEVEL=info \ + --restart unless-stopped \ + ghcr.io/czlonkowski/n8n-mcp:latest +``` + +### Building from Source (Advanced Users) + +Only build from source if you need custom modifications or are contributing to development: ```bash # Clone and build @@ -119,49 +154,18 @@ cd n8n-mcp # Build Docker image docker build -t n8n-mcp:latest . -# Create a Docker network if n8n uses one -docker network create n8n-net - -# Run n8n-MCP container +# Run using your local image docker run -d \ --name n8n-mcp \ - --network n8n-net \ -p 3000:3000 \ -e N8N_MODE=true \ -e MCP_MODE=http \ - -e N8N_API_URL=http://n8n:5678 \ - -e N8N_API_KEY=your-n8n-api-key \ -e MCP_AUTH_TOKEN=$(openssl rand -hex 32) \ -e AUTH_TOKEN=$(openssl rand -hex 32) \ - -e LOG_LEVEL=info \ - --restart unless-stopped \ + # ... other settings n8n-mcp:latest ``` -### Using Pre-built Image (May Be Outdated) - -⚠️ **Warning**: Pre-built images may be outdated due to CI/CD synchronization issues. Always check the [GitHub releases](https://github.com/czlonkowski/n8n-mcp/releases) for the latest version. - -```bash -# Create a Docker network if n8n uses one -docker network create n8n-net - -# Run n8n-MCP container -docker run -d \ - --name n8n-mcp \ - --network n8n-net \ - -p 3000:3000 \ - -e N8N_MODE=true \ - -e MCP_MODE=http \ - -e N8N_API_URL=http://n8n:5678 \ - -e N8N_API_KEY=your-n8n-api-key \ - -e MCP_AUTH_TOKEN=$(openssl rand -hex 32) \ - -e AUTH_TOKEN=$(openssl rand -hex 32) \ - -e LOG_LEVEL=info \ - --restart unless-stopped \ - ghcr.io/czlonkowski/n8n-mcp:latest -``` - ### Using systemd (for native installation) ```bash @@ -198,43 +202,19 @@ sudo systemctl start n8n-mcp Deploy n8n-MCP on a separate server from your n8n instance: -#### Quick Docker Deployment (Build from Source) +#### Quick Docker Deployment (Recommended) + +**Always pull the latest image to ensure you have the current version:** ```bash # On your cloud server (Hetzner, AWS, DigitalOcean, etc.) -# First, clone and build -git clone https://github.com/czlonkowski/n8n-mcp.git -cd n8n-mcp -docker build -t n8n-mcp:latest . +# ALWAYS pull the latest image first +docker pull ghcr.io/czlonkowski/n8n-mcp:latest # Generate auth tokens AUTH_TOKEN=$(openssl rand -hex 32) echo "Save this AUTH_TOKEN: $AUTH_TOKEN" -# Run the container -docker run -d \ - --name n8n-mcp \ - -p 3000:3000 \ - -e N8N_MODE=true \ - -e MCP_MODE=http \ - -e N8N_API_URL=https://your-n8n-instance.com \ - -e N8N_API_KEY=your-n8n-api-key \ - -e MCP_AUTH_TOKEN=$AUTH_TOKEN \ - -e AUTH_TOKEN=$AUTH_TOKEN \ - -e LOG_LEVEL=info \ - --restart unless-stopped \ - n8n-mcp:latest -``` - -#### Quick Docker Deployment (Pre-built Image) - -⚠️ **Warning**: May be outdated. Check [releases](https://github.com/czlonkowski/n8n-mcp/releases) first. - -```bash -# Generate auth tokens -AUTH_TOKEN=$(openssl rand -hex 32) -echo "Save this AUTH_TOKEN: $AUTH_TOKEN" - # Run the container docker run -d \ --name n8n-mcp \ @@ -250,6 +230,24 @@ docker run -d \ ghcr.io/czlonkowski/n8n-mcp:latest ``` +#### Building from Source (Advanced) + +Only needed if you're modifying the code: + +```bash +# Clone and build +git clone https://github.com/czlonkowski/n8n-mcp.git +cd n8n-mcp +docker build -t n8n-mcp:latest . + +# Run using local image +docker run -d \ + --name n8n-mcp \ + -p 3000:3000 \ + # ... same environment variables as above + n8n-mcp:latest +``` + #### Full Production Setup (Hetzner/AWS/DigitalOcean) 1. **Server Requirements**: @@ -269,61 +267,7 @@ curl -fsSL https://get.docker.com | sh 3. **Deploy n8n-MCP with SSL** (using Caddy for automatic HTTPS): -**Option A: Build from Source (Recommended)** -```bash -# Clone and prepare -git clone https://github.com/czlonkowski/n8n-mcp.git -cd n8n-mcp - -# Build local image -docker build -t n8n-mcp:latest . - -# Create docker-compose.yml -cat > docker-compose.yml << 'EOF' -version: '3.8' - -services: - n8n-mcp: - image: n8n-mcp:latest # Using locally built image - container_name: n8n-mcp - restart: unless-stopped - environment: - - N8N_MODE=true - - MCP_MODE=http - - N8N_API_URL=${N8N_API_URL} - - N8N_API_KEY=${N8N_API_KEY} - - MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN} - - AUTH_TOKEN=${AUTH_TOKEN} - - PORT=3000 - - LOG_LEVEL=info - networks: - - web - - caddy: - image: caddy:2-alpine - container_name: caddy - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile - - caddy_data:/data - - caddy_config:/config - networks: - - web - -networks: - web: - driver: bridge - -volumes: - caddy_data: - caddy_config: -EOF -``` - -**Option B: Pre-built Image (May Be Outdated)** +**Using Docker Compose (Recommended)** ```bash # Create docker-compose.yml cat > docker-compose.yml << 'EOF' @@ -332,6 +276,7 @@ version: '3.8' services: n8n-mcp: image: ghcr.io/czlonkowski/n8n-mcp:latest + pull_policy: always # Always pull latest image container_name: n8n-mcp restart: unless-stopped environment: @@ -370,7 +315,56 @@ volumes: EOF ``` -**Complete Setup (Both Options)** +**Note**: The `pull_policy: always` ensures you always get the latest version. + +**Building from Source (if needed)** +```bash +# Only if you need custom modifications +git clone https://github.com/czlonkowski/n8n-mcp.git +cd n8n-mcp +docker build -t n8n-mcp:local . + +# Then update docker-compose.yml to use: +# image: n8n-mcp:local + container_name: n8n-mcp + restart: unless-stopped + environment: + - N8N_MODE=true + - MCP_MODE=http + - N8N_API_URL=${N8N_API_URL} + - N8N_API_KEY=${N8N_API_KEY} + - MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN} + - AUTH_TOKEN=${AUTH_TOKEN} + - PORT=3000 + - LOG_LEVEL=info + networks: + - web + + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + - caddy_data:/data + - caddy_config:/config + networks: + - web + +networks: + web: + driver: bridge + +volumes: + caddy_data: + caddy_config: +EOF +``` + +**Complete the Setup** ```bash # Create Caddyfile cat > Caddyfile << 'EOF' @@ -481,12 +475,21 @@ You are an n8n workflow expert. Use the MCP tools to: - **IP Whitelisting**: Consider restricting access to known n8n instances ### Docker Security +- **Always pull latest images**: Docker caches images locally, so run `docker pull` before deployment - Run containers with `--read-only` flag if possible - Use specific image versions instead of `:latest` in production - Regular updates: `docker pull ghcr.io/czlonkowski/n8n-mcp:latest` ## Troubleshooting +### Docker Image Issues + +**Using Outdated Cached Images** +- **Symptom**: Missing features, old bugs reappearing, features not working as documented +- **Cause**: Docker uses locally cached images instead of pulling the latest version +- **Solution**: Always run `docker pull ghcr.io/czlonkowski/n8n-mcp:latest` before deployment +- **Verification**: Check image age with `docker images | grep n8n-mcp` + ### Common Configuration Issues **Missing `MCP_MODE=http` Environment Variable** @@ -572,10 +575,10 @@ You are an n8n workflow expert. Use the MCP tools to: ### Version Compatibility Issues -**"Outdated Docker Image"** +**"Features Not Working as Expected"** - **Symptom**: Missing features, old bugs, or compatibility issues -- **Solution**: Build from source instead of using pre-built images -- **Check**: Compare your image version with [GitHub releases](https://github.com/czlonkowski/n8n-mcp/releases) +- **Solution**: Pull the latest image: `docker pull ghcr.io/czlonkowski/n8n-mcp:latest` +- **Check**: Verify image date with `docker inspect ghcr.io/czlonkowski/n8n-mcp:latest | grep Created` **"Protocol version mismatch"** - n8n-MCP automatically uses version 2024-11-05 for n8n compatibility @@ -743,48 +746,6 @@ curl http://localhost:3001/mcp - **Response time**: Average 12ms for queries - **Caching**: Built-in 15-minute cache for repeated queries -## Railway Deployment for n8n Integration -[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp) - -If you're using the **Deploy to Railway** button, you'll need to modify some environment variables since Railway uses a different Docker image (`Dockerfile.railway`). - -### Required Environment Variable Changes - -When deploying with Railway for n8n integration, add these variables in your Railway dashboard: - -1. **Go to Railway dashboard** → Your service → **Variables tab** -2. **Add the following variables**: - -```bash -# Required for n8n integration mode -N8N_MODE=true - -# Already set by Railway template, but verify: -MCP_MODE=http # Required for HTTP mode -MCP_AUTH_TOKEN= # Must match AUTH_TOKEN -AUTH_TOKEN= # Same value as MCP_AUTH_TOKEN - -# Optional: For workflow management features -N8N_API_URL=https://your-n8n-instance.com -N8N_API_KEY=your-n8n-api-key -``` - -3. **Save changes** - Railway will automatically redeploy - -### Connecting n8n to Railway-deployed n8n-MCP - -In your n8n workflow, configure the MCP Client Tool with: - -``` -Server URL: https://your-app.up.railway.app/mcp -Auth Token: [Your AUTH_TOKEN value] -Transport: HTTP Streamable (SSE) -``` - -> **Note**: The Railway deployment automatically includes all required dependencies and uses the optimized `Dockerfile.railway` which is compatible with both Claude Desktop and n8n integrations. - -For more details on Railway deployment, see our [Railway Deployment Guide](./RAILWAY_DEPLOYMENT.md). - ## Next Steps - Test your setup with the [MCP Client Tool in n8n](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-langchain.mcpclienttool/) @@ -794,4 +755,4 @@ For more details on Railway deployment, see our [Railway Deployment Guide](./RAI --- -Need help? Open an issue on [GitHub](https://github.com/czlonkowski/n8n-mcp/issues) or check the [n8n forums](https://community.n8n.io). \ No newline at end of file +Need help? Open an issue on [GitHub](https://github.com/czlonkowski/n8n-mcp/issues) or check the [n8n forums](https://community.n8n.io) \ No newline at end of file diff --git a/scripts/debug-n8n-mode.js b/scripts/debug-n8n-mode.js deleted file mode 100644 index fec2114..0000000 --- a/scripts/debug-n8n-mode.js +++ /dev/null @@ -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); -}); \ No newline at end of file diff --git a/scripts/test-n8n-mode.sh b/scripts/test-n8n-mode.sh deleted file mode 100755 index 420f7a6..0000000 --- a/scripts/test-n8n-mode.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test-n8n-mode.ts b/scripts/test-n8n-mode.ts deleted file mode 100644 index 4afcd44..0000000 --- a/scripts/test-n8n-mode.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - console.log('\n🧹 Cleaning up...'); - - if (this.mcpProcess) { - this.mcpProcess.kill('SIGTERM'); - - // Wait for graceful shutdown - await new Promise((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 { - 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 }; \ No newline at end of file