Files
n8n-mcp/docs/MCP_QUICK_START_GUIDE.md
czlonkowski 1884d5babf feat: implement AI-optimized MCP tools with 95% size reduction
- Add get_node_essentials tool for 10-20 essential properties only
- Add search_node_properties for targeted property search
- Add get_node_for_task with 14 pre-configured templates
- Add validate_node_config for comprehensive validation
- Add get_property_dependencies for visibility analysis
- Implement PropertyFilter service with curated essentials
- Implement ExampleGenerator with working examples
- Implement TaskTemplates for common workflows
- Implement ConfigValidator with security checks
- Implement PropertyDependencies for dependency analysis
- Enhance property descriptions to 100% coverage
- Add version information to essentials response
- Update documentation with new tools

Response sizes reduced from 100KB+ to <5KB for better AI agent usability.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-16 12:37:45 +02:00

11 KiB

MCP Implementation Quick Start Guide

Immediate Actions (Day 1)

1. Create Essential Properties Configuration

Create src/data/essential-properties.json:

{
  "nodes-base.httpRequest": {
    "required": ["url"],
    "common": ["method", "authentication", "sendBody", "contentType", "sendHeaders"],
    "examples": {
      "minimal": {
        "url": "https://api.example.com/data"
      },
      "getWithAuth": {
        "method": "GET",
        "url": "https://api.example.com/protected",
        "authentication": "genericCredentialType",
        "genericAuthType": "headerAuth"
      },
      "postJson": {
        "method": "POST",
        "url": "https://api.example.com/create",
        "sendBody": true,
        "contentType": "json",
        "jsonBody": "{ \"name\": \"example\" }"
      }
    }
  },
  "nodes-base.webhook": {
    "required": [],
    "common": ["path", "method", "responseMode", "responseData"],
    "examples": {
      "minimal": {
        "path": "webhook",
        "method": "POST"
      }
    }
  }
}

2. Implement get_node_essentials Tool

Add to src/mcp/server.ts:

// Add to tool implementations
case "get_node_essentials": {
  const { nodeType } = request.params.arguments as { nodeType: string };
  
  // Load essential properties config
  const essentialsConfig = require('../data/essential-properties.json');
  const nodeConfig = essentialsConfig[nodeType];
  
  if (!nodeConfig) {
    // Fallback: extract from existing data
    const node = await service.getNodeByType(nodeType);
    if (!node) {
      return { error: `Node type ${nodeType} not found` };
    }
    
    // Parse properties to find required ones
    const properties = JSON.parse(node.properties_schema || '[]');
    const required = properties.filter((p: any) => p.required);
    const common = properties.slice(0, 5); // Top 5 as fallback
    
    return {
      nodeType,
      displayName: node.display_name,
      description: node.description,
      requiredProperties: required.map(simplifyProperty),
      commonProperties: common.map(simplifyProperty),
      examples: {
        minimal: {},
        common: {}
      }
    };
  }
  
  // Use configured essentials
  const node = await service.getNodeByType(nodeType);
  const properties = JSON.parse(node.properties_schema || '[]');
  
  const requiredProps = nodeConfig.required.map((name: string) => {
    const prop = findPropertyByName(properties, name);
    return prop ? simplifyProperty(prop) : null;
  }).filter(Boolean);
  
  const commonProps = nodeConfig.common.map((name: string) => {
    const prop = findPropertyByName(properties, name);
    return prop ? simplifyProperty(prop) : null;
  }).filter(Boolean);
  
  return {
    nodeType,
    displayName: node.display_name,
    description: node.description,
    requiredProperties: requiredProps,
    commonProperties: commonProps,
    examples: nodeConfig.examples || {}
  };
}

// Helper functions
function simplifyProperty(prop: any) {
  return {
    name: prop.name,
    type: prop.type,
    description: prop.description || prop.displayName || '',
    default: prop.default,
    options: prop.options?.map((opt: any) => 
      typeof opt === 'string' ? opt : opt.value
    ),
    placeholder: prop.placeholder
  };
}

function findPropertyByName(properties: any[], name: string): any {
  for (const prop of properties) {
    if (prop.name === name) return prop;
    // Check in nested collections
    if (prop.type === 'collection' && prop.options) {
      const found = findPropertyByName(prop.options, name);
      if (found) return found;
    }
  }
  return null;
}

3. Add Tool Definition

Add to tool definitions:

{
  name: "get_node_essentials",
  description: "Get only essential and commonly-used properties for a node - perfect for quick configuration",
  inputSchema: {
    type: "object",
    properties: {
      nodeType: {
        type: "string",
        description: "The node type (e.g., 'nodes-base.httpRequest')"
      }
    },
    required: ["nodeType"]
  }
}

4. Create Property Parser Service

Create src/services/property-parser.ts:

export class PropertyParser {
  /**
   * Parse nested properties and flatten to searchable format
   */
  static parseProperties(properties: any[], path = ''): ParsedProperty[] {
    const results: ParsedProperty[] = [];
    
    for (const prop of properties) {
      const currentPath = path ? `${path}.${prop.name}` : prop.name;
      
      // Add current property
      results.push({
        name: prop.name,
        path: currentPath,
        type: prop.type,
        description: prop.description || prop.displayName || '',
        required: prop.required || false,
        displayConditions: prop.displayOptions,
        default: prop.default,
        options: prop.options?.filter((opt: any) => typeof opt === 'string' || opt.value)
      });
      
      // Recursively parse nested properties
      if (prop.type === 'collection' && prop.options) {
        results.push(...this.parseProperties(prop.options, currentPath));
      } else if (prop.type === 'fixedCollection' && prop.options) {
        for (const option of prop.options) {
          if (option.values) {
            results.push(...this.parseProperties(option.values, `${currentPath}.${option.name}`));
          }
        }
      }
    }
    
    return results;
  }
  
  /**
   * Find properties matching a search query
   */
  static searchProperties(properties: ParsedProperty[], query: string): ParsedProperty[] {
    const lowerQuery = query.toLowerCase();
    return properties.filter(prop => 
      prop.name.toLowerCase().includes(lowerQuery) ||
      prop.description.toLowerCase().includes(lowerQuery) ||
      prop.path.toLowerCase().includes(lowerQuery)
    );
  }
  
  /**
   * Categorize properties
   */
  static categorizeProperties(properties: ParsedProperty[]): CategorizedProperties {
    const categories: CategorizedProperties = {
      authentication: [],
      request: [],
      response: [],
      advanced: [],
      other: []
    };
    
    for (const prop of properties) {
      if (prop.name.includes('auth') || prop.name.includes('credential')) {
        categories.authentication.push(prop);
      } else if (prop.name.includes('body') || prop.name.includes('header') || 
                 prop.name.includes('query') || prop.name.includes('url')) {
        categories.request.push(prop);
      } else if (prop.name.includes('response') || prop.name.includes('output')) {
        categories.response.push(prop);
      } else if (prop.path.includes('options.')) {
        categories.advanced.push(prop);
      } else {
        categories.other.push(prop);
      }
    }
    
    return categories;
  }
}

interface ParsedProperty {
  name: string;
  path: string;
  type: string;
  description: string;
  required: boolean;
  displayConditions?: any;
  default?: any;
  options?: any[];
}

interface CategorizedProperties {
  authentication: ParsedProperty[];
  request: ParsedProperty[];
  response: ParsedProperty[];
  advanced: ParsedProperty[];
  other: ParsedProperty[];
}

5. Quick Test Script

Create scripts/test-essentials.ts:

import { MCPClient } from '../src/mcp/client';

async function testEssentials() {
  const client = new MCPClient();
  
  console.log('Testing get_node_essentials...\n');
  
  // Test HTTP Request node
  const httpEssentials = await client.call('get_node_essentials', {
    nodeType: 'nodes-base.httpRequest'
  });
  
  console.log('HTTP Request Essentials:');
  console.log(`- Required: ${httpEssentials.requiredProperties.map(p => p.name).join(', ')}`);
  console.log(`- Common: ${httpEssentials.commonProperties.map(p => p.name).join(', ')}`);
  console.log(`- Total properties: ${httpEssentials.requiredProperties.length + httpEssentials.commonProperties.length}`);
  
  // Compare with full response
  const fullInfo = await client.call('get_node_info', {
    nodeType: 'nodes-base.httpRequest'
  });
  
  const fullSize = JSON.stringify(fullInfo).length;
  const essentialSize = JSON.stringify(httpEssentials).length;
  
  console.log(`\nSize comparison:`);
  console.log(`- Full response: ${(fullSize / 1024).toFixed(1)}KB`);
  console.log(`- Essential response: ${(essentialSize / 1024).toFixed(1)}KB`);
  console.log(`- Reduction: ${((1 - essentialSize / fullSize) * 100).toFixed(1)}%`);
}

testEssentials().catch(console.error);

Day 2-3: Implement search_node_properties

case "search_node_properties": {
  const { nodeType, query } = request.params.arguments as { 
    nodeType: string; 
    query: string;
  };
  
  const node = await service.getNodeByType(nodeType);
  if (!node) {
    return { error: `Node type ${nodeType} not found` };
  }
  
  const properties = JSON.parse(node.properties_schema || '[]');
  const parsed = PropertyParser.parseProperties(properties);
  const matches = PropertyParser.searchProperties(parsed, query);
  
  return {
    query,
    matches: matches.map(prop => ({
      name: prop.name,
      type: prop.type,
      path: prop.path,
      description: prop.description,
      visibleWhen: prop.displayConditions?.show
    })),
    totalMatches: matches.length
  };
}

Day 4-5: Implement get_node_for_task

Create src/data/task-templates.json:

{
  "post_json_request": {
    "description": "Make a POST request with JSON data",
    "nodeType": "nodes-base.httpRequest",
    "configuration": {
      "method": "POST",
      "url": "",
      "sendBody": true,
      "contentType": "json",
      "specifyBody": "json",
      "jsonBody": ""
    },
    "userMustProvide": [
      { "property": "url", "description": "API endpoint URL" },
      { "property": "jsonBody", "description": "JSON data to send" }
    ],
    "optionalEnhancements": [
      { "property": "authentication", "description": "Add authentication if required" },
      { "property": "sendHeaders", "description": "Add custom headers" }
    ]
  }
}

Testing Checklist

  • Test get_node_essentials with HTTP Request node
  • Verify size reduction is >90%
  • Test with Webhook, Agent, and Code nodes
  • Validate examples work correctly
  • Test property search functionality
  • Verify task templates are valid
  • Check backward compatibility
  • Measure response times (<100ms)

Success Indicators

  1. Immediate (Day 1):

    • get_node_essentials returns <5KB for HTTP Request
    • Response includes working examples
    • No errors with top 10 nodes
  2. Week 1:

    • 90% reduction in response size
    • Property search working
    • 5+ task templates created
    • Positive AI agent feedback
  3. Month 1:

    • All tools implemented
    • 50+ nodes optimized
    • Configuration time <1 minute
    • Error rate <10%