Add AI Agent node source code extraction capability
This commit implements the ability to extract n8n node source code through MCP:
Features:
- New MCP tools: get_node_source_code and list_available_nodes
- NodeSourceExtractor utility for file system access to n8n nodes
- Support for extracting any n8n node including AI Agent from @n8n/n8n-nodes-langchain
- Resource endpoint for accessing node source: nodes://source/{nodeType}
Testing:
- Docker test environment with mounted n8n node_modules
- Multiple test scripts for different scenarios
- Comprehensive test documentation
- Standalone MCP client test demonstrating full extraction flow
The implementation successfully demonstrates:
1. MCP server can access n8n's installed nodes
2. Source code can be extracted and returned to MCP clients
3. Full metadata including package info and file locations
4. Support for credential code extraction when available
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -31,4 +31,10 @@ export const n8nResources: ResourceDefinition[] = [
|
||||
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',
|
||||
},
|
||||
];
|
||||
@@ -15,13 +15,16 @@ 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';
|
||||
|
||||
export class N8NMCPServer {
|
||||
private server: Server;
|
||||
private n8nClient: N8NApiClient;
|
||||
private nodeExtractor: NodeSourceExtractor;
|
||||
|
||||
constructor(config: MCPServerConfig, n8nConfig: N8NConfig) {
|
||||
this.n8nClient = new N8NApiClient(n8nConfig);
|
||||
this.nodeExtractor = new NodeSourceExtractor();
|
||||
logger.info('Initializing n8n MCP server', { config, n8nConfig });
|
||||
this.server = new Server(
|
||||
{
|
||||
@@ -154,16 +157,25 @@ export class N8NMCPServer {
|
||||
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);
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async readResource(uri: string): Promise<any> {
|
||||
// Resource reading logic will be implemented
|
||||
// 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}`);
|
||||
}
|
||||
@@ -258,6 +270,50 @@ export class N8NMCPServer {
|
||||
}
|
||||
}
|
||||
|
||||
private async getNodeSourceCode(args: any): Promise<any> {
|
||||
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<any> {
|
||||
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'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
try {
|
||||
logger.info('Starting n8n MCP server...');
|
||||
|
||||
@@ -145,4 +145,40 @@ export const n8nTools: ToolDefinition[] = [
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user