Files
n8n-mcp/IMPLEMENTATION_PLAN.md
czlonkowski 8bf670c31e feat: Implement n8n-MCP Enhancement Plan v2.1 Final
- Implement simple node loader supporting n8n-nodes-base and langchain packages
- Create parser handling declarative, programmatic, and versioned nodes
- Build documentation mapper with 89% coverage (405/457 nodes)
- Setup SQLite database with minimal schema
- Create rebuild script for one-command database updates
- Implement validation script for critical nodes
- Update MCP server with documentation-focused tools
- Add npm scripts for streamlined workflow

Successfully loads 457/458 nodes with accurate documentation mapping.
Versioned node detection working (46 nodes detected).
3/4 critical nodes pass validation tests.

Known limitations:
- Slack operations extraction incomplete for some versioned nodes
- One langchain node fails due to missing dependency
- No AI tools detected (none have usableAsTool flag)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-12 14:18:19 +02:00

18 KiB

n8n-MCP Enhancement Implementation Plan v2.1 Final

Executive Summary

This ultra-focused MVP implementation plan delivers accurate n8n node documentation in 2 weeks by working directly with n8n's architecture. We prioritize simplicity and accuracy over complex features.

Core MVP Principles

  1. Start with the simplest thing that works
  2. Test with real nodes early and often
  3. Don't try to be too clever - n8n's structure is fine
  4. Focus on accuracy over completeness
  5. Work WITH n8n's architecture, not against it

Key Insight

We're not trying to understand n8n's nodes, we're just accurately cataloging them.

Simplified Architecture

n8n-mcp/
├── src/
│   ├── loaders/
│   │   └── node-loader.ts         # Simple npm package loader
│   ├── parsers/
│   │   └── simple-parser.ts       # Single parser for all nodes
│   ├── mappers/
│   │   └── docs-mapper.ts         # Deterministic documentation mapping
│   ├── scripts/
│   │   ├── rebuild.ts             # One-command rebuild
│   │   └── validate.ts            # Validation script
│   └── mcp/
│       └── server.ts              # Enhanced MCP server
└── data/
    └── nodes.db                   # Minimal SQLite database

Implementation Strategy

Quick Win Approach

Get something working end-to-end on Day 1, even if it only loads 5 nodes. This proves the architecture and builds momentum.

Documentation Strategy

Clone the n8n-docs repo locally for simpler file-based access:

git clone https://github.com/n8n-io/n8n-docs.git ../n8n-docs

Test-First Development

Build the rebuild script first as a test harness:

npm run rebuild && sqlite3 data/nodes.db "SELECT node_type, display_name FROM nodes LIMIT 10"

Week 1: Core Implementation

Day 1-2: Simple Node Loader + Initial Rebuild Script

Start with the rebuild script to enable quick iteration!

File: src/scripts/rebuild.ts (Build this first!)

#!/usr/bin/env node
import Database from 'better-sqlite3';
import { N8nNodeLoader } from '../loaders/node-loader';
import { SimpleParser } from '../parsers/simple-parser';
import { DocsMapper } from '../mappers/docs-mapper';

async function rebuild() {
  console.log('🔄 Rebuilding n8n node database...\n');
  
  const db = new Database('./data/nodes.db');
  const loader = new N8nNodeLoader();
  const parser = new SimpleParser();
  const mapper = new DocsMapper();
  
  // Initialize database
  const schema = require('fs').readFileSync('./src/database/schema.sql', 'utf8');
  db.exec(schema);
  
  // Clear existing data
  db.exec('DELETE FROM nodes');
  console.log('🗑️  Cleared existing data\n');
  
  // Load all nodes
  const nodes = await loader.loadAllNodes();
  console.log(`📦 Loaded ${nodes.length} nodes from packages\n`);
  
  // Statistics
  let successful = 0;
  let failed = 0;
  let aiTools = 0;
  
  // Process each node
  for (const { packageName, nodeName, NodeClass } of nodes) {
    try {
      // Parse node
      const parsed = parser.parse(NodeClass);
      
      // Get documentation
      const docs = await mapper.fetchDocumentation(parsed.nodeType);
      
      // Insert into database
      db.prepare(`
        INSERT INTO nodes (
          node_type, package_name, display_name, description,
          category, development_style, is_ai_tool, is_trigger,
          is_webhook, is_versioned, version, documentation,
          properties_schema, operations, credentials_required
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run(
        parsed.nodeType,
        packageName,
        parsed.displayName,
        parsed.description,
        parsed.category,
        parsed.style,
        parsed.isAITool ? 1 : 0,
        parsed.isTrigger ? 1 : 0,
        parsed.isWebhook ? 1 : 0,
        parsed.isVersioned ? 1 : 0,
        parsed.version,
        docs,
        JSON.stringify(parsed.properties),
        JSON.stringify(parsed.operations),
        JSON.stringify(parsed.credentials)
      );
      
      successful++;
      if (parsed.isAITool) aiTools++;
      
      console.log(`✅ ${parsed.nodeType}`);
    } catch (error) {
      failed++;
      console.error(`❌ Failed to process ${nodeName}: ${error.message}`);
    }
  }
  
  // Summary
  console.log('\n📊 Summary:');
  console.log(`   Total nodes: ${nodes.length}`);
  console.log(`   Successful: ${successful}`);
  console.log(`   Failed: ${failed}`);
  console.log(`   AI Tools: ${aiTools}`);
  console.log('\n✨ Rebuild complete!');
  
  db.close();
}

// Run if called directly
if (require.main === module) {
  rebuild().catch(console.error);
}

File: src/loaders/node-loader.ts

export class N8nNodeLoader {
  private readonly CORE_PACKAGES = [
    'n8n-nodes-base',
    '@n8n/n8n-nodes-langchain'
  ];

  async loadAllNodes() {
    const results = [];
    
    for (const pkg of this.CORE_PACKAGES) {
      try {
        // Direct require - no complex path resolution
        const packageJson = require(`${pkg}/package.json`);
        const nodes = await this.loadPackageNodes(pkg, packageJson);
        results.push(...nodes);
      } catch (error) {
        console.error(`Failed to load ${pkg}:`, error);
      }
    }
    
    return results;
  }

  private async loadPackageNodes(packageName: string, packageJson: any) {
    const n8nConfig = packageJson.n8n || {};
    const nodes = [];
    
    // Load from n8n.nodes configuration
    for (const [nodeName, nodePath] of Object.entries(n8nConfig.nodes || {})) {
      const fullPath = require.resolve(`${packageName}/${nodePath}`);
      const nodeModule = require(fullPath);
      
      // Handle default export
      const NodeClass = nodeModule.default || nodeModule[nodeName];
      nodes.push({ packageName, nodeName, NodeClass });
    }
    
    return nodes;
  }
}

Day 3: Simple Parser

File: src/parsers/simple-parser.ts

export interface ParsedNode {
  style: 'declarative' | 'programmatic';
  nodeType: string;
  displayName: string;
  description?: string;
  category?: string;
  properties: any[];
  credentials: string[];
  isAITool: boolean;
  isTrigger: boolean;
  isWebhook: boolean;
  operations: any[];
  version?: string;
  isVersioned: boolean;
}

export class SimpleParser {
  parse(nodeClass: any): ParsedNode {
    const description = nodeClass.description || {};
    const isDeclarative = !!description.routing;
    
    return {
      style: isDeclarative ? 'declarative' : 'programmatic',
      nodeType: description.name,
      displayName: description.displayName,
      description: description.description,
      category: description.group?.[0] || description.categories?.[0],
      properties: description.properties || [],
      credentials: description.credentials || [],
      isAITool: description.usableAsTool === true,
      isTrigger: description.polling === true || description.trigger === true,
      isWebhook: description.webhooks?.length > 0,
      operations: isDeclarative ? this.extractOperations(description.routing) : [],
      version: this.extractVersion(nodeClass),
      isVersioned: this.isVersionedNode(nodeClass)
    };
  }
  
  private extractOperations(routing: any): any[] {
    // Simple extraction without complex logic
    const operations = [];
    const resources = routing?.request?.resource?.options || [];
    
    resources.forEach(resource => {
      operations.push({
        resource: resource.value,
        name: resource.name
      });
    });
    
    return operations;
  }

  private extractVersion(nodeClass: any): string {
    if (nodeClass.baseDescription?.defaultVersion) {
      return nodeClass.baseDescription.defaultVersion.toString();
    }
    return nodeClass.description?.version || '1';
  }

  private isVersionedNode(nodeClass: any): boolean {
    return !!(nodeClass.baseDescription && nodeClass.nodeVersions);
  }
}

Day 4: Documentation Mapper

File: src/mappers/docs-mapper.ts

import { promises as fs } from 'fs';
import path from 'path';

export class DocsMapper {
  private docsPath = path.join(__dirname, '../../../n8n-docs');
  
  // Known documentation mapping fixes
  private readonly KNOWN_FIXES = {
    'n8n-nodes-base.httpRequest': 'httprequest',
    'n8n-nodes-base.code': 'code',
    'n8n-nodes-base.webhook': 'webhook',
    'n8n-nodes-base.respondToWebhook': 'respondtowebhook'
  };

  async fetchDocumentation(nodeType: string): Promise<string | null> {
    // Apply known fixes first
    const fixedType = this.KNOWN_FIXES[nodeType] || nodeType;
    
    // Extract node name
    const nodeName = fixedType.split('.').pop()?.toLowerCase();
    if (!nodeName) return null;
    
    // Try different documentation paths
    const possiblePaths = [
      `docs/integrations/builtin/core-nodes/n8n-nodes-base.${nodeName}.md`,
      `docs/integrations/builtin/app-nodes/n8n-nodes-base.${nodeName}.md`,
      `docs/integrations/builtin/trigger-nodes/n8n-nodes-base.${nodeName}.md`,
      `docs/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.${nodeName}.md`
    ];
    
    // Try each path
    for (const relativePath of possiblePaths) {
      try {
        const fullPath = path.join(this.docsPath, relativePath);
        const content = await fs.readFile(fullPath, 'utf-8');
        return content;
      } catch (error) {
        // File doesn't exist, try next
        continue;
      }
    }
    
    return null;
  }
}

Day 5: Database Setup

File: src/database/schema.sql

-- Ultra-simple schema for MVP
CREATE TABLE IF NOT EXISTS nodes (
  node_type TEXT PRIMARY KEY,
  package_name TEXT NOT NULL,
  display_name TEXT NOT NULL,
  description TEXT,
  category TEXT,
  development_style TEXT CHECK(development_style IN ('declarative', 'programmatic')),
  is_ai_tool INTEGER DEFAULT 0,
  is_trigger INTEGER DEFAULT 0,
  is_webhook INTEGER DEFAULT 0,
  is_versioned INTEGER DEFAULT 0,
  version TEXT,
  documentation TEXT,
  properties_schema TEXT,
  operations TEXT,
  credentials_required TEXT,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Minimal indexes for performance
CREATE INDEX IF NOT EXISTS idx_package ON nodes(package_name);
CREATE INDEX IF NOT EXISTS idx_ai_tool ON nodes(is_ai_tool);
CREATE INDEX IF NOT EXISTS idx_category ON nodes(category);

Week 2: Integration and Testing

Day 6-7: Test Priority Nodes

Focus on these nodes first (they cover most edge cases):

  1. HTTP Request - Known documentation mismatch
  2. Slack - Complex declarative node
  3. Code - Versioned node with documentation issues
  4. AI Agent - LangChain node with AI tool flag

Day 8-9: MCP Server Updates

File: src/mcp/tools-update.ts

// Simplified get_node_info tool
async function getNodeInfo(nodeType: string) {
  const node = db.prepare(`
    SELECT * FROM nodes WHERE node_type = ?
  `).get(nodeType);
  
  if (!node) {
    throw new Error(`Node ${nodeType} not found`);
  }
  
  return {
    nodeType: node.node_type,
    displayName: node.display_name,
    description: node.description,
    category: node.category,
    developmentStyle: node.development_style,
    isAITool: !!node.is_ai_tool,
    isTrigger: !!node.is_trigger,
    isWebhook: !!node.is_webhook,
    version: node.version,
    properties: JSON.parse(node.properties_schema),
    operations: JSON.parse(node.operations || '[]'),
    credentials: JSON.parse(node.credentials_required),
    documentation: node.documentation
  };
}

// New tool: list_ai_tools
{
  name: 'list_ai_tools',
  description: 'List all nodes that can be used as AI Agent tools',
  inputSchema: {
    type: 'object',
    properties: {}
  }
}

async function listAITools() {
  const tools = db.prepare(`
    SELECT node_type, display_name, description, package_name
    FROM nodes 
    WHERE is_ai_tool = 1
    ORDER BY display_name
  `).all();
  
  return {
    tools,
    totalCount: tools.length,
    requirements: {
      environmentVariable: 'N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true',
      nodeProperty: 'usableAsTool: true'
    }
  };
}

Day 10: Validation Script

File: src/scripts/validate.ts

#!/usr/bin/env node
import Database from 'better-sqlite3';

async function validate() {
  const db = new Database('./data/nodes.db');
  
  console.log('🔍 Validating critical nodes...\n');
  
  const criticalChecks = [
    { 
      type: 'n8n-nodes-base.httpRequest', 
      checks: {
        hasDocumentation: true,
        documentationContains: 'httprequest',
        style: 'programmatic'
      }
    },
    { 
      type: 'n8n-nodes-base.code', 
      checks: {
        hasDocumentation: true,
        documentationContains: 'code',
        isVersioned: true
      }
    },
    { 
      type: 'n8n-nodes-base.slack', 
      checks: {
        hasOperations: true,
        style: 'declarative'
      }
    },
    {
      type: '@n8n/n8n-nodes-langchain.agent',
      checks: {
        isAITool: true,
        packageName: '@n8n/n8n-nodes-langchain'
      }
    }
  ];
  
  let passed = 0;
  let failed = 0;
  
  for (const check of criticalChecks) {
    const node = db.prepare('SELECT * FROM nodes WHERE node_type = ?').get(check.type);
    
    if (!node) {
      console.log(`❌ ${check.type}: NOT FOUND`);
      failed++;
      continue;
    }
    
    let nodeOk = true;
    const issues = [];
    
    // Run checks
    if (check.checks.hasDocumentation && !node.documentation) {
      nodeOk = false;
      issues.push('missing documentation');
    }
    
    if (check.checks.documentationContains && 
        !node.documentation?.includes(check.checks.documentationContains)) {
      nodeOk = false;
      issues.push(`documentation doesn't contain "${check.checks.documentationContains}"`);
    }
    
    if (check.checks.style && node.development_style !== check.checks.style) {
      nodeOk = false;
      issues.push(`wrong style: ${node.development_style}`);
    }
    
    if (check.checks.hasOperations) {
      const operations = JSON.parse(node.operations || '[]');
      if (!operations.length) {
        nodeOk = false;
        issues.push('no operations found');
      }
    }
    
    if (check.checks.isAITool && !node.is_ai_tool) {
      nodeOk = false;
      issues.push('not marked as AI tool');
    }
    
    if (check.checks.isVersioned && !node.is_versioned) {
      nodeOk = false;
      issues.push('not marked as versioned');
    }
    
    if (nodeOk) {
      console.log(`✅ ${check.type}`);
      passed++;
    } else {
      console.log(`❌ ${check.type}: ${issues.join(', ')}`);
      failed++;
    }
  }
  
  console.log(`\n📊 Results: ${passed} passed, ${failed} failed`);
  
  // Additional statistics
  const stats = db.prepare(`
    SELECT 
      COUNT(*) as total,
      SUM(is_ai_tool) as ai_tools,
      SUM(is_trigger) as triggers,
      SUM(is_versioned) as versioned,
      COUNT(DISTINCT package_name) as packages
    FROM nodes
  `).get();
  
  console.log('\n📈 Database Statistics:');
  console.log(`   Total nodes: ${stats.total}`);
  console.log(`   AI tools: ${stats.ai_tools}`);
  console.log(`   Triggers: ${stats.triggers}`);
  console.log(`   Versioned: ${stats.versioned}`);
  console.log(`   Packages: ${stats.packages}`);
  
  db.close();
  process.exit(failed > 0 ? 1 : 0);
}

if (require.main === module) {
  validate().catch(console.error);
}

MVP Deliverables Checklist

Week 1

  • Clone n8n-docs repository locally
  • Build rebuild script first (test harness)
  • Basic node loader for n8n-nodes-base and langchain packages
  • Simple parser (no complex analysis)
  • Documentation fetcher with file-based access
  • SQLite database setup with minimal schema
  • Get 5 nodes working end-to-end on Day 1

Week 2

  • Test priority nodes (HTTP Request, Slack, Code, AI Agent)
  • Fix all documentation mapping issues
  • Update MCP tools for simplified schema
  • Add AI tools listing functionality
  • Create validation script
  • Document usage instructions
  • Run full validation suite

What We're Deferring Post-MVP

  1. Version history tracking - Just current version
  2. Source code extraction - Not needed for documentation
  3. Complex property type analysis - Keep n8n's structure as-is
  4. Custom node directory support - Focus on npm packages only
  5. Performance optimizations - SQLite is fast enough
  6. Real-time monitoring - Static documentation only
  7. Web UI - CLI tools only
  8. Multi-tenant support - Single instance
  9. Advanced search - Basic SQL queries are sufficient
  10. Community nodes - Just official packages for now

Success Metrics

  1. Accuracy: 100% correct node-to-documentation mapping for test nodes
  2. Coverage: All nodes from n8n-nodes-base and n8n-nodes-langchain
  3. Performance: Full rebuild in <30 seconds
  4. Simplicity: Single command rebuild (npm run rebuild)
  5. Reliability: No failures on standard nodes
  6. Validation: All critical nodes pass validation script

Quick Start Guide

# Setup
git clone https://github.com/n8n-io/n8n-docs.git ../n8n-docs
npm install

# Build
npm run build

# Rebuild database
npm run rebuild

# Validate
npm run validate

# Start MCP server
npm start

NPM Scripts

{
  "scripts": {
    "build": "tsc",
    "rebuild": "node dist/scripts/rebuild.js",
    "validate": "node dist/scripts/validate.js",
    "start": "node dist/mcp/server.js",
    "dev": "npm run build && npm run rebuild && npm run validate"
  }
}

Summary

This v2.1 Final plan delivers a working MVP in 2 weeks by:

  • Starting with the test harness - Build rebuild script first
  • Getting quick wins - 5 nodes on Day 1
  • Testing critical nodes early - HTTP Request, Slack, Code, AI Agent
  • Using local documentation - Clone n8n-docs for file access
  • Validating success - Automated validation script

The result: A reliable, accurate node documentation service that can be enhanced incrementally post-MVP.

Ready to build! 🚀