Skip to main content
Glama

n8n MCP Server

MIT License
133
1,377
  • Apple
  • Linux
extending.md15.7 kB
# Extending the Server This guide explains how to extend the n8n MCP Server with new functionality. ## Overview The n8n MCP Server is designed to be extensible, allowing developers to add new tools and resources without modifying existing code. This extensibility makes it easy to support new n8n features or customize the server for specific use cases. ## Adding a New Tool Tools in the MCP server represent executable operations that AI assistants can use. To add a new tool, follow these steps: ### 1. Define the Tool Interface Create a new TypeScript interface that defines the input parameters for your tool: ```typescript // src/types/tools/my-tool.ts export interface MyToolParams { param1: string; param2?: number; // Optional parameter } ``` ### 2. Create the Tool Handler Create a new file for your tool in the appropriate category under `src/tools/`: ```typescript // src/tools/category/my-tool.ts import { ToolCallResponse, ToolDefinition } from '@modelcontextprotocol/sdk/types.js'; import { N8nClient } from '../../api/n8n-client.js'; import { MyToolParams } from '../../types/tools/my-tool.js'; // Define the tool export function getMyToolDefinition(): ToolDefinition { return { name: 'my_tool', description: 'Description of what my tool does', inputSchema: { type: 'object', properties: { param1: { type: 'string', description: 'Description of param1' }, param2: { type: 'number', description: 'Description of param2' } }, required: ['param1'] } }; } // Implement the tool handler export async function handleMyTool( client: N8nClient, params: MyToolParams ): Promise<ToolCallResponse> { try { // Implement the tool logic here // Use the N8nClient to interact with n8n // Return the response return { content: [ { type: 'text', text: 'Result of the operation' } ] }; } catch (error) { // Handle errors return { content: [ { type: 'text', text: `Error: ${error.message}` } ], isError: true }; } } ``` ### 3. Register the Tool in the Handler Update the main handler file for your tool category (e.g., `src/tools/category/handler.ts`): ```typescript // src/tools/category/handler.ts import { getMyToolDefinition, handleMyTool } from './my-tool.js'; // Add your tool to the tools object export const categoryTools = { // ... existing tools my_tool: { definition: getMyToolDefinition, handler: handleMyTool } }; ``` ### 4. Add Handler to Main Server Update the main tool handler registration in `src/index.ts`: ```typescript // src/index.ts import { categoryTools } from './tools/category/handler.js'; // In the server initialization const server = new Server( { name: 'n8n-mcp-server', version: '0.1.0' }, { capabilities: { tools: { // ... existing categories category: true } } } ); // Register tool handlers Object.entries(categoryTools).forEach(([name, { definition, handler }]) => { server.setToolHandler(definition(), async (request) => { return await handler(client, request.params.arguments as any); }); }); ``` ### 5. Add Unit Tests Create unit tests for your new tool: ```typescript // tests/unit/tools/category/my-tool.test.ts import { describe, it, expect, jest } from '@jest/globals'; import { getMyToolDefinition, handleMyTool } from '../../../../src/tools/category/my-tool.js'; describe('My Tool', () => { describe('getMyToolDefinition', () => { it('should return the correct tool definition', () => { const definition = getMyToolDefinition(); expect(definition.name).toBe('my_tool'); expect(definition.description).toBeTruthy(); expect(definition.inputSchema).toBeDefined(); expect(definition.inputSchema.properties).toHaveProperty('param1'); expect(definition.inputSchema.required).toEqual(['param1']); }); }); describe('handleMyTool', () => { it('should handle valid parameters', async () => { const mockClient = { // Mock the necessary client methods }; const result = await handleMyTool(mockClient as any, { param1: 'test value' }); expect(result.isError).toBeFalsy(); expect(result.content[0].text).toBeTruthy(); }); it('should handle errors properly', async () => { const mockClient = { // Mock client that throws an error someMethod: jest.fn().mockRejectedValue(new Error('Test error')) }; const result = await handleMyTool(mockClient as any, { param1: 'test value' }); expect(result.isError).toBeTruthy(); expect(result.content[0].text).toContain('Error'); }); }); }); ``` ## Adding a New Resource Resources in the MCP server provide data access through URI-based templates. To add a new resource, follow these steps: ### 1. Create a Static Resource (No Parameters) For a resource that doesn't require parameters: ```typescript // src/resources/static/my-resource.ts import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js'; import { ErrorCode } from '../../errors/error-codes.js'; import { N8nClient } from '../../api/n8n-client.js'; export const MY_RESOURCE_URI = 'n8n://my-resource'; export async function handleMyResourceRequest( client: N8nClient ): Promise<ReadResourceResponse> { try { // Implement the resource logic // Use the N8nClient to interact with n8n // Return the response return { contents: [ { uri: MY_RESOURCE_URI, mimeType: 'application/json', text: JSON.stringify( { // Resource data property1: 'value1', property2: 'value2' }, null, 2 ) } ] }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to retrieve resource: ${error.message}` ); } } ``` ### 2. Create a Dynamic Resource (With Parameters) For a resource that requires parameters: ```typescript // src/resources/dynamic/my-resource.ts import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js'; import { ErrorCode } from '../../errors/error-codes.js'; import { N8nClient } from '../../api/n8n-client.js'; export const MY_RESOURCE_URI_TEMPLATE = 'n8n://my-resource/{id}'; export function matchMyResourceUri(uri: string): { id: string } | null { const match = uri.match(/^n8n:\/\/my-resource\/([^/]+)$/); if (!match) return null; return { id: decodeURIComponent(match[1]) }; } export async function handleMyResourceRequest( client: N8nClient, uri: string ): Promise<ReadResourceResponse> { const params = matchMyResourceUri(uri); if (!params) { throw new McpError( ErrorCode.InvalidRequest, `Invalid URI format: ${uri}` ); } try { // Implement the resource logic using params.id // Use the N8nClient to interact with n8n // Return the response return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify( { // Resource data with the specific ID id: params.id, property1: 'value1', property2: 'value2' }, null, 2 ) } ] }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to retrieve resource: ${error.message}` ); } } ``` ### 3. Register Resources in the Handler Files Update the resource handler registration: #### For Static Resources ```typescript // src/resources/static/index.ts import { MY_RESOURCE_URI, handleMyResourceRequest } from './my-resource.js'; export const staticResources = { // ... existing static resources [MY_RESOURCE_URI]: handleMyResourceRequest }; ``` #### For Dynamic Resources ```typescript // src/resources/dynamic/index.ts import { MY_RESOURCE_URI_TEMPLATE, matchMyResourceUri, handleMyResourceRequest } from './my-resource.js'; export const dynamicResourceMatchers = [ // ... existing dynamic resource matchers { uriTemplate: MY_RESOURCE_URI_TEMPLATE, match: matchMyResourceUri, handler: handleMyResourceRequest } ]; ``` ### 4. Add Resource Listings Update the resource listing functions: ```typescript // src/resources/index.ts // Update the resource templates listing export function getResourceTemplates() { return [ // ... existing templates { uriTemplate: MY_RESOURCE_URI_TEMPLATE, name: 'My Resource', description: 'Description of my resource' } ]; } // Update the static resources listing export function getStaticResources() { return [ // ... existing resources { uri: MY_RESOURCE_URI, name: 'My Resource List', description: 'List of all my resources' } ]; } ``` ### 5. Add Unit Tests Create tests for your new resource: ```typescript // tests/unit/resources/static/my-resource.test.ts // or // tests/unit/resources/dynamic/my-resource.test.ts import { describe, it, expect, jest } from '@jest/globals'; import { MY_RESOURCE_URI, handleMyResourceRequest } from '../../../../src/resources/static/my-resource.js'; describe('My Resource', () => { it('should return resource data', async () => { const mockClient = { // Mock the necessary client methods }; const response = await handleMyResourceRequest(mockClient as any); expect(response.contents).toHaveLength(1); expect(response.contents[0].uri).toBe(MY_RESOURCE_URI); expect(response.contents[0].mimeType).toBe('application/json'); const data = JSON.parse(response.contents[0].text); expect(data).toHaveProperty('property1'); expect(data).toHaveProperty('property2'); }); it('should handle errors properly', async () => { const mockClient = { // Mock client that throws an error someMethod: jest.fn().mockRejectedValue(new Error('Test error')) }; await expect(handleMyResourceRequest(mockClient as any)) .rejects .toThrow('Failed to retrieve resource'); }); }); ``` ## Extending the API Client If you need to add support for new n8n API features, extend the N8nClient class: ### 1. Add New Methods to the Client ```typescript // src/api/n8n-client.ts export class N8nClient { // ... existing methods // Add new methods async myNewApiMethod(param1: string): Promise<any> { try { const response = await this.httpClient.get(`/endpoint/${param1}`); return response.data; } catch (error) { this.handleApiError(error); } } } ``` ### 2. Add Type Definitions ```typescript // src/types/api.ts // Add types for API responses and requests export interface MyApiResponse { id: string; name: string; // Other properties } export interface MyApiRequest { param1: string; param2?: number; } ``` ### 3. Add Tests for the New API Methods ```typescript // tests/unit/api/n8n-client.test.ts describe('N8nClient', () => { // ... existing tests describe('myNewApiMethod', () => { it('should call the correct API endpoint', async () => { // Set up mock Axios axiosMock.onGet('/endpoint/test').reply(200, { id: '123', name: 'Test' }); const client = new N8nClient({ apiUrl: 'http://localhost:5678/api/v1', apiKey: 'test-api-key' }); const result = await client.myNewApiMethod('test'); expect(result).toEqual({ id: '123', name: 'Test' }); }); it('should handle errors correctly', async () => { // Set up mock Axios axiosMock.onGet('/endpoint/test').reply(404, { message: 'Not found' }); const client = new N8nClient({ apiUrl: 'http://localhost:5678/api/v1', apiKey: 'test-api-key' }); await expect(client.myNewApiMethod('test')) .rejects .toThrow('Resource not found'); }); }); }); ``` ## Best Practices for Extensions 1. **Follow the Existing Patterns**: Try to follow the patterns already established in the codebase. 2. **Type Safety**: Use TypeScript types and interfaces to ensure type safety. 3. **Error Handling**: Implement comprehensive error handling in all extensions. 4. **Testing**: Write thorough tests for all new functionality. 5. **Documentation**: Document your extensions, including JSDoc comments for all public methods. 6. **Backward Compatibility**: Ensure that your extensions don't break existing functionality. ## Example: Adding Support for n8n Tags Here's a complete example of adding support for n8n tags: ### API Client Extension ```typescript // src/api/n8n-client.ts export class N8nClient { // ... existing methods // Add tag methods async getTags(): Promise<Tag[]> { try { const response = await this.httpClient.get('/tags'); return response.data; } catch (error) { this.handleApiError(error); } } async createTag(data: CreateTagRequest): Promise<Tag> { try { const response = await this.httpClient.post('/tags', data); return response.data; } catch (error) { this.handleApiError(error); } } async deleteTag(id: string): Promise<void> { try { await this.httpClient.delete(`/tags/${id}`); } catch (error) { this.handleApiError(error); } } } ``` ### Type Definitions ```typescript // src/types/api.ts export interface Tag { id: string; name: string; createdAt: string; updatedAt: string; } export interface CreateTagRequest { name: string; } ``` ### Tool Implementations ```typescript // src/tools/tag/list.ts export function getTagListToolDefinition(): ToolDefinition { return { name: 'tag_list', description: 'List all tags in n8n', inputSchema: { type: 'object', properties: {}, required: [] } }; } export async function handleTagList( client: N8nClient, params: any ): Promise<ToolCallResponse> { try { const tags = await client.getTags(); return { content: [ { type: 'text', text: JSON.stringify(tags, null, 2) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error listing tags: ${error.message}` } ], isError: true }; } } ``` ### Resource Implementation ```typescript // src/resources/static/tags.ts export const TAGS_URI = 'n8n://tags'; export async function handleTagsRequest( client: N8nClient ): Promise<ReadResourceResponse> { try { const tags = await client.getTags(); return { contents: [ { uri: TAGS_URI, mimeType: 'application/json', text: JSON.stringify( { tags, count: tags.length }, null, 2 ) } ] }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to retrieve tags: ${error.message}` ); } } ``` ### Integration Register the new tools and resources in the appropriate handler files, and update the main server initialization to include them. By following these patterns, you can extend the n8n MCP Server to support any n8n feature or add custom functionality tailored to your specific needs.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/leonardsellem/n8n-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server