From 7d6691cd4260c5222970dbcfc7faa27ff0a6b4ac Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Mon, 16 Jun 2025 00:31:04 +0200 Subject: [PATCH] refactor: remove legacy MCP implementation files and update tool descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed unused legacy files: - server.ts (legacy server with workflow management) - tools.ts (legacy tool definitions) - prompts.ts (unused prompt definitions) - resources.ts (unused resource definitions) - server-optimized.ts (unused alternative implementation) - Updated tools-update.ts with improved MCP tool descriptions: - Clear, action-oriented descriptions following MCP best practices - Better parameter documentation with examples - Emphasized documentation-only nature of the server - Added technical details (e.g., SQLite FTS5 for search) The active implementation now only uses server-update.ts and tools-update.ts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/mcp/prompts.ts | 73 ------ src/mcp/resources.ts | 40 ---- src/mcp/server-optimized.ts | 337 -------------------------- src/mcp/server.ts | 459 ------------------------------------ src/mcp/tools-update.ts | 37 +-- src/mcp/tools.ts | 253 -------------------- 6 files changed, 22 insertions(+), 1177 deletions(-) delete mode 100644 src/mcp/prompts.ts delete mode 100644 src/mcp/resources.ts delete mode 100644 src/mcp/server-optimized.ts delete mode 100644 src/mcp/server.ts delete mode 100644 src/mcp/tools.ts diff --git a/src/mcp/prompts.ts b/src/mcp/prompts.ts deleted file mode 100644 index 163e11c..0000000 --- a/src/mcp/prompts.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { PromptDefinition } from '../types'; - -export const n8nPrompts: PromptDefinition[] = [ - { - name: 'create_workflow_prompt', - description: 'Generate a prompt to create a new n8n workflow', - arguments: [ - { - name: 'description', - description: 'Description of what the workflow should do', - required: true, - }, - { - name: 'inputType', - description: 'Type of input the workflow expects', - required: false, - }, - { - name: 'outputType', - description: 'Type of output the workflow should produce', - required: false, - }, - ], - }, - { - name: 'debug_workflow_prompt', - description: 'Generate a prompt to debug an n8n workflow', - arguments: [ - { - name: 'workflowId', - description: 'ID of the workflow to debug', - required: true, - }, - { - name: 'errorMessage', - description: 'Error message or issue description', - required: false, - }, - ], - }, - { - name: 'optimize_workflow_prompt', - description: 'Generate a prompt to optimize an n8n workflow', - arguments: [ - { - name: 'workflowId', - description: 'ID of the workflow to optimize', - required: true, - }, - { - name: 'optimizationGoal', - description: 'What to optimize for (speed, reliability, cost)', - required: false, - }, - ], - }, - { - name: 'explain_workflow_prompt', - description: 'Generate a prompt to explain how a workflow works', - arguments: [ - { - name: 'workflowId', - description: 'ID of the workflow to explain', - required: true, - }, - { - name: 'audienceLevel', - description: 'Technical level of the audience (beginner, intermediate, expert)', - required: false, - }, - ], - }, -]; \ No newline at end of file diff --git a/src/mcp/resources.ts b/src/mcp/resources.ts deleted file mode 100644 index 606d0ae..0000000 --- a/src/mcp/resources.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ResourceDefinition } from '../types'; - -export const n8nResources: ResourceDefinition[] = [ - { - uri: 'workflow://active', - name: 'Active Workflows', - description: 'List of all active workflows in n8n', - mimeType: 'application/json', - }, - { - uri: 'workflow://all', - name: 'All Workflows', - description: 'List of all workflows in n8n', - mimeType: 'application/json', - }, - { - uri: 'execution://recent', - name: 'Recent Executions', - description: 'Recent workflow execution history', - mimeType: 'application/json', - }, - { - uri: 'credentials://types', - name: 'Credential Types', - description: 'Available credential types in n8n', - mimeType: 'application/json', - }, - { - uri: 'nodes://available', - name: 'Available Nodes', - description: 'List of all available n8n nodes', - mimeType: 'application/json', - }, - { - uri: 'nodes://source', - name: 'Node Source Code', - description: 'Source code of n8n nodes', - mimeType: 'text/javascript', - }, -]; \ No newline at end of file diff --git a/src/mcp/server-optimized.ts b/src/mcp/server-optimized.ts deleted file mode 100644 index f8e2ab0..0000000 --- a/src/mcp/server-optimized.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import { createDatabaseAdapter, DatabaseAdapter } from '../database/database-adapter'; -import { logger } from '../utils/logger'; - -interface OptimizedNode { - nodeType: string; - packageName: string; - displayName: string; - description: string; - category: string; - nodeSourceCode?: string; - credentialSourceCode?: string; - sourceLocation?: string; - properties?: any[]; - operations?: any[]; - documentation?: string; - isAITool?: boolean; - isTrigger?: boolean; -} - -/** - * Optimized MCP Server that reads everything from pre-built database - * No runtime dependency on n8n packages - */ -export class OptimizedMCPServer { - private server: Server; - private db: DatabaseAdapter | null = null; - private transport: StdioServerTransport; - - constructor() { - this.server = new Server( - { - name: 'n8n-mcp-optimized', - version: '1.0.0', - }, - { - capabilities: { - tools: {}, - }, - } - ); - - this.transport = new StdioServerTransport(); - this.setupHandlers(); - } - - private async initDatabase() { - const dbPath = process.env.NODE_DB_PATH || './data/nodes.db'; - this.db = await createDatabaseAdapter(dbPath); - logger.info('Database initialized'); - } - - private setupHandlers() { - // List available tools - this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: [ - { - name: 'list_nodes', - description: 'List all available n8n nodes with filtering options', - inputSchema: { - type: 'object', - properties: { - category: { type: 'string', description: 'Filter by category' }, - packageName: { type: 'string', description: 'Filter by package' }, - isAITool: { type: 'boolean', description: 'Filter AI-capable nodes' }, - isTrigger: { type: 'boolean', description: 'Filter trigger nodes' }, - limit: { type: 'number', description: 'Max results', default: 50 } - } - } - }, - { - name: 'get_node_info', - description: 'Get comprehensive information about a specific n8n node', - inputSchema: { - type: 'object', - properties: { - nodeType: { type: 'string', description: 'Node type identifier' } - }, - required: ['nodeType'] - } - }, - { - name: 'search_nodes', - description: 'Full-text search across all nodes', - inputSchema: { - type: 'object', - properties: { - query: { type: 'string', description: 'Search query' }, - limit: { type: 'number', description: 'Max results', default: 20 } - }, - required: ['query'] - } - }, - { - name: 'list_ai_tools', - description: 'List all AI-capable n8n nodes', - inputSchema: { - type: 'object', - properties: {} - } - }, - { - name: 'get_node_source', - description: 'Get source code for a specific node', - inputSchema: { - type: 'object', - properties: { - nodeType: { type: 'string', description: 'Node type identifier' } - }, - required: ['nodeType'] - } - }, - { - name: 'get_database_statistics', - description: 'Get statistics about the node database', - inputSchema: { - type: 'object', - properties: {} - } - } - ] - }; - }); - - // Handle tool calls - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - if (!this.db) { - await this.initDatabase(); - } - - try { - switch (name) { - case 'list_nodes': - return await this.listNodes(args); - case 'get_node_info': - return await this.getNodeInfo(args); - case 'search_nodes': - return await this.searchNodes(args); - case 'list_ai_tools': - return await this.listAITools(); - case 'get_node_source': - return await this.getNodeSource(args); - case 'get_database_statistics': - return await this.getDatabaseStatistics(); - default: - throw new Error(`Unknown tool: ${name}`); - } - } catch (error) { - logger.error(`Tool execution failed: ${name}`, error); - throw error; - } - }); - } - - private async listNodes(args: any) { - const conditions: string[] = ['1=1']; - const params: any[] = []; - - if (args.category) { - conditions.push('category = ?'); - params.push(args.category); - } - - if (args.packageName) { - conditions.push('package_name = ?'); - params.push(args.packageName); - } - - if (args.isAITool !== undefined) { - conditions.push('is_ai_tool = ?'); - params.push(args.isAITool ? 1 : 0); - } - - if (args.isTrigger !== undefined) { - conditions.push('is_trigger = ?'); - params.push(args.isTrigger ? 1 : 0); - } - - params.push(args.limit || 50); - - const query = ` - SELECT node_type, package_name, display_name, description, category, - is_ai_tool, is_trigger, is_webhook - FROM nodes - WHERE ${conditions.join(' AND ')} - LIMIT ? - `; - - const nodes = this.db!.prepare(query).all(...params); - - return { - nodes: nodes.map((n: any) => ({ - nodeType: n.node_type, - packageName: n.package_name, - displayName: n.display_name, - description: n.description, - category: n.category, - isAITool: n.is_ai_tool === 1, - isTrigger: n.is_trigger === 1, - isWebhook: n.is_webhook === 1 - })), - total: nodes.length - }; - } - - private async getNodeInfo(args: any) { - const query = ` - SELECT * FROM nodes WHERE node_type = ? - `; - - const node = this.db!.prepare(query).get(args.nodeType); - - if (!node) { - throw new Error(`Node ${args.nodeType} not found`); - } - - return { - nodeType: node.node_type, - packageName: node.package_name, - displayName: node.display_name, - description: node.description, - category: node.category, - developmentStyle: node.development_style, - isAITool: node.is_ai_tool === 1, - isTrigger: node.is_trigger === 1, - isWebhook: node.is_webhook === 1, - isVersioned: node.is_versioned === 1, - version: node.version, - documentation: node.documentation, - properties: JSON.parse(node.properties_schema || '[]'), - operations: JSON.parse(node.operations || '[]'), - credentialsRequired: JSON.parse(node.credentials_required || '[]'), - sourceExtractedAt: node.source_extracted_at - }; - } - - private async searchNodes(args: any) { - const query = ` - SELECT n.* FROM nodes n - JOIN nodes_fts ON n.rowid = nodes_fts.rowid - WHERE nodes_fts MATCH ? - LIMIT ? - `; - - const results = this.db!.prepare(query).all(args.query, args.limit || 20); - - return { - nodes: results.map((n: any) => ({ - nodeType: n.node_type, - displayName: n.display_name, - description: n.description, - category: n.category, - packageName: n.package_name, - relevance: n.rank - })), - total: results.length - }; - } - - private async listAITools() { - const query = ` - SELECT node_type, display_name, description, category, package_name - FROM nodes - WHERE is_ai_tool = 1 - ORDER BY display_name - `; - - const nodes = this.db!.prepare(query).all(); - - return { - aiTools: nodes.map((n: any) => ({ - nodeType: n.node_type, - displayName: n.display_name, - description: n.description, - category: n.category, - packageName: n.package_name - })), - total: nodes.length - }; - } - - private async getNodeSource(args: any) { - const query = ` - SELECT node_source_code, credential_source_code, source_location - FROM nodes - WHERE node_type = ? - `; - - const result = this.db!.prepare(query).get(args.nodeType); - - if (!result) { - throw new Error(`Node ${args.nodeType} not found`); - } - - return { - nodeType: args.nodeType, - sourceCode: result.node_source_code || 'Source code not available', - credentialCode: result.credential_source_code, - location: result.source_location - }; - } - - private async getDatabaseStatistics() { - const stats = { - totalNodes: this.db!.prepare('SELECT COUNT(*) as count FROM nodes').get().count, - aiTools: this.db!.prepare('SELECT COUNT(*) as count FROM nodes WHERE is_ai_tool = 1').get().count, - triggers: this.db!.prepare('SELECT COUNT(*) as count FROM nodes WHERE is_trigger = 1').get().count, - webhooks: this.db!.prepare('SELECT COUNT(*) as count FROM nodes WHERE is_webhook = 1').get().count, - withSource: this.db!.prepare('SELECT COUNT(*) as count FROM nodes WHERE node_source_code IS NOT NULL').get().count, - withDocs: this.db!.prepare('SELECT COUNT(*) as count FROM nodes WHERE documentation IS NOT NULL').get().count, - categories: this.db!.prepare('SELECT DISTINCT category FROM nodes').all().map((r: any) => r.category), - packages: this.db!.prepare('SELECT DISTINCT package_name FROM nodes').all().map((r: any) => r.package_name) - }; - - return stats; - } - - async start() { - await this.initDatabase(); - await this.server.connect(this.transport); - logger.info('Optimized MCP Server started'); - } -} - -// Start the server if run directly -if (require.main === module) { - const server = new OptimizedMCPServer(); - server.start().catch(console.error); -} \ No newline at end of file diff --git a/src/mcp/server.ts b/src/mcp/server.ts deleted file mode 100644 index f4ed06b..0000000 --- a/src/mcp/server.ts +++ /dev/null @@ -1,459 +0,0 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ListResourcesRequestSchema, - ListToolsRequestSchema, - ListPromptsRequestSchema, - GetPromptRequestSchema, - ReadResourceRequestSchema -} from '@modelcontextprotocol/sdk/types.js'; -import { MCPServerConfig, N8NConfig } from '../types'; -import { n8nTools } from './tools'; -import { n8nResources } from './resources'; -import { n8nPrompts } from './prompts'; -import { N8NApiClient } from '../utils/n8n-client'; -import { N8NMCPBridge } from '../utils/bridge'; -import { logger } from '../utils/logger'; -import { NodeSourceExtractor } from '../utils/node-source-extractor'; -import { NodeDocumentationService } from '../services/node-documentation-service'; - -export class N8NMCPServer { - private server: Server; - private n8nClient: N8NApiClient; - private nodeExtractor: NodeSourceExtractor; - private nodeDocService: NodeDocumentationService; - - constructor(config: MCPServerConfig, n8nConfig: N8NConfig) { - this.n8nClient = new N8NApiClient(n8nConfig); - this.nodeExtractor = new NodeSourceExtractor(); - this.nodeDocService = new NodeDocumentationService(); - logger.info('Initializing n8n MCP server', { config, n8nConfig }); - this.server = new Server( - { - name: 'n8n-mcp-server', - version: '1.0.0', - }, - { - capabilities: { - tools: {}, - resources: {}, - prompts: {}, - }, - } - ); - - this.setupHandlers(); - } - - private setupHandlers(): void { - // Handle tool listing - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: n8nTools, - })); - - // Handle tool execution - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - logger.debug(`Executing tool: ${name}`, { args }); - const result = await this.executeTool(name, args); - logger.debug(`Tool ${name} executed successfully`, { result }); - return { - content: [ - { - type: 'text', - text: JSON.stringify(result, null, 2), - }, - ], - }; - } catch (error) { - logger.error(`Error executing tool ${name}`, error); - return { - content: [ - { - type: 'text', - text: `Error executing tool ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - ], - isError: true, - }; - } - }); - - // Handle resource listing - this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ - resources: n8nResources, - })); - - // Handle resource reading - this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { - const { uri } = request.params; - - try { - logger.debug(`Reading resource: ${uri}`); - const content = await this.readResource(uri); - logger.debug(`Resource ${uri} read successfully`); - return { - contents: [ - { - uri, - mimeType: 'application/json', - text: JSON.stringify(content, null, 2), - }, - ], - }; - } catch (error) { - logger.error(`Failed to read resource ${uri}`, error); - throw new Error(`Failed to read resource ${uri}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - }); - - // Handle prompt listing - this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ - prompts: n8nPrompts, - })); - - // Handle prompt retrieval - this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - const prompt = n8nPrompts.find(p => p.name === name); - if (!prompt) { - throw new Error(`Prompt ${name} not found`); - } - - const promptText = await this.generatePrompt(name, args); - - return { - description: prompt.description, - messages: [ - { - role: 'user', - content: { - type: 'text', - text: promptText, - }, - }, - ], - }; - }); - } - - private async executeTool(name: string, args: any): Promise { - // Tool execution logic based on specific n8n operations - switch (name) { - case 'execute_workflow': - return this.executeWorkflow(args); - case 'list_workflows': - return this.listWorkflows(args); - case 'get_workflow': - return this.getWorkflow(args); - case 'create_workflow': - return this.createWorkflow(args); - case 'update_workflow': - return this.updateWorkflow(args); - case 'delete_workflow': - return this.deleteWorkflow(args); - case 'get_executions': - return this.getExecutions(args); - case 'get_execution_data': - return this.getExecutionData(args); - case 'get_node_source_code': - return this.getNodeSourceCode(args); - case 'list_available_nodes': - return this.listAvailableNodes(args); - case 'get_node_info': - return this.getNodeInfo(args); - case 'search_nodes': - return this.searchNodes(args); - case 'get_node_statistics': - return this.getNodeStatistics(args); - case 'rebuild_documentation_database': - return this.rebuildDocumentationDatabase(args); - default: - throw new Error(`Unknown tool: ${name}`); - } - } - - private async readResource(uri: string): Promise { - // Resource reading logic - if (uri.startsWith('workflow://')) { - const workflowId = uri.replace('workflow://', ''); - return this.getWorkflow({ id: workflowId }); - } else if (uri === 'nodes://available') { - return this.listAvailableNodes({}); - } else if (uri.startsWith('nodes://source/')) { - const nodeType = uri.replace('nodes://source/', ''); - return this.getNodeSourceCode({ nodeType }); - } - throw new Error(`Unknown resource URI: ${uri}`); - } - - private async generatePrompt(name: string, args: any): Promise { - // Prompt generation logic will be implemented - switch (name) { - case 'create_workflow_prompt': - return `Create an n8n workflow that ${args.description}`; - case 'debug_workflow_prompt': - return `Debug the n8n workflow with ID ${args.workflowId} and identify any issues`; - default: - throw new Error(`Unknown prompt: ${name}`); - } - } - - // n8n integration methods - private async executeWorkflow(args: any): Promise { - try { - const result = await this.n8nClient.executeWorkflow(args.workflowId, args.data); - return N8NMCPBridge.sanitizeData(result); - } catch (error) { - throw new Error(`Failed to execute workflow: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async listWorkflows(args: any): Promise { - try { - const workflows = await this.n8nClient.getWorkflows(args); - return { - workflows: workflows.data.map((wf: any) => N8NMCPBridge.n8nWorkflowToMCP(wf)), - }; - } catch (error) { - throw new Error(`Failed to list workflows: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async getWorkflow(args: any): Promise { - try { - const workflow = await this.n8nClient.getWorkflow(args.id); - return N8NMCPBridge.n8nWorkflowToMCP(workflow); - } catch (error) { - throw new Error(`Failed to get workflow: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async createWorkflow(args: any): Promise { - try { - const workflowData = N8NMCPBridge.mcpToN8NWorkflow(args); - const result = await this.n8nClient.createWorkflow(workflowData); - return N8NMCPBridge.n8nWorkflowToMCP(result); - } catch (error) { - throw new Error(`Failed to create workflow: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async updateWorkflow(args: any): Promise { - try { - const result = await this.n8nClient.updateWorkflow(args.id, args.updates); - return N8NMCPBridge.n8nWorkflowToMCP(result); - } catch (error) { - throw new Error(`Failed to update workflow: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async deleteWorkflow(args: any): Promise { - try { - await this.n8nClient.deleteWorkflow(args.id); - return { success: true, id: args.id }; - } catch (error) { - throw new Error(`Failed to delete workflow: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async getExecutions(args: any): Promise { - try { - const executions = await this.n8nClient.getExecutions(args); - return { - executions: executions.data.map((exec: any) => N8NMCPBridge.n8nExecutionToMCPResource(exec)), - }; - } catch (error) { - throw new Error(`Failed to get executions: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async getExecutionData(args: any): Promise { - try { - const execution = await this.n8nClient.getExecution(args.executionId); - return N8NMCPBridge.n8nExecutionToMCPResource(execution); - } catch (error) { - throw new Error(`Failed to get execution data: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async getNodeSourceCode(args: any): Promise { - try { - logger.info(`Getting source code for node: ${args.nodeType}`); - const nodeInfo = await this.nodeExtractor.extractNodeSource(args.nodeType); - - const result: any = { - nodeType: nodeInfo.nodeType, - sourceCode: nodeInfo.sourceCode, - location: nodeInfo.location, - }; - - if (args.includeCredentials && nodeInfo.credentialCode) { - result.credentialCode = nodeInfo.credentialCode; - } - - if (nodeInfo.packageInfo) { - result.packageInfo = { - name: nodeInfo.packageInfo.name, - version: nodeInfo.packageInfo.version, - description: nodeInfo.packageInfo.description, - }; - } - - return result; - } catch (error) { - logger.error(`Failed to get node source code`, error); - throw new Error(`Failed to get node source code: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async listAvailableNodes(args: any): Promise { - try { - logger.info('Listing available nodes', args); - const nodes = await this.nodeExtractor.listAvailableNodes(args.category, args.search); - return { - nodes, - total: nodes.length, - }; - } catch (error) { - logger.error(`Failed to list available nodes`, error); - throw new Error(`Failed to list available nodes: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - - private async getNodeInfo(args: any): Promise { - try { - logger.info('Getting comprehensive node information', args); - const nodeInfo = await this.nodeDocService.getNodeInfo(args.nodeType); - - if (!nodeInfo) { - throw new Error(`Node ${args.nodeType} not found`); - } - - return { - nodeType: nodeInfo.nodeType, - name: nodeInfo.name, - displayName: nodeInfo.displayName, - description: nodeInfo.description, - category: nodeInfo.category, - subcategory: nodeInfo.subcategory, - icon: nodeInfo.icon, - documentation: { - markdown: nodeInfo.documentationMarkdown, - url: nodeInfo.documentationUrl, - title: nodeInfo.documentationTitle, - }, - operations: nodeInfo.operations || [], - apiMethods: nodeInfo.apiMethods || [], - examples: nodeInfo.documentationExamples || [], - templates: nodeInfo.templates || [], - relatedResources: nodeInfo.relatedResources || [], - requiredScopes: nodeInfo.requiredScopes || [], - exampleWorkflow: nodeInfo.exampleWorkflow, - exampleParameters: nodeInfo.exampleParameters, - propertiesSchema: nodeInfo.propertiesSchema, - metadata: { - packageName: nodeInfo.packageName, - version: nodeInfo.version, - hasCredentials: nodeInfo.hasCredentials, - isTrigger: nodeInfo.isTrigger, - isWebhook: nodeInfo.isWebhook, - aliases: nodeInfo.aliases, - }, - sourceCode: { - node: nodeInfo.sourceCode, - credential: nodeInfo.credentialCode, - }, - }; - } catch (error) { - logger.error(`Failed to get node info`, error); - throw new Error(`Failed to get node info: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async searchNodes(args: any): Promise { - try { - logger.info('Searching nodes with enhanced filtering', args); - const results = await this.nodeDocService.searchNodes({ - query: args.query, - category: args.category, - packageName: args.packageName, - hasCredentials: args.hasCredentials, - isTrigger: args.isTrigger, - limit: args.limit || 20, - }); - - return { - nodes: results.map(node => ({ - nodeType: node.nodeType, - name: node.name, - displayName: node.displayName, - description: node.description, - category: node.category, - packageName: node.packageName, - hasDocumentation: !!node.documentationMarkdown, - hasExamples: !!(node.documentationExamples && node.documentationExamples.length > 0), - operationCount: node.operations?.length || 0, - metadata: { - hasCredentials: node.hasCredentials, - isTrigger: node.isTrigger, - isWebhook: node.isWebhook, - }, - })), - total: results.length, - }; - } catch (error) { - logger.error(`Failed to search nodes`, error); - throw new Error(`Failed to search nodes: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async getNodeStatistics(args: any): Promise { - try { - logger.info(`Getting node statistics`); - const stats = await this.nodeDocService.getStatistics(); - - return { - ...stats, - formattedTotalSize: stats.totalCodeSize ? `${(stats.totalCodeSize / 1024 / 1024).toFixed(2)} MB` : '0 MB', - }; - } catch (error) { - logger.error(`Failed to get node statistics`, error); - throw new Error(`Failed to get node statistics: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - private async rebuildDocumentationDatabase(args: any): Promise { - try { - logger.info('Rebuilding documentation database', args); - const stats = await this.nodeDocService.rebuildDatabase(); - - return { - success: true, - message: 'Documentation database rebuilt successfully', - statistics: stats, - }; - } catch (error) { - logger.error(`Failed to rebuild documentation database`, error); - throw new Error(`Failed to rebuild documentation database: ${error instanceof Error ? error.message : 'Unknown error'}`); - } - } - - - async start(): Promise { - try { - logger.info('Starting n8n MCP server...'); - const transport = new StdioServerTransport(); - await this.server.connect(transport); - logger.info('n8n MCP server started successfully'); - } catch (error) { - logger.error('Failed to start MCP server', error); - throw error; - } - } -} \ No newline at end of file diff --git a/src/mcp/tools-update.ts b/src/mcp/tools-update.ts index c9a013d..9fbb874 100644 --- a/src/mcp/tools-update.ts +++ b/src/mcp/tools-update.ts @@ -1,32 +1,39 @@ import { ToolDefinition } from '../types'; +/** + * n8n Documentation MCP Tools + * + * These tools provide read-only access to n8n node documentation, properties, + * and metadata. They enable AI assistants to understand n8n's capabilities + * and help users build workflows. + */ export const n8nDocumentationTools: ToolDefinition[] = [ { name: 'list_nodes', - description: 'List all available n8n nodes with filtering options', + description: 'List available n8n workflow automation nodes with filtering. Returns node metadata including names, categories, packages, and capabilities. Use this to discover nodes for specific tasks or explore n8n\'s capabilities.', inputSchema: { type: 'object', properties: { package: { type: 'string', - description: 'Filter by package name (e.g., n8n-nodes-base, @n8n/n8n-nodes-langchain)', + description: 'Filter by package name (e.g., "n8n-nodes-base" for core nodes, "@n8n/n8n-nodes-langchain" for AI nodes)', }, category: { type: 'string', - description: 'Filter by category', + description: 'Filter by category (e.g., "AI", "Data Transformation", "Communication", "Developer Tools")', }, developmentStyle: { type: 'string', enum: ['declarative', 'programmatic'], - description: 'Filter by development style', + description: 'Filter by implementation style - declarative (config-based) or programmatic (code-based)', }, isAITool: { type: 'boolean', - description: 'Filter to show only AI tools', + description: 'Filter to show only nodes with usableAsTool property for AI agent integration', }, limit: { type: 'number', - description: 'Maximum number of results to return', + description: 'Maximum number of nodes to return', default: 50, }, }, @@ -34,13 +41,13 @@ export const n8nDocumentationTools: ToolDefinition[] = [ }, { name: 'get_node_info', - description: 'Get comprehensive information about a specific n8n node', + description: 'Get comprehensive details about a specific n8n node including properties, operations, credentials, documentation, examples, and source code. Essential for understanding node configuration and usage.', inputSchema: { type: 'object', properties: { nodeType: { type: 'string', - description: 'The node type (e.g., httpRequest, slack, code)', + description: 'The node type identifier (e.g., "httpRequest", "slack", "code", "agent", "llmChain")', }, }, required: ['nodeType'], @@ -48,17 +55,17 @@ export const n8nDocumentationTools: ToolDefinition[] = [ }, { name: 'search_nodes', - description: 'Full-text search across all node documentation', + description: 'Full-text search across all n8n node documentation, names, and descriptions using SQLite FTS5. Find nodes by functionality, features, or any text content. Returns relevance-ranked results.', inputSchema: { type: 'object', properties: { query: { type: 'string', - description: 'Search query', + description: 'Search keywords to find nodes (e.g., "send email", "AI chat", "database query")', }, limit: { type: 'number', - description: 'Maximum number of results', + description: 'Maximum number of search results', default: 20, }, }, @@ -67,7 +74,7 @@ export const n8nDocumentationTools: ToolDefinition[] = [ }, { name: 'list_ai_tools', - description: 'List all nodes that can be used as AI Agent tools', + description: 'List all n8n nodes that have the "usableAsTool" property set to true. These nodes can be used by AI agents and LangChain integrations as function-calling tools. Returns AI-specific capabilities and metadata.', inputSchema: { type: 'object', properties: {}, @@ -75,13 +82,13 @@ export const n8nDocumentationTools: ToolDefinition[] = [ }, { name: 'get_node_documentation', - description: 'Get the full documentation for a specific node', + description: 'Get the full parsed documentation for a specific n8n node from the official n8n-docs repository. Returns structured markdown with examples, parameter details, authentication info, and common use cases.', inputSchema: { type: 'object', properties: { nodeType: { type: 'string', - description: 'The node type', + description: 'The node type to retrieve documentation for', }, }, required: ['nodeType'], @@ -89,7 +96,7 @@ export const n8nDocumentationTools: ToolDefinition[] = [ }, { name: 'get_database_statistics', - description: 'Get statistics about the node database', + description: 'Get comprehensive statistics about the n8n node documentation database including total nodes (525+), coverage metrics, AI tools count (263+), package breakdown, and storage size. Useful for understanding data completeness.', inputSchema: { type: 'object', properties: {}, diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts deleted file mode 100644 index 50f1f3f..0000000 --- a/src/mcp/tools.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { ToolDefinition } from '../types'; - -export const n8nTools: ToolDefinition[] = [ - { - name: 'execute_workflow', - description: 'Execute an n8n workflow by ID', - inputSchema: { - type: 'object', - properties: { - workflowId: { - type: 'string', - description: 'The ID of the workflow to execute', - }, - data: { - type: 'object', - description: 'Input data for the workflow execution', - }, - }, - required: ['workflowId'], - }, - }, - { - name: 'list_workflows', - description: 'List all available n8n workflows', - inputSchema: { - type: 'object', - properties: { - active: { - type: 'boolean', - description: 'Filter by active status', - }, - tags: { - type: 'array', - items: { type: 'string' }, - description: 'Filter by tags', - }, - }, - }, - }, - { - name: 'get_workflow', - description: 'Get details of a specific workflow', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'The workflow ID', - }, - }, - required: ['id'], - }, - }, - { - name: 'create_workflow', - description: 'Create a new n8n workflow', - inputSchema: { - type: 'object', - properties: { - name: { - type: 'string', - description: 'Name of the workflow', - }, - nodes: { - type: 'array', - description: 'Array of node definitions', - }, - connections: { - type: 'object', - description: 'Node connections', - }, - settings: { - type: 'object', - description: 'Workflow settings', - }, - }, - required: ['name'], - }, - }, - { - name: 'update_workflow', - description: 'Update an existing workflow', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'The workflow ID', - }, - updates: { - type: 'object', - description: 'Updates to apply to the workflow', - }, - }, - required: ['id', 'updates'], - }, - }, - { - name: 'delete_workflow', - description: 'Delete a workflow', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'The workflow ID to delete', - }, - }, - required: ['id'], - }, - }, - { - name: 'get_executions', - description: 'Get workflow execution history', - inputSchema: { - type: 'object', - properties: { - workflowId: { - type: 'string', - description: 'Filter by workflow ID', - }, - status: { - type: 'string', - enum: ['success', 'error', 'running', 'waiting'], - description: 'Filter by execution status', - }, - limit: { - type: 'number', - description: 'Maximum number of executions to return', - }, - }, - }, - }, - { - name: 'get_execution_data', - description: 'Get detailed data for a specific execution', - inputSchema: { - type: 'object', - properties: { - executionId: { - type: 'string', - description: 'The execution ID', - }, - }, - required: ['executionId'], - }, - }, - { - name: 'get_node_source_code', - description: 'Extract source code of a specific n8n node', - inputSchema: { - type: 'object', - properties: { - nodeType: { - type: 'string', - description: 'The node type identifier (e.g., @n8n/n8n-nodes-langchain.Agent)', - }, - includeCredentials: { - type: 'boolean', - description: 'Include credential type definitions if available', - default: false, - }, - }, - required: ['nodeType'], - }, - }, - { - name: 'list_available_nodes', - description: 'List all available n8n nodes with their types', - inputSchema: { - type: 'object', - properties: { - category: { - type: 'string', - description: 'Filter by category (e.g., AI, Data Transformation)', - }, - search: { - type: 'string', - description: 'Search term to filter nodes', - }, - }, - }, - }, - { - name: 'get_node_statistics', - description: 'Get statistics about stored n8n nodes', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - { - name: 'get_node_info', - description: 'Get comprehensive information about a specific n8n node including documentation, operations, API methods, and examples', - inputSchema: { - type: 'object', - properties: { - nodeType: { - type: 'string', - description: 'The node type identifier (e.g., n8n-nodes-base.slack)', - }, - }, - required: ['nodeType'], - }, - }, - { - name: 'search_nodes', - description: 'Search n8n nodes with full-text search and advanced filtering', - inputSchema: { - type: 'object', - properties: { - query: { - type: 'string', - description: 'Search query for full-text search', - }, - category: { - type: 'string', - description: 'Filter by node category', - }, - packageName: { - type: 'string', - description: 'Filter by package name', - }, - hasCredentials: { - type: 'boolean', - description: 'Filter nodes that require credentials', - }, - isTrigger: { - type: 'boolean', - description: 'Filter trigger nodes only', - }, - limit: { - type: 'number', - description: 'Maximum results to return', - default: 20, - }, - }, - }, - }, - { - name: 'rebuild_documentation_database', - description: 'Rebuild the node documentation database with the latest information', - inputSchema: { - type: 'object', - properties: { - packageFilter: { - type: 'string', - description: 'Optional: Only rebuild nodes from specific package', - }, - }, - }, - }, -]; \ No newline at end of file