feat: add HTTP server mode for remote deployment with token auth
This commit is contained in:
25
.env.example
25
.env.example
@@ -29,22 +29,17 @@ MCP_SERVER_HOST=localhost
|
|||||||
# MCP_AUTH_TOKEN=optional-for-local-development
|
# 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
|
# Server mode: stdio (local) or http (remote)
|
||||||
MCP_PORT=3000
|
MCP_MODE=stdio
|
||||||
MCP_HOST=0.0.0.0
|
|
||||||
MCP_DOMAIN=localhost
|
|
||||||
|
|
||||||
# Authentication (REQUIRED for production HTTP mode)
|
# HTTP Server Configuration (only used when MCP_MODE=http)
|
||||||
# Generate a secure token: openssl rand -hex 32
|
PORT=3000
|
||||||
# MCP_AUTH_TOKEN=your-secure-auth-token-here
|
HOST=0.0.0.0
|
||||||
|
|
||||||
# CORS - Enable for browser-based access
|
# Authentication token for HTTP mode (REQUIRED)
|
||||||
MCP_CORS=false
|
# Generate with: openssl rand -base64 32
|
||||||
|
AUTH_TOKEN=your-secure-token-here
|
||||||
# TLS Configuration (optional for HTTPS)
|
|
||||||
# MCP_TLS_CERT=/path/to/cert.pem
|
|
||||||
# MCP_TLS_KEY=/path/to/key.pem
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [2.3.0] - 2024-12-06
|
## [2.3.0] - 2024-12-06
|
||||||
|
|
||||||
### Added
|
### 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
|
- **Universal Node.js Compatibility**: Automatic database adapter fallback system
|
||||||
- Primary adapter: `better-sqlite3` for optimal performance
|
- Primary adapter: `better-sqlite3` for optimal performance
|
||||||
- Fallback adapter: `sql.js` (pure JavaScript) for version mismatches
|
- 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`)
|
- Database adapter abstraction layer (`src/database/database-adapter.ts`)
|
||||||
- Version detection and logging for troubleshooting
|
- Version detection and logging for troubleshooting
|
||||||
- sql.js dependency for pure JavaScript SQLite implementation
|
- sql.js dependency for pure JavaScript SQLite implementation
|
||||||
|
- HTTP server implementation (`src/http-server.ts`)
|
||||||
|
- Deployment documentation and scripts
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updated all database operations to use the adapter interface
|
- Updated all database operations to use the adapter interface
|
||||||
- Removed Node.js v20.17.0 requirement - now works with ANY version
|
- Removed Node.js v20.17.0 requirement - now works with ANY version
|
||||||
- Simplified Claude Desktop setup - no wrapper scripts needed
|
- Simplified Claude Desktop setup - no wrapper scripts needed
|
||||||
- Enhanced error messages for database initialization
|
- Enhanced error messages for database initialization
|
||||||
|
- Made all MCP tool handlers async for proper initialization
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- NODE_MODULE_VERSION mismatch errors with Claude Desktop
|
- NODE_MODULE_VERSION mismatch errors with Claude Desktop
|
||||||
- Native module compilation issues in restricted environments
|
- Native module compilation issues in restricted environments
|
||||||
- Compatibility issues when running with different Node.js versions
|
- Compatibility issues when running with different Node.js versions
|
||||||
|
- Database initialization race conditions in HTTP mode
|
||||||
|
|
||||||
### Technical Details
|
### Technical Details
|
||||||
- Better-sqlite3: ~10-50x faster (when compatible)
|
- Better-sqlite3: ~10-50x faster (when compatible)
|
||||||
- sql.js: ~2-5x slower but universally compatible
|
- sql.js: ~2-5x slower but universally compatible
|
||||||
- Both adapters maintain identical API and functionality
|
- Both adapters maintain identical API and functionality
|
||||||
- Automatic persistence for sql.js with 100ms debounced saves
|
- Automatic persistence for sql.js with 100ms debounced saves
|
||||||
|
- HTTP server uses StreamableHTTPServerTransport for MCP compatibility
|
||||||
|
|
||||||
## [2.2.0] - 2024-12-06
|
## [2.2.0] - 2024-12-06
|
||||||
|
|
||||||
|
|||||||
48
CLAUDE.md
48
CLAUDE.md
@@ -72,13 +72,19 @@ npm run rebuild # Rebuild node database
|
|||||||
npm run validate # Validate critical nodes
|
npm run validate # Validate critical nodes
|
||||||
npm run test-nodes # Test critical node properties/operations
|
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):
|
# Legacy Commands (deprecated):
|
||||||
npm run db:rebuild # Old rebuild command
|
npm run db:rebuild # Old rebuild command
|
||||||
npm run db:init # Initialize empty database
|
npm run db:init # Initialize empty database
|
||||||
npm run docs:rebuild # Rebuild documentation from TypeScript source
|
npm run docs:rebuild # Rebuild documentation from TypeScript source
|
||||||
|
|
||||||
# Production
|
# 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
|
## High-Level Architecture
|
||||||
@@ -234,6 +240,46 @@ This project uses the Sustainable Use License. Key points:
|
|||||||
- ❌ Cannot host as a service without permission
|
- ❌ Cannot host as a service without permission
|
||||||
- ❌ Cannot include in commercial products 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
|
## Recent Problem Solutions
|
||||||
|
|
||||||
### SQLite Version Mismatch (Solved in v2.3)
|
### SQLite Version Mismatch (Solved in v2.3)
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -184,21 +184,45 @@ Current implementation achieves:
|
|||||||
- ✅ 35 AI-capable tools detected
|
- ✅ 35 AI-capable tools detected
|
||||||
- ✅ All critical nodes validated
|
- ✅ 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:
|
n8n-MCP now supports HTTP mode for remote deployments. This allows you to:
|
||||||
- Centralized hosting on cloud servers
|
- Host the MCP server on a cloud VPS
|
||||||
- Multiple users connecting to a single instance
|
- Connect from Claude Desktop using mcp-remote
|
||||||
- No local installation required
|
- Single-user design for private use
|
||||||
- Enterprise-ready authentication
|
- Simple token-based authentication
|
||||||
|
|
||||||
For detailed planning documents, see:
|
### Quick Start for HTTP Mode
|
||||||
- [HTTP Remote Deployment Plan](./HTTP_REMOTE_DEPLOYMENT_PLAN.md)
|
|
||||||
- [HTTP Implementation Guide](./HTTP_IMPLEMENTATION_GUIDE.md)
|
1. On your server:
|
||||||
- [HTTP Implementation Roadmap](./HTTP_IMPLEMENTATION_ROADMAP.md)
|
```bash
|
||||||
- [HTTP Remote Summary](./HTTP_REMOTE_SUMMARY.md)
|
# 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
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
214
docs/HTTP_DEPLOYMENT.md
Normal file
214
docs/HTTP_DEPLOYMENT.md
Normal 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
10
package-lock.json
generated
@@ -11,7 +11,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@n8n/n8n-nodes-langchain": "^1.0.0",
|
"@n8n/n8n-nodes-langchain": "^1.0.0",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
|
||||||
"better-sqlite3": "^11.10.0",
|
"better-sqlite3": "^11.10.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@@ -13151,15 +13150,6 @@
|
|||||||
"@babel/types": "^7.20.7"
|
"@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": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.6",
|
"version": "1.19.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
"validate": "node dist/scripts/validate.js",
|
"validate": "node dist/scripts/validate.js",
|
||||||
"test-nodes": "node dist/scripts/test-nodes.js",
|
"test-nodes": "node dist/scripts/test-nodes.js",
|
||||||
"start": "node dist/mcp/index.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": "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",
|
"test": "jest",
|
||||||
"lint": "tsc --noEmit",
|
"lint": "tsc --noEmit",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
|||||||
41
scripts/deploy-http.sh
Executable file
41
scripts/deploy-http.sh
Executable 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
117
src/http-server.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -18,13 +18,21 @@ process.on('unhandledRejection', (reason, promise) => {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
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('Current directory:', process.cwd());
|
||||||
console.error('Script directory:', __dirname);
|
|
||||||
console.error('Node version:', process.version);
|
console.error('Node version:', process.version);
|
||||||
|
|
||||||
const server = new N8NDocumentationMCPServer();
|
if (mode === 'http') {
|
||||||
await server.run();
|
// 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) {
|
} catch (error) {
|
||||||
console.error('Failed to start MCP server:', error);
|
console.error('Failed to start MCP server:', error);
|
||||||
logger.error('Failed to start MCP server', error);
|
logger.error('Failed to start MCP server', error);
|
||||||
|
|||||||
@@ -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';
|
let query = 'SELECT * FROM nodes WHERE 1=1';
|
||||||
const params: any[] = [];
|
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');
|
if (!this.repository) throw new Error('Repository not initialized');
|
||||||
let node = this.repository.getNode(nodeType);
|
let node = this.repository.getNode(nodeType);
|
||||||
|
|
||||||
@@ -228,7 +231,8 @@ export class N8NDocumentationMCPServer {
|
|||||||
return node;
|
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');
|
if (!this.db) throw new Error('Database not initialized');
|
||||||
// Simple search across multiple fields
|
// Simple search across multiple fields
|
||||||
const searchQuery = `%${query}%`;
|
const searchQuery = `%${query}%`;
|
||||||
@@ -273,7 +277,8 @@ export class N8NDocumentationMCPServer {
|
|||||||
return 'low';
|
return 'low';
|
||||||
}
|
}
|
||||||
|
|
||||||
private listAITools(): any {
|
private async listAITools(): Promise<any> {
|
||||||
|
await this.ensureInitialized();
|
||||||
if (!this.repository) throw new Error('Repository not initialized');
|
if (!this.repository) throw new Error('Repository not initialized');
|
||||||
const tools = this.repository.getAITools();
|
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');
|
if (!this.db) throw new Error('Database not initialized');
|
||||||
const node = this.db!.prepare(`
|
const node = this.db!.prepare(`
|
||||||
SELECT node_type, display_name, documentation
|
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');
|
if (!this.db) throw new Error('Database not initialized');
|
||||||
const stats = this.db!.prepare(`
|
const stats = this.db!.prepare(`
|
||||||
SELECT
|
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> {
|
async run(): Promise<void> {
|
||||||
// Ensure database is initialized before starting server
|
// Ensure database is initialized before starting server
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|||||||
Reference in New Issue
Block a user