- Add intelligent URL detection supporting BASE_URL, PUBLIC_URL, and proxy headers - Fix hardcoded localhost URLs in server console output - Add hostname validation to prevent host header injection attacks - Restrict URL schemes to http/https only (block javascript:, file://, etc.) - Remove sensitive environment data from API responses - Add GET endpoints (/, /mcp) for better API discovery - Fix version inconsistency between server implementations - Update HTTP bridge to use HOST/PORT environment variables - Add comprehensive test scripts for URL configuration and security This resolves issues #41 and #42 by making the HTTP server properly handle deployment behind reverse proxies and adds critical security validations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.6 KiB
JavaScript
Executable File
107 lines
2.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* HTTP-to-stdio bridge for n8n-MCP
|
|
* Connects to n8n-MCP HTTP server and bridges stdio communication
|
|
*/
|
|
|
|
const http = require('http');
|
|
const readline = require('readline');
|
|
|
|
// Use MCP_URL from environment or construct from HOST/PORT if available
|
|
const defaultHost = process.env.HOST || 'localhost';
|
|
const defaultPort = process.env.PORT || '3000';
|
|
const MCP_URL = process.env.MCP_URL || `http://${defaultHost}:${defaultPort}/mcp`;
|
|
const AUTH_TOKEN = process.env.AUTH_TOKEN || process.argv[2];
|
|
|
|
if (!AUTH_TOKEN) {
|
|
console.error('Error: AUTH_TOKEN environment variable or first argument required');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Parse URL
|
|
const url = new URL(MCP_URL);
|
|
|
|
// Create readline interface for stdio
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
terminal: false
|
|
});
|
|
|
|
// Buffer for incomplete JSON messages
|
|
let buffer = '';
|
|
|
|
// Handle incoming stdio messages
|
|
rl.on('line', async (line) => {
|
|
try {
|
|
const message = JSON.parse(line);
|
|
|
|
// Forward to HTTP server
|
|
const options = {
|
|
hostname: url.hostname,
|
|
port: url.port || 80,
|
|
path: url.pathname,
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${AUTH_TOKEN}`,
|
|
'Accept': 'application/json, text/event-stream'
|
|
}
|
|
};
|
|
|
|
const req = http.request(options, (res) => {
|
|
let responseData = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
responseData += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
// Try to parse as JSON
|
|
const response = JSON.parse(responseData);
|
|
console.log(JSON.stringify(response));
|
|
} catch (e) {
|
|
// Handle SSE format
|
|
const lines = responseData.split('\n');
|
|
for (const line of lines) {
|
|
if (line.startsWith('data: ')) {
|
|
try {
|
|
const data = JSON.parse(line.substring(6));
|
|
console.log(JSON.stringify(data));
|
|
} catch (e) {
|
|
// Ignore parse errors
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
console.error(JSON.stringify({
|
|
jsonrpc: '2.0',
|
|
error: {
|
|
code: -32000,
|
|
message: `HTTP request failed: ${error.message}`
|
|
},
|
|
id: message.id || null
|
|
}));
|
|
});
|
|
|
|
req.write(JSON.stringify(message));
|
|
req.end();
|
|
} catch (error) {
|
|
// Not valid JSON, ignore
|
|
}
|
|
});
|
|
|
|
// Handle process termination
|
|
process.on('SIGTERM', () => {
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
process.exit(0);
|
|
}); |