feat: add HTTP server mode for remote deployment with token auth

This commit is contained in:
czlonkowski
2025-06-13 11:54:42 +02:00
parent 23a21071bc
commit 89e1df03de
11 changed files with 512 additions and 48 deletions

View File

@@ -29,22 +29,17 @@ MCP_SERVER_HOST=localhost
# MCP_AUTH_TOKEN=optional-for-local-development
# =========================
# HTTP MODE CONFIGURATION
# SIMPLE HTTP MODE
# =========================
# Used when running: npm run start:http or npm run dev:http
# Used for private single-user deployments
# HTTP Server Configuration
MCP_PORT=3000
MCP_HOST=0.0.0.0
MCP_DOMAIN=localhost
# Server mode: stdio (local) or http (remote)
MCP_MODE=stdio
# Authentication (REQUIRED for production HTTP mode)
# Generate a secure token: openssl rand -hex 32
# MCP_AUTH_TOKEN=your-secure-auth-token-here
# HTTP Server Configuration (only used when MCP_MODE=http)
PORT=3000
HOST=0.0.0.0
# CORS - Enable for browser-based access
MCP_CORS=false
# TLS Configuration (optional for HTTPS)
# MCP_TLS_CERT=/path/to/cert.pem
# MCP_TLS_KEY=/path/to/key.pem
# Authentication token for HTTP mode (REQUIRED)
# Generate with: openssl rand -base64 32
AUTH_TOKEN=your-secure-token-here

View File

@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
## [2.3.0] - 2024-12-06
### Added
- **HTTP Remote Deployment**: Single-user HTTP server for remote access
- Stateless architecture for simple deployments
- Bearer token authentication
- Compatible with mcp-remote adapter for Claude Desktop
- New HTTP mode scripts and deployment helper
- **Universal Node.js Compatibility**: Automatic database adapter fallback system
- Primary adapter: `better-sqlite3` for optimal performance
- Fallback adapter: `sql.js` (pure JavaScript) for version mismatches
@@ -13,23 +18,28 @@ All notable changes to this project will be documented in this file.
- Database adapter abstraction layer (`src/database/database-adapter.ts`)
- Version detection and logging for troubleshooting
- sql.js dependency for pure JavaScript SQLite implementation
- HTTP server implementation (`src/http-server.ts`)
- Deployment documentation and scripts
### Changed
- Updated all database operations to use the adapter interface
- Removed Node.js v20.17.0 requirement - now works with ANY version
- Simplified Claude Desktop setup - no wrapper scripts needed
- Enhanced error messages for database initialization
- Made all MCP tool handlers async for proper initialization
### Fixed
- NODE_MODULE_VERSION mismatch errors with Claude Desktop
- Native module compilation issues in restricted environments
- Compatibility issues when running with different Node.js versions
- Database initialization race conditions in HTTP mode
### Technical Details
- Better-sqlite3: ~10-50x faster (when compatible)
- sql.js: ~2-5x slower but universally compatible
- Both adapters maintain identical API and functionality
- Automatic persistence for sql.js with 100ms debounced saves
- HTTP server uses StreamableHTTPServerTransport for MCP compatibility
## [2.2.0] - 2024-12-06

View File

@@ -72,13 +72,19 @@ npm run rebuild # Rebuild node database
npm run validate # Validate critical nodes
npm run test-nodes # Test critical node properties/operations
# HTTP Server Commands:
npm run start:http # Start server in HTTP mode
npm run http # Build and start HTTP server
npm run dev:http # HTTP server with auto-reload
# Legacy Commands (deprecated):
npm run db:rebuild # Old rebuild command
npm run db:init # Initialize empty database
npm run docs:rebuild # Rebuild documentation from TypeScript source
# Production
npm start # Run built application
npm start # Run built application (stdio mode)
npm run start:http # Run in HTTP mode for remote access
```
## High-Level Architecture
@@ -234,6 +240,46 @@ This project uses the Sustainable Use License. Key points:
- ❌ Cannot host as a service without permission
- ❌ Cannot include in commercial products without permission
## HTTP Remote Deployment (v2.3.0)
### ✅ HTTP Server Implementation Complete
The project now includes a simplified HTTP server mode for remote deployments:
- **Single-user design**: Stateless architecture for private deployments
- **Simple token auth**: Bearer token authentication
- **MCP-compatible**: Works with mcp-remote adapter for Claude Desktop
- **Easy deployment**: Minimal configuration required
### Quick Start
```bash
# Server setup
export MCP_MODE=http
export AUTH_TOKEN=$(openssl rand -base64 32)
npm run start:http
# Client setup (Claude Desktop config)
{
"mcpServers": {
"n8n-documentation": {
"command": "mcp-remote",
"args": [
"https://your-server.com/mcp",
"--header",
"Authorization: Bearer your-auth-token"
]
}
}
}
```
### Available Scripts
- `npm run start:http` - Start in HTTP mode
- `npm run http` - Build and start HTTP server
- `npm run dev:http` - Development mode with auto-reload
- `./scripts/deploy-http.sh` - Deployment helper script
For detailed deployment instructions, see [HTTP Deployment Guide](./docs/HTTP_DEPLOYMENT.md).
## Recent Problem Solutions
### SQLite Version Mismatch (Solved in v2.3)

View File

@@ -184,21 +184,45 @@ Current implementation achieves:
- ✅ 35 AI-capable tools detected
- ✅ All critical nodes validated
## Future Development
## Remote Deployment
### HTTP Remote Deployment (Planned)
### HTTP Server Mode
We are planning to add HTTP transport support to enable remote deployment of the MCP server. This will allow:
- Centralized hosting on cloud servers
- Multiple users connecting to a single instance
- No local installation required
- Enterprise-ready authentication
n8n-MCP now supports HTTP mode for remote deployments. This allows you to:
- Host the MCP server on a cloud VPS
- Connect from Claude Desktop using mcp-remote
- Single-user design for private use
- Simple token-based authentication
For detailed planning documents, see:
- [HTTP Remote Deployment Plan](./HTTP_REMOTE_DEPLOYMENT_PLAN.md)
- [HTTP Implementation Guide](./HTTP_IMPLEMENTATION_GUIDE.md)
- [HTTP Implementation Roadmap](./HTTP_IMPLEMENTATION_ROADMAP.md)
- [HTTP Remote Summary](./HTTP_REMOTE_SUMMARY.md)
### Quick Start for HTTP Mode
1. On your server:
```bash
# Set environment variables
export MCP_MODE=http
export AUTH_TOKEN=$(openssl rand -base64 32)
# Start the server
npm run start:http
```
2. On your client, configure Claude Desktop with mcp-remote:
```json
{
"mcpServers": {
"n8n-documentation": {
"command": "mcp-remote",
"args": [
"https://your-server.com/mcp",
"--header",
"Authorization: Bearer your-auth-token"
]
}
}
}
```
For detailed instructions, see [HTTP Deployment Guide](./docs/HTTP_DEPLOYMENT.md).
## Contributing

214
docs/HTTP_DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,214 @@
# HTTP Deployment Guide
This guide explains how to deploy n8n-MCP as a private HTTP server for remote access.
## Overview
The HTTP mode allows you to run n8n-MCP on a remote server and connect to it from Claude Desktop using the mcp-remote adapter. This is designed for single-user private deployments.
## Requirements
- Node.js v16+ on the server
- A server with a public IP or domain
- HTTPS proxy (nginx/caddy) for secure connections
- mcp-remote installed on the client
## Server Setup
### 1. Clone and Build
```bash
git clone https://github.com/yourusername/n8n-mcp.git
cd n8n-mcp
npm install
npm run build
npm run rebuild
```
### 2. Configure Environment
```bash
cp .env.example .env
```
Edit `.env`:
```env
# HTTP mode configuration
MCP_MODE=http
PORT=3000
HOST=0.0.0.0
# Generate secure token
AUTH_TOKEN=your-secure-token-here
# Other settings
NODE_DB_PATH=./data/nodes.db
MCP_LOG_LEVEL=info
NODE_ENV=production
```
Generate a secure token:
```bash
openssl rand -base64 32
```
### 3. Start the Server
```bash
# Using the deployment script
./scripts/deploy-http.sh
# Or manually
MCP_MODE=http npm start
```
The server will start on `http://0.0.0.0:3000`
### 4. Setup HTTPS Proxy (Recommended)
#### Using nginx:
```nginx
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
#### Using Caddy:
```caddyfile
your-domain.com {
reverse_proxy localhost:3000
}
```
## Client Setup
### 1. Install mcp-remote
```bash
npm install -g mcp-remote
```
### 2. Configure Claude Desktop
Edit Claude Desktop config:
```json
{
"mcpServers": {
"n8n-documentation": {
"command": "mcp-remote",
"args": [
"https://your-domain.com/mcp",
"--header",
"Authorization: Bearer your-secure-token-here"
]
}
}
}
```
### 3. Test Connection
1. Restart Claude Desktop
2. The MCP tools should be available
3. Test with: "List all n8n nodes"
## Security Considerations
1. **Always use HTTPS** in production
2. **Keep AUTH_TOKEN secret** - treat it like a password
3. **Firewall rules** - Only expose necessary ports
4. **Regular updates** - Keep dependencies updated
5. **Monitor logs** - Check for unauthorized access attempts
## Health Monitoring
Check server health:
```bash
curl https://your-domain.com/health
```
Expected response:
```json
{
"status": "ok",
"mode": "http",
"version": "2.3.0"
}
```
## Troubleshooting
### Connection Refused
- Check firewall rules
- Verify server is running
- Check nginx/proxy configuration
### Authentication Failed
- Verify AUTH_TOKEN matches in both server and client
- Check Authorization header format
### MCP Tools Not Available
- Restart Claude Desktop
- Check mcp-remote installation
- Verify server logs for errors
## Performance Tips
1. Use a VPS with good network connectivity
2. Enable gzip compression in your proxy
3. Consider using PM2 for process management:
```bash
pm2 start npm --name "n8n-mcp" -- run start:http
```
## Example Systemd Service
Create `/etc/systemd/system/n8n-mcp.service`:
```ini
[Unit]
Description=n8n MCP HTTP Server
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/n8n-mcp
ExecStart=/usr/bin/npm run start:http
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl enable n8n-mcp
sudo systemctl start n8n-mcp
```
## Limitations
- Single-user design (no multi-tenancy)
- Stateless (no session persistence)
- No built-in rate limiting
- Basic token authentication only
For multi-user deployments, consider implementing a proper API gateway with user management.

10
package-lock.json generated
View File

@@ -11,7 +11,6 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"@n8n/n8n-nodes-langchain": "^1.0.0",
"@types/better-sqlite3": "^7.6.13",
"better-sqlite3": "^11.10.0",
"dotenv": "^16.5.0",
"express": "^5.1.0",
@@ -13151,15 +13150,6 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",

View File

@@ -9,7 +9,10 @@
"validate": "node dist/scripts/validate.js",
"test-nodes": "node dist/scripts/test-nodes.js",
"start": "node dist/mcp/index.js",
"start:http": "MCP_MODE=http node dist/mcp/index.js",
"http": "npm run build && npm run start:http",
"dev": "npm run build && npm run rebuild && npm run validate",
"dev:http": "MCP_MODE=http nodemon --watch src --ext ts --exec 'npm run build && npm run start:http'",
"test": "jest",
"lint": "tsc --noEmit",
"typecheck": "tsc --noEmit",

41
scripts/deploy-http.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Simple deployment script for n8n-MCP HTTP server
# For private, single-user deployments only
set -e
echo "n8n-MCP HTTP Deployment Script"
echo "=============================="
echo ""
# Check if .env exists
if [ ! -f .env ]; then
echo "Creating .env file..."
cp .env.example .env
echo ""
echo "⚠️ Please edit .env file and set:"
echo " - AUTH_TOKEN (generate with: openssl rand -base64 32)"
echo " - MCP_MODE=http"
echo " - PORT (default 3000)"
echo ""
exit 1
fi
# Check if AUTH_TOKEN is set
if ! grep -q "AUTH_TOKEN=.*[a-zA-Z0-9]" .env; then
echo "ERROR: AUTH_TOKEN not set in .env file"
echo "Generate one with: openssl rand -base64 32"
exit 1
fi
# Build and start
echo "Building project..."
npm run build
echo ""
echo "Starting HTTP server..."
echo "Use Ctrl+C to stop"
echo ""
# Start with production settings
NODE_ENV=production npm run start:http

117
src/http-server.ts Normal file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env node
/**
* Minimal HTTP server for n8n-MCP
* Single-user, stateless design for private deployments
*/
import express from 'express';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { N8NDocumentationMCPServer } from './mcp/server-update';
import { logger } from './utils/logger';
import dotenv from 'dotenv';
dotenv.config();
export async function startHTTPServer() {
const app = express();
app.use(express.json({ limit: '10mb' }));
// Simple token auth
const authToken = process.env.AUTH_TOKEN;
if (!authToken) {
logger.error('AUTH_TOKEN environment variable required');
console.error('ERROR: AUTH_TOKEN environment variable is required for HTTP mode');
console.error('Generate one with: openssl rand -base64 32');
process.exit(1);
}
// Request logging middleware
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('user-agent')
});
next();
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
mode: 'http',
version: '2.3.0'
});
});
// Main MCP endpoint - Create a new server and transport for each request (stateless)
app.post('/mcp', async (req: express.Request, res: express.Response): Promise<void> => {
// Simple auth check
const authHeader = req.headers.authorization;
const token = authHeader?.startsWith('Bearer ')
? authHeader.slice(7)
: authHeader;
if (token !== authToken) {
logger.warn('Authentication failed', { ip: req.ip });
res.status(401).json({ error: 'Unauthorized' });
return;
}
// Create new instances for each request (stateless)
const mcpServer = new N8NDocumentationMCPServer();
try {
// Create a stateless transport
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // Stateless mode
});
// Connect server to transport
await mcpServer.connect(transport);
// Handle the request
await transport.handleRequest(req, res, req.body);
// Clean up on close
res.on('close', () => {
logger.debug('Request closed, cleaning up');
transport.close();
});
} catch (error) {
logger.error('MCP request error:', error);
if (!res.headersSent) {
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined
});
}
}
});
// Error handler
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.error('Request error:', err);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
const port = parseInt(process.env.PORT || '3000');
const host = process.env.HOST || '0.0.0.0';
app.listen(port, host, () => {
logger.info(`n8n MCP HTTP Server started`, { port, host });
console.log(`n8n MCP HTTP Server running on ${host}:${port}`);
console.log(`Health check: http://localhost:${port}/health`);
console.log(`MCP endpoint: http://localhost:${port}/mcp`);
});
}
// Start if called directly
if (require.main === module) {
startHTTPServer().catch(error => {
logger.error('Failed to start HTTP server:', error);
console.error('Failed to start HTTP server:', error);
process.exit(1);
});
}

View File

@@ -18,13 +18,21 @@ process.on('unhandledRejection', (reason, promise) => {
async function main() {
try {
console.error('Starting n8n Documentation MCP Server...');
const mode = process.env.MCP_MODE || 'stdio';
console.error(`Starting n8n Documentation MCP Server in ${mode} mode...`);
console.error('Current directory:', process.cwd());
console.error('Script directory:', __dirname);
console.error('Node version:', process.version);
const server = new N8NDocumentationMCPServer();
await server.run();
if (mode === 'http') {
// HTTP mode - for remote deployment
const { startHTTPServer } = await import('../http-server');
await startHTTPServer();
} else {
// Stdio mode - for local Claude Desktop
const server = new N8NDocumentationMCPServer();
await server.run();
}
} catch (error) {
console.error('Failed to start MCP server:', error);
logger.error('Failed to start MCP server', error);

View File

@@ -150,7 +150,9 @@ export class N8NDocumentationMCPServer {
}
}
private listNodes(filters: any = {}): any {
private async listNodes(filters: any = {}): Promise<any> {
await this.ensureInitialized();
let query = 'SELECT * FROM nodes WHERE 1=1';
const params: any[] = [];
@@ -199,7 +201,8 @@ export class N8NDocumentationMCPServer {
};
}
private getNodeInfo(nodeType: string): any {
private async getNodeInfo(nodeType: string): Promise<any> {
await this.ensureInitialized();
if (!this.repository) throw new Error('Repository not initialized');
let node = this.repository.getNode(nodeType);
@@ -228,7 +231,8 @@ export class N8NDocumentationMCPServer {
return node;
}
private searchNodes(query: string, limit: number = 20): any {
private async searchNodes(query: string, limit: number = 20): Promise<any> {
await this.ensureInitialized();
if (!this.db) throw new Error('Database not initialized');
// Simple search across multiple fields
const searchQuery = `%${query}%`;
@@ -273,7 +277,8 @@ export class N8NDocumentationMCPServer {
return 'low';
}
private listAITools(): any {
private async listAITools(): Promise<any> {
await this.ensureInitialized();
if (!this.repository) throw new Error('Repository not initialized');
const tools = this.repository.getAITools();
@@ -287,7 +292,8 @@ export class N8NDocumentationMCPServer {
};
}
private getNodeDocumentation(nodeType: string): any {
private async getNodeDocumentation(nodeType: string): Promise<any> {
await this.ensureInitialized();
if (!this.db) throw new Error('Database not initialized');
const node = this.db!.prepare(`
SELECT node_type, display_name, documentation
@@ -307,7 +313,8 @@ export class N8NDocumentationMCPServer {
};
}
private getDatabaseStatistics(): any {
private async getDatabaseStatistics(): Promise<any> {
await this.ensureInitialized();
if (!this.db) throw new Error('Database not initialized');
const stats = this.db!.prepare(`
SELECT
@@ -345,6 +352,15 @@ export class N8NDocumentationMCPServer {
};
}
// Add connect method to accept any transport
async connect(transport: any): Promise<void> {
await this.ensureInitialized();
await this.server.connect(transport);
logger.info('MCP Server connected', {
transportType: transport.constructor.name
});
}
async run(): Promise<void> {
// Ensure database is initialized before starting server
await this.ensureInitialized();