refactor: clean up file names and fix version management
- Renamed files to remove unnecessary suffixes: - tools-update.ts → tools.ts - server-update.ts → server.ts - http-server-fixed.ts → http-server.ts - Created version utility to read from package.json as single source of truth - Updated all imports across 21+ files - Removed legacy files: - src/http-server.ts (legacy HTTP server with known issues) - src/utils/n8n-client.ts (unused legacy API client) - Added n8n_diagnostic tool to help troubleshoot management tools visibility - Added script to sync package.runtime.json version - Fixed version mismatch issue (was hardcoded 2.4.1, now reads 2.7.0 from package.json) This addresses GitHub issue #5 regarding version mismatch and provides better diagnostics for users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,18 +24,18 @@ else
|
||||
echo "❌ dist/mcp/index.js not found - run 'npm run build'"
|
||||
fi
|
||||
|
||||
if [ -f "dist/mcp/server-update.js" ]; then
|
||||
echo "✅ dist/mcp/server-update.js exists"
|
||||
echo " Last modified: $(stat -f "%Sm" dist/mcp/server-update.js 2>/dev/null || stat -c "%y" dist/mcp/server-update.js 2>/dev/null)"
|
||||
if [ -f "dist/mcp/server.js" ]; then
|
||||
echo "✅ dist/mcp/server.js exists"
|
||||
echo " Last modified: $(stat -f "%Sm" dist/mcp/server.js 2>/dev/null || stat -c "%y" dist/mcp/server.js 2>/dev/null)"
|
||||
else
|
||||
echo "❌ dist/mcp/server-update.js not found"
|
||||
echo "❌ dist/mcp/server.js not found"
|
||||
fi
|
||||
|
||||
if [ -f "dist/mcp/tools-update.js" ]; then
|
||||
echo "✅ dist/mcp/tools-update.js exists"
|
||||
echo " Last modified: $(stat -f "%Sm" dist/mcp/tools-update.js 2>/dev/null || stat -c "%y" dist/mcp/tools-update.js 2>/dev/null)"
|
||||
if [ -f "dist/mcp/tools.js" ]; then
|
||||
echo "✅ dist/mcp/tools.js exists"
|
||||
echo " Last modified: $(stat -f "%Sm" dist/mcp/tools.js 2>/dev/null || stat -c "%y" dist/mcp/tools.js 2>/dev/null)"
|
||||
else
|
||||
echo "❌ dist/mcp/tools-update.js not found"
|
||||
echo "❌ dist/mcp/tools.js not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
@@ -52,12 +52,12 @@ echo ""
|
||||
|
||||
# Check tools in compiled code
|
||||
echo "5. Compiled tools check:"
|
||||
if [ -f "dist/mcp/tools-update.js" ]; then
|
||||
TOOL_COUNT=$(grep "name: '" dist/mcp/tools-update.js | wc -l | tr -d ' ')
|
||||
if [ -f "dist/mcp/tools.js" ]; then
|
||||
TOOL_COUNT=$(grep "name: '" dist/mcp/tools.js | wc -l | tr -d ' ')
|
||||
echo " Total tools found: $TOOL_COUNT"
|
||||
echo " New tools present:"
|
||||
for tool in "get_node_for_task" "validate_node_config" "get_property_dependencies" "list_tasks" "search_node_properties" "get_node_essentials"; do
|
||||
if grep -q "name: '$tool'" dist/mcp/tools-update.js; then
|
||||
if grep -q "name: '$tool'" dist/mcp/tools.js; then
|
||||
echo " ✅ $tool"
|
||||
else
|
||||
echo " ❌ $tool"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"start": "node dist/mcp/index.js",
|
||||
"start:http": "MCP_MODE=http node dist/mcp/index.js",
|
||||
"start:http:fixed": "MCP_MODE=http USE_FIXED_HTTP=true node dist/mcp/index.js",
|
||||
"start:http:legacy": "MCP_MODE=http node dist/http-server.js",
|
||||
"http": "npm run build && npm run start:http:fixed",
|
||||
"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'",
|
||||
@@ -40,7 +39,8 @@
|
||||
"test:update-partial:debug": "node dist/scripts/test-update-partial-debug.js",
|
||||
"db:rebuild": "node dist/scripts/rebuild-database.js",
|
||||
"db:init": "node -e \"new (require('./dist/services/sqlite-storage-service').SQLiteStorageService)(); console.log('Database initialized')\"",
|
||||
"docs:rebuild": "ts-node src/scripts/rebuild-database.ts"
|
||||
"docs:rebuild": "ts-node src/scripts/rebuild-database.ts",
|
||||
"sync:runtime-version": "node scripts/sync-runtime-version.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Debug the essentials implementation
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server');
|
||||
const { PropertyFilter } = require('../dist/services/property-filter');
|
||||
const { ExampleGenerator } = require('../dist/services/example-generator');
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Debug script to check node data structure
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server');
|
||||
|
||||
async function debugNode() {
|
||||
console.log('🔍 Debugging node data\n');
|
||||
|
||||
40
scripts/sync-runtime-version.js
Executable file
40
scripts/sync-runtime-version.js
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Sync version from package.json to package.runtime.json
|
||||
* This ensures both files always have the same version
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||
const packageRuntimePath = path.join(__dirname, '..', 'package.runtime.json');
|
||||
|
||||
try {
|
||||
// Read package.json
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
const version = packageJson.version;
|
||||
|
||||
// Read package.runtime.json
|
||||
const packageRuntime = JSON.parse(fs.readFileSync(packageRuntimePath, 'utf-8'));
|
||||
|
||||
// Update version if different
|
||||
if (packageRuntime.version !== version) {
|
||||
packageRuntime.version = version;
|
||||
|
||||
// Write back with proper formatting
|
||||
fs.writeFileSync(
|
||||
packageRuntimePath,
|
||||
JSON.stringify(packageRuntime, null, 2) + '\n',
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
console.log(`✅ Updated package.runtime.json version to ${version}`);
|
||||
} else {
|
||||
console.log(`✓ package.runtime.json already at version ${version}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error syncing version:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Direct test of the server functionality without MCP protocol
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server');
|
||||
|
||||
async function testDirect() {
|
||||
console.log('🧪 Direct server test\n');
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 4. Tests the property search functionality
|
||||
*/
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server-update';
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Final validation test
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server');
|
||||
|
||||
const colors = {
|
||||
green: '\x1b[32m',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Test get_node_info to diagnose timeout issues
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server');
|
||||
|
||||
async function testNodeInfo() {
|
||||
console.log('🔍 Testing get_node_info...\n');
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Fixed HTTP server for n8n-MCP that properly handles StreamableHTTPServerTransport initialization
|
||||
* This implementation ensures the transport is properly initialized before handling requests
|
||||
*/
|
||||
import express from 'express';
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { n8nDocumentationToolsFinal } from './mcp/tools-update';
|
||||
import { N8NDocumentationMCPServer } from './mcp/server-update';
|
||||
import { logger } from './utils/logger';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
let expressServer: any;
|
||||
|
||||
/**
|
||||
* Validate required environment variables
|
||||
*/
|
||||
function validateEnvironment() {
|
||||
const required = ['AUTH_TOKEN'];
|
||||
const missing = required.filter(key => !process.env[key]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
logger.error(`Missing required environment variables: ${missing.join(', ')}`);
|
||||
console.error(`ERROR: Missing required environment variables: ${missing.join(', ')}`);
|
||||
console.error('Generate AUTH_TOKEN with: openssl rand -base64 32');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.AUTH_TOKEN && process.env.AUTH_TOKEN.length < 32) {
|
||||
logger.warn('AUTH_TOKEN should be at least 32 characters for security');
|
||||
console.warn('WARNING: AUTH_TOKEN should be at least 32 characters for security');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Graceful shutdown handler
|
||||
*/
|
||||
async function shutdown() {
|
||||
logger.info('Shutting down HTTP server...');
|
||||
console.log('Shutting down HTTP server...');
|
||||
|
||||
if (expressServer) {
|
||||
expressServer.close(() => {
|
||||
logger.info('HTTP server closed');
|
||||
console.log('HTTP server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logger.error('Forced shutdown after timeout');
|
||||
process.exit(1);
|
||||
}, 10000);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
export async function startFixedHTTPServer() {
|
||||
validateEnvironment();
|
||||
|
||||
const app = express();
|
||||
|
||||
// CRITICAL: Don't use any body parser - StreamableHTTPServerTransport needs raw stream
|
||||
|
||||
// Security headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
next();
|
||||
});
|
||||
|
||||
// CORS configuration
|
||||
app.use((req, res, next) => {
|
||||
const allowedOrigin = process.env.CORS_ORIGIN || '*';
|
||||
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept');
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// Request logging
|
||||
app.use((req, res, next) => {
|
||||
logger.info(`${req.method} ${req.path}`, {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
contentLength: req.get('content-length')
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
// Create a single persistent MCP server instance
|
||||
const mcpServer = new N8NDocumentationMCPServer();
|
||||
logger.info('Created persistent MCP server instance');
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
mode: 'http-fixed',
|
||||
version: '2.4.1',
|
||||
uptime: Math.floor(process.uptime()),
|
||||
memory: {
|
||||
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024),
|
||||
unit: 'MB'
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Version endpoint
|
||||
app.get('/version', (req, res) => {
|
||||
res.json({
|
||||
version: '2.4.1',
|
||||
buildTime: new Date().toISOString(),
|
||||
tools: n8nDocumentationToolsFinal.map(t => t.name),
|
||||
commit: process.env.GIT_COMMIT || 'unknown'
|
||||
});
|
||||
});
|
||||
|
||||
// Test tools endpoint
|
||||
app.get('/test-tools', async (req, res) => {
|
||||
try {
|
||||
const result = await mcpServer.executeTool('get_node_essentials', { nodeType: 'nodes-base.httpRequest' });
|
||||
res.json({ status: 'ok', hasData: !!result, toolCount: n8nDocumentationToolsFinal.length });
|
||||
} catch (error) {
|
||||
res.json({ status: 'error', message: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Main MCP endpoint - handle each request with custom transport handling
|
||||
app.post('/mcp', async (req: express.Request, res: express.Response): Promise<void> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Simple auth check
|
||||
const authHeader = req.headers.authorization;
|
||||
const token = authHeader?.startsWith('Bearer ')
|
||||
? authHeader.slice(7)
|
||||
: authHeader;
|
||||
|
||||
if (token !== process.env.AUTH_TOKEN) {
|
||||
logger.warn('Authentication failed', {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent')
|
||||
});
|
||||
res.status(401).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32001,
|
||||
message: 'Unauthorized'
|
||||
},
|
||||
id: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Instead of using StreamableHTTPServerTransport, we'll handle the request directly
|
||||
// This avoids the initialization issues with the transport
|
||||
|
||||
// Collect the raw body
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const jsonRpcRequest = JSON.parse(body);
|
||||
logger.debug('Received JSON-RPC request:', { method: jsonRpcRequest.method });
|
||||
|
||||
// Handle the request based on method
|
||||
let response;
|
||||
|
||||
switch (jsonRpcRequest.method) {
|
||||
case 'initialize':
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
protocolVersion: '1.0',
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {}
|
||||
},
|
||||
serverInfo: {
|
||||
name: 'n8n-documentation-mcp',
|
||||
version: '2.4.1'
|
||||
}
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
break;
|
||||
|
||||
case 'tools/list':
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
tools: n8nDocumentationToolsFinal
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
break;
|
||||
|
||||
case 'tools/call':
|
||||
// Delegate to the MCP server
|
||||
const toolName = jsonRpcRequest.params?.name;
|
||||
const toolArgs = jsonRpcRequest.params?.arguments || {};
|
||||
|
||||
try {
|
||||
const result = await mcpServer.executeTool(toolName, toolArgs);
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}
|
||||
]
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
} catch (error) {
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: `Error executing tool ${toolName}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32601,
|
||||
message: `Method not found: ${jsonRpcRequest.method}`
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
}
|
||||
|
||||
// Send response
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.json(response);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info('MCP request completed', {
|
||||
duration,
|
||||
method: jsonRpcRequest.method
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error);
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32700,
|
||||
message: 'Parse error',
|
||||
data: error instanceof Error ? error.message : 'Unknown error'
|
||||
},
|
||||
id: null
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('MCP request error:', error);
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: 'Internal server error',
|
||||
data: process.env.NODE_ENV === 'development'
|
||||
? (error as Error).message
|
||||
: undefined
|
||||
},
|
||||
id: null
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 404 handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: 'Not found',
|
||||
message: `Cannot ${req.method} ${req.path}`
|
||||
});
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.error('Express error handler:', err);
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: 'Internal server error',
|
||||
data: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||
},
|
||||
id: null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const port = parseInt(process.env.PORT || '3000');
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
|
||||
expressServer = app.listen(port, host, () => {
|
||||
logger.info(`n8n MCP Fixed HTTP Server started`, { port, host });
|
||||
console.log(`n8n MCP Fixed HTTP Server running on ${host}:${port}`);
|
||||
console.log(`Health check: http://localhost:${port}/health`);
|
||||
console.log(`MCP endpoint: http://localhost:${port}/mcp`);
|
||||
console.log('\nPress Ctrl+C to stop the server');
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
expressServer.on('error', (error: any) => {
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
logger.error(`Port ${port} is already in use`);
|
||||
console.error(`ERROR: Port ${port} is already in use`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
logger.error('Server error:', error);
|
||||
console.error('Server error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Graceful shutdown handlers
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
// Handle uncaught errors
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('Uncaught exception:', error);
|
||||
console.error('Uncaught exception:', error);
|
||||
shutdown();
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled rejection:', reason);
|
||||
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
||||
shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
// Make executeTool public on the server
|
||||
declare module './mcp/server-update' {
|
||||
interface N8NDocumentationMCPServer {
|
||||
executeTool(name: string, args: any): Promise<any>;
|
||||
}
|
||||
}
|
||||
|
||||
// Start if called directly
|
||||
if (require.main === module) {
|
||||
startFixedHTTPServer().catch(error => {
|
||||
logger.error('Failed to start Fixed HTTP server:', error);
|
||||
console.error('Failed to start Fixed HTTP server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import express from 'express';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import { N8NDocumentationMCPServer } from './mcp/server-update';
|
||||
import { N8NDocumentationMCPServer } from './mcp/server';
|
||||
import { ConsoleManager } from './utils/console-manager';
|
||||
import { logger } from './utils/logger';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Minimal HTTP server for n8n-MCP
|
||||
* Single-user, stateless design for private deployments
|
||||
* Fixed HTTP server for n8n-MCP that properly handles StreamableHTTPServerTransport initialization
|
||||
* This implementation ensures the transport is properly initialized before handling requests
|
||||
*/
|
||||
import express from 'express';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import { N8NDocumentationMCPServer } from './mcp/server-update';
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { n8nDocumentationToolsFinal } from './mcp/tools';
|
||||
import { N8NDocumentationMCPServer } from './mcp/server';
|
||||
import { logger } from './utils/logger';
|
||||
import { PROJECT_VERSION } from './utils/version';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
let server: any;
|
||||
let expressServer: any;
|
||||
|
||||
/**
|
||||
* Validate required environment variables
|
||||
@@ -27,7 +29,6 @@ function validateEnvironment() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate AUTH_TOKEN length
|
||||
if (process.env.AUTH_TOKEN && process.env.AUTH_TOKEN.length < 32) {
|
||||
logger.warn('AUTH_TOKEN should be at least 32 characters for security');
|
||||
console.warn('WARNING: AUTH_TOKEN should be at least 32 characters for security');
|
||||
@@ -41,14 +42,13 @@ async function shutdown() {
|
||||
logger.info('Shutting down HTTP server...');
|
||||
console.log('Shutting down HTTP server...');
|
||||
|
||||
if (server) {
|
||||
server.close(() => {
|
||||
if (expressServer) {
|
||||
expressServer.close(() => {
|
||||
logger.info('HTTP server closed');
|
||||
console.log('HTTP server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Force shutdown after 10 seconds
|
||||
setTimeout(() => {
|
||||
logger.error('Forced shutdown after timeout');
|
||||
process.exit(1);
|
||||
@@ -58,14 +58,12 @@ async function shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function startHTTPServer() {
|
||||
// Validate environment
|
||||
export async function startFixedHTTPServer() {
|
||||
validateEnvironment();
|
||||
|
||||
const app = express();
|
||||
|
||||
// DON'T parse JSON globally - StreamableHTTPServerTransport needs raw stream
|
||||
// Only parse for specific endpoints that need it
|
||||
// CRITICAL: Don't use any body parser - StreamableHTTPServerTransport needs raw stream
|
||||
|
||||
// Security headers
|
||||
app.use((req, res, next) => {
|
||||
@@ -76,13 +74,13 @@ export async function startHTTPServer() {
|
||||
next();
|
||||
});
|
||||
|
||||
// CORS configuration for mcp-remote compatibility
|
||||
// CORS configuration
|
||||
app.use((req, res, next) => {
|
||||
const allowedOrigin = process.env.CORS_ORIGIN || '*';
|
||||
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept');
|
||||
res.setHeader('Access-Control-Max-Age', '86400'); // 24 hours
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(204);
|
||||
@@ -91,7 +89,7 @@ export async function startHTTPServer() {
|
||||
next();
|
||||
});
|
||||
|
||||
// Request logging middleware
|
||||
// Request logging
|
||||
app.use((req, res, next) => {
|
||||
logger.info(`${req.method} ${req.path}`, {
|
||||
ip: req.ip,
|
||||
@@ -101,12 +99,16 @@ export async function startHTTPServer() {
|
||||
next();
|
||||
});
|
||||
|
||||
// Enhanced health check endpoint
|
||||
// Create a single persistent MCP server instance
|
||||
const mcpServer = new N8NDocumentationMCPServer();
|
||||
logger.info('Created persistent MCP server instance');
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
mode: 'http',
|
||||
version: '2.3.2',
|
||||
mode: 'http-fixed',
|
||||
version: PROJECT_VERSION,
|
||||
uptime: Math.floor(process.uptime()),
|
||||
memory: {
|
||||
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||
@@ -117,7 +119,27 @@ export async function startHTTPServer() {
|
||||
});
|
||||
});
|
||||
|
||||
// Main MCP endpoint - Create a new server and transport for each request (stateless)
|
||||
// Version endpoint
|
||||
app.get('/version', (req, res) => {
|
||||
res.json({
|
||||
version: PROJECT_VERSION,
|
||||
buildTime: new Date().toISOString(),
|
||||
tools: n8nDocumentationToolsFinal.map(t => t.name),
|
||||
commit: process.env.GIT_COMMIT || 'unknown'
|
||||
});
|
||||
});
|
||||
|
||||
// Test tools endpoint
|
||||
app.get('/test-tools', async (req, res) => {
|
||||
try {
|
||||
const result = await mcpServer.executeTool('get_node_essentials', { nodeType: 'nodes-base.httpRequest' });
|
||||
res.json({ status: 'ok', hasData: !!result, toolCount: n8nDocumentationToolsFinal.length });
|
||||
} catch (error) {
|
||||
res.json({ status: 'error', message: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Main MCP endpoint - handle each request with custom transport handling
|
||||
app.post('/mcp', async (req: express.Request, res: express.Response): Promise<void> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -143,38 +165,119 @@ export async function startHTTPServer() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new instances for each request (stateless)
|
||||
const mcpServer = new N8NDocumentationMCPServer();
|
||||
try {
|
||||
// Instead of using StreamableHTTPServerTransport, we'll handle the request directly
|
||||
// This avoids the initialization issues with the transport
|
||||
|
||||
// Collect the raw body
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const jsonRpcRequest = JSON.parse(body);
|
||||
logger.debug('Received JSON-RPC request:', { method: jsonRpcRequest.method });
|
||||
|
||||
// Handle the request based on method
|
||||
let response;
|
||||
|
||||
switch (jsonRpcRequest.method) {
|
||||
case 'initialize':
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
protocolVersion: '1.0',
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {}
|
||||
},
|
||||
serverInfo: {
|
||||
name: 'n8n-documentation-mcp',
|
||||
version: PROJECT_VERSION
|
||||
}
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
break;
|
||||
|
||||
case 'tools/list':
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
tools: n8nDocumentationToolsFinal
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
break;
|
||||
|
||||
case 'tools/call':
|
||||
// Delegate to the MCP server
|
||||
const toolName = jsonRpcRequest.params?.name;
|
||||
const toolArgs = jsonRpcRequest.params?.arguments || {};
|
||||
|
||||
try {
|
||||
// Create a stateless transport
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined, // Stateless mode
|
||||
});
|
||||
const result = await mcpServer.executeTool(toolName, toolArgs);
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}
|
||||
]
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
} catch (error) {
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: `Error executing tool ${toolName}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
// Connect server to transport
|
||||
await mcpServer.connect(transport);
|
||||
default:
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32601,
|
||||
message: `Method not found: ${jsonRpcRequest.method}`
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
}
|
||||
|
||||
// Handle the request - Fixed: removed third parameter
|
||||
await transport.handleRequest(req, res);
|
||||
// Send response
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.json(response);
|
||||
|
||||
// Log request duration
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info('MCP request completed', {
|
||||
duration
|
||||
duration,
|
||||
method: jsonRpcRequest.method
|
||||
});
|
||||
|
||||
// Clean up on close
|
||||
res.on('close', () => {
|
||||
logger.debug('Request closed, cleaning up');
|
||||
transport.close().catch(err =>
|
||||
logger.error('Error closing transport:', err)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Error processing request:', error);
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32700,
|
||||
message: 'Parse error',
|
||||
data: error instanceof Error ? error.message : 'Unknown error'
|
||||
},
|
||||
id: null
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('MCP request error:', error);
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error('MCP request failed', { duration });
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
@@ -220,16 +323,16 @@ export async function startHTTPServer() {
|
||||
const port = parseInt(process.env.PORT || '3000');
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
|
||||
server = app.listen(port, host, () => {
|
||||
logger.info(`n8n MCP HTTP Server started`, { port, host });
|
||||
console.log(`n8n MCP HTTP Server running on ${host}:${port}`);
|
||||
expressServer = app.listen(port, host, () => {
|
||||
logger.info(`n8n MCP Fixed HTTP Server started`, { port, host });
|
||||
console.log(`n8n MCP Fixed HTTP Server running on ${host}:${port}`);
|
||||
console.log(`Health check: http://localhost:${port}/health`);
|
||||
console.log(`MCP endpoint: http://localhost:${port}/mcp`);
|
||||
console.log('\nPress Ctrl+C to stop the server');
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
server.on('error', (error: any) => {
|
||||
expressServer.on('error', (error: any) => {
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
logger.error(`Port ${port} is already in use`);
|
||||
console.error(`ERROR: Port ${port} is already in use`);
|
||||
@@ -259,11 +362,18 @@ export async function startHTTPServer() {
|
||||
});
|
||||
}
|
||||
|
||||
// Make executeTool public on the server
|
||||
declare module './mcp/server' {
|
||||
interface N8NDocumentationMCPServer {
|
||||
executeTool(name: string, args: any): Promise<any>;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
startFixedHTTPServer().catch(error => {
|
||||
logger.error('Failed to start Fixed HTTP server:', error);
|
||||
console.error('Failed to start Fixed HTTP server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
export { N8NMCPEngine, EngineHealth, EngineOptions } from './mcp-engine';
|
||||
export { SingleSessionHTTPServer } from './http-server-single-session';
|
||||
export { ConsoleManager } from './utils/console-manager';
|
||||
export { N8NDocumentationMCPServer } from './mcp/server-update';
|
||||
export { N8NDocumentationMCPServer } from './mcp/server';
|
||||
|
||||
// Default export for convenience
|
||||
import N8NMCPEngine from './mcp-engine';
|
||||
|
||||
@@ -832,3 +832,107 @@ export async function handleListAvailableTools(): Promise<McpToolResponse> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handler: n8n_diagnostic
|
||||
export async function handleDiagnostic(request: any): Promise<McpToolResponse> {
|
||||
const verbose = request.params?.arguments?.verbose || false;
|
||||
|
||||
// Check environment variables
|
||||
const envVars = {
|
||||
N8N_API_URL: process.env.N8N_API_URL || null,
|
||||
N8N_API_KEY: process.env.N8N_API_KEY ? '***configured***' : null,
|
||||
NODE_ENV: process.env.NODE_ENV || 'production',
|
||||
MCP_MODE: process.env.MCP_MODE || 'stdio'
|
||||
};
|
||||
|
||||
// Check API configuration
|
||||
const apiConfigured = n8nApiConfig !== null;
|
||||
const apiClient = getN8nApiClient();
|
||||
|
||||
// Test API connectivity if configured
|
||||
let apiStatus = {
|
||||
configured: apiConfigured,
|
||||
connected: false,
|
||||
error: null as string | null,
|
||||
version: null as string | null
|
||||
};
|
||||
|
||||
if (apiClient) {
|
||||
try {
|
||||
const health = await apiClient.healthCheck();
|
||||
apiStatus.connected = true;
|
||||
apiStatus.version = health.n8nVersion || 'unknown';
|
||||
} catch (error) {
|
||||
apiStatus.error = error instanceof Error ? error.message : 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
// Check which tools are available
|
||||
const documentationTools = 22; // Base documentation tools
|
||||
const managementTools = apiConfigured ? 16 : 0;
|
||||
const totalTools = documentationTools + managementTools;
|
||||
|
||||
// Build diagnostic report
|
||||
const diagnostic: any = {
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: envVars,
|
||||
apiConfiguration: {
|
||||
configured: apiConfigured,
|
||||
status: apiStatus,
|
||||
config: apiConfigured && n8nApiConfig ? {
|
||||
baseUrl: n8nApiConfig.baseUrl,
|
||||
timeout: n8nApiConfig.timeout,
|
||||
maxRetries: n8nApiConfig.maxRetries
|
||||
} : null
|
||||
},
|
||||
toolsAvailability: {
|
||||
documentationTools: {
|
||||
count: documentationTools,
|
||||
enabled: true,
|
||||
description: 'Always available - node info, search, validation, etc.'
|
||||
},
|
||||
managementTools: {
|
||||
count: managementTools,
|
||||
enabled: apiConfigured,
|
||||
description: apiConfigured ?
|
||||
'Management tools are ENABLED - create, update, execute workflows' :
|
||||
'Management tools are DISABLED - configure N8N_API_URL and N8N_API_KEY to enable'
|
||||
},
|
||||
totalAvailable: totalTools
|
||||
},
|
||||
troubleshooting: {
|
||||
steps: apiConfigured ? [
|
||||
'API is configured and should work',
|
||||
'If tools are not showing in Claude Desktop:',
|
||||
'1. Restart Claude Desktop completely',
|
||||
'2. Check if using latest Docker image',
|
||||
'3. Verify environment variables are passed correctly',
|
||||
'4. Try running n8n_health_check to test connectivity'
|
||||
] : [
|
||||
'To enable management tools:',
|
||||
'1. Set N8N_API_URL environment variable (e.g., https://your-n8n-instance.com)',
|
||||
'2. Set N8N_API_KEY environment variable (get from n8n API settings)',
|
||||
'3. Restart the MCP server',
|
||||
'4. Management tools will automatically appear'
|
||||
],
|
||||
documentation: 'For detailed setup instructions, see: https://github.com/czlonkowski/n8n-mcp#n8n-management-tools-new-v260---requires-api-configuration'
|
||||
}
|
||||
};
|
||||
|
||||
// Add verbose debug info if requested
|
||||
if (verbose) {
|
||||
diagnostic['debug'] = {
|
||||
processEnv: Object.keys(process.env).filter(key =>
|
||||
key.startsWith('N8N_') || key.startsWith('MCP_')
|
||||
),
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
workingDirectory: process.cwd()
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: diagnostic
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { N8NDocumentationMCPServer } from './server-update';
|
||||
import { N8NDocumentationMCPServer } from './server';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// Add error details to stderr for Claude Desktop debugging
|
||||
@@ -35,7 +35,7 @@ async function main() {
|
||||
// Check if we should use the fixed implementation
|
||||
if (process.env.USE_FIXED_HTTP === 'true') {
|
||||
// Use the fixed HTTP implementation that bypasses StreamableHTTPServerTransport issues
|
||||
const { startFixedHTTPServer } = await import('../http-server-fixed');
|
||||
const { startFixedHTTPServer } = await import('../http-server');
|
||||
await startFixedHTTPServer();
|
||||
} else {
|
||||
// HTTP mode - for remote deployment with single-session architecture
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { n8nDocumentationToolsFinal } from './tools-update';
|
||||
import { n8nDocumentationToolsFinal } from './tools';
|
||||
import { n8nManagementTools } from './tools-n8n-manager';
|
||||
import { logger } from '../utils/logger';
|
||||
import { NodeRepository } from '../database/node-repository';
|
||||
@@ -24,6 +24,7 @@ import { WorkflowValidator } from '../services/workflow-validator';
|
||||
import { isN8nApiConfigured } from '../config/n8n-api';
|
||||
import * as n8nHandlers from './handlers-n8n-manager';
|
||||
import { handleUpdatePartialWorkflow } from './handlers-workflow-diff';
|
||||
import { PROJECT_VERSION } from '../utils/version';
|
||||
|
||||
interface NodeRow {
|
||||
node_type: string;
|
||||
@@ -121,7 +122,7 @@ export class N8NDocumentationMCPServer {
|
||||
},
|
||||
serverInfo: {
|
||||
name: 'n8n-documentation-mcp',
|
||||
version: '2.4.1',
|
||||
version: PROJECT_VERSION,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -261,6 +262,8 @@ export class N8NDocumentationMCPServer {
|
||||
return n8nHandlers.handleHealthCheck();
|
||||
case 'n8n_list_available_tools':
|
||||
return n8nHandlers.handleListAvailableTools();
|
||||
case 'n8n_diagnostic':
|
||||
return n8nHandlers.handleDiagnostic({ params: { arguments: args } });
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
@@ -40,7 +40,7 @@ console.count = () => {};
|
||||
console.countReset = () => {};
|
||||
|
||||
// Import and run the server AFTER suppressing output
|
||||
import { N8NDocumentationMCPServer } from './server-update';
|
||||
import { N8NDocumentationMCPServer } from './server';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
|
||||
@@ -484,5 +484,18 @@ Validation example:
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_diagnostic',
|
||||
description: `Diagnose n8n API configuration and management tools availability. Shows current configuration status, which tools are enabled/disabled, and helps troubleshoot why management tools might not be appearing. Returns detailed diagnostic information including environment variables, API connectivity, and tool registration status.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
verbose: {
|
||||
type: 'boolean',
|
||||
description: 'Include detailed debug information (default: false)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { createDatabaseAdapter } from '../database/database-adapter';
|
||||
import { NodeRepository } from '../database/node-repository';
|
||||
import { N8NDocumentationMCPServer } from '../mcp/server-update';
|
||||
import { N8NDocumentationMCPServer } from '../mcp/server';
|
||||
import { Logger } from '../utils/logger';
|
||||
|
||||
const logger = new Logger({ prefix: '[TestMCPTools]' });
|
||||
|
||||
@@ -4,11 +4,6 @@ export interface MCPServerConfig {
|
||||
authToken?: string;
|
||||
}
|
||||
|
||||
export interface N8NConfig {
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export interface ToolDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import { N8NConfig } from '../types';
|
||||
|
||||
export class N8NApiClient {
|
||||
private config: N8NConfig;
|
||||
private headers: Record<string, string>;
|
||||
|
||||
constructor(config: N8NConfig) {
|
||||
this.config = config;
|
||||
this.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-N8N-API-KEY': config.apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
private async request(endpoint: string, options: RequestInit = {}): Promise<any> {
|
||||
const url = `${this.config.apiUrl}/api/v1${endpoint}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...this.headers,
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`n8n API error: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to connect to n8n: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Workflow operations
|
||||
async getWorkflows(filters?: { active?: boolean; tags?: string[] }): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
if (filters?.active !== undefined) {
|
||||
query.append('active', filters.active.toString());
|
||||
}
|
||||
if (filters?.tags?.length) {
|
||||
query.append('tags', filters.tags.join(','));
|
||||
}
|
||||
|
||||
return this.request(`/workflows${query.toString() ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
async getWorkflow(id: string): Promise<any> {
|
||||
return this.request(`/workflows/${id}`);
|
||||
}
|
||||
|
||||
async createWorkflow(workflowData: any): Promise<any> {
|
||||
return this.request('/workflows', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(workflowData),
|
||||
});
|
||||
}
|
||||
|
||||
async updateWorkflow(id: string, updates: any): Promise<any> {
|
||||
return this.request(`/workflows/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteWorkflow(id: string): Promise<any> {
|
||||
return this.request(`/workflows/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async activateWorkflow(id: string): Promise<any> {
|
||||
return this.request(`/workflows/${id}/activate`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
async deactivateWorkflow(id: string): Promise<any> {
|
||||
return this.request(`/workflows/${id}/deactivate`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
// Execution operations
|
||||
async executeWorkflow(id: string, data?: any): Promise<any> {
|
||||
return this.request(`/workflows/${id}/execute`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
}
|
||||
|
||||
async getExecutions(filters?: {
|
||||
workflowId?: string;
|
||||
status?: string;
|
||||
limit?: number
|
||||
}): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
if (filters?.workflowId) {
|
||||
query.append('workflowId', filters.workflowId);
|
||||
}
|
||||
if (filters?.status) {
|
||||
query.append('status', filters.status);
|
||||
}
|
||||
if (filters?.limit) {
|
||||
query.append('limit', filters.limit.toString());
|
||||
}
|
||||
|
||||
return this.request(`/executions${query.toString() ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
async getExecution(id: string): Promise<any> {
|
||||
return this.request(`/executions/${id}`);
|
||||
}
|
||||
|
||||
async deleteExecution(id: string): Promise<any> {
|
||||
return this.request(`/executions/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// Credential operations
|
||||
async getCredentialTypes(): Promise<any> {
|
||||
return this.request('/credential-types');
|
||||
}
|
||||
|
||||
async getCredentials(): Promise<any> {
|
||||
return this.request('/credentials');
|
||||
}
|
||||
|
||||
// Node operations
|
||||
async getNodeTypes(): Promise<any> {
|
||||
return this.request('/node-types');
|
||||
}
|
||||
|
||||
async getNodeType(nodeType: string): Promise<any> {
|
||||
return this.request(`/node-types/${nodeType}`);
|
||||
}
|
||||
|
||||
// Extended methods for node source extraction
|
||||
async getNodeSourceCode(nodeType: string): Promise<any> {
|
||||
// This is a special endpoint we'll need to handle differently
|
||||
// as n8n doesn't expose source code directly through API
|
||||
// We'll need to implement this through file system access
|
||||
throw new Error('Node source code extraction requires special implementation');
|
||||
}
|
||||
|
||||
async getNodeDescription(nodeType: string): Promise<any> {
|
||||
try {
|
||||
const nodeTypeData = await this.getNodeType(nodeType);
|
||||
return {
|
||||
name: nodeTypeData.name,
|
||||
displayName: nodeTypeData.displayName,
|
||||
description: nodeTypeData.description,
|
||||
version: nodeTypeData.version,
|
||||
defaults: nodeTypeData.defaults,
|
||||
inputs: nodeTypeData.inputs,
|
||||
outputs: nodeTypeData.outputs,
|
||||
properties: nodeTypeData.properties,
|
||||
credentials: nodeTypeData.credentials,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get node description: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/utils/version.ts
Normal file
19
src/utils/version.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Get the project version from package.json
|
||||
* This ensures we have a single source of truth for versioning
|
||||
*/
|
||||
function getProjectVersion(): string {
|
||||
try {
|
||||
const packageJsonPath = join(__dirname, '../../package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
return packageJson.version || '0.0.0';
|
||||
} catch (error) {
|
||||
console.error('Failed to read version from package.json:', error);
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
export const PROJECT_VERSION = getProjectVersion();
|
||||
Reference in New Issue
Block a user