- 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>
650 lines
18 KiB
Markdown
650 lines
18 KiB
Markdown
# 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:
|
|
```bash
|
|
git clone https://github.com/n8n-io/n8n-docs.git ../n8n-docs
|
|
```
|
|
|
|
### Test-First Development
|
|
Build the rebuild script first as a test harness:
|
|
```bash
|
|
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!)
|
|
|
|
```typescript
|
|
#!/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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```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`
|
|
|
|
```typescript
|
|
// 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`
|
|
|
|
```typescript
|
|
#!/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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```json
|
|
{
|
|
"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! 🚀** |