Skip to main content
Glama

n8n-MCP

by 88-888
update-partial-workflow.test.tsโ€ข35.1 kB
/** * Integration Tests: handleUpdatePartialWorkflow * * Tests diff-based partial workflow updates against a real n8n instance. * Covers all 15 operation types: node operations (6), connection operations (5), * and metadata operations (4). */ import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest'; import { createTestContext, TestContext, createTestWorkflowName } from '../utils/test-context'; import { getTestN8nClient } from '../utils/n8n-client'; import { N8nApiClient } from '../../../../src/services/n8n-api-client'; import { SIMPLE_WEBHOOK_WORKFLOW, SIMPLE_HTTP_WORKFLOW, MULTI_NODE_WORKFLOW } from '../utils/fixtures'; import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers'; import { createMcpContext, getMcpRepository } from '../utils/mcp-context'; import { InstanceContext } from '../../../../src/types/instance-context'; import { NodeRepository } from '../../../../src/database/node-repository'; import { handleUpdatePartialWorkflow } from '../../../../src/mcp/handlers-workflow-diff'; describe('Integration: handleUpdatePartialWorkflow', () => { let context: TestContext; let client: N8nApiClient; let mcpContext: InstanceContext; let repository: NodeRepository; beforeEach(async () => { context = createTestContext(); client = getTestN8nClient(); mcpContext = createMcpContext(); repository = await getMcpRepository(); }); afterEach(async () => { await context.cleanup(); }); afterAll(async () => { if (!process.env.CI) { await cleanupOrphanedWorkflows(); } }); // ====================================================================== // NODE OPERATIONS (6 operations) // ====================================================================== describe('Node Operations', () => { describe('addNode', () => { it('should add a new node to workflow', async () => { // Create simple workflow const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Add Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Add a Set node and connect it to maintain workflow validity const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addNode', node: { name: 'Set', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300], parameters: { assignments: { assignments: [ { id: 'assign-1', name: 'test', value: 'value', type: 'string' } ] } } } }, { type: 'addConnection', source: 'Webhook', target: 'Set', sourcePort: 'main', targetPort: 'main' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; expect(updated.nodes).toHaveLength(2); expect(updated.nodes.find((n: any) => n.name === 'Set')).toBeDefined(); }); it('should return error for duplicate node name', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Duplicate Node Name'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to add node with same name as existing const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addNode', node: { name: 'Webhook', // Duplicate name type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300], parameters: {} } } ] }, repository, mcpContext ); expect(response.success).toBe(false); expect(response.error).toBeDefined(); }); }); describe('removeNode', () => { it('should remove node by name', async () => { const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Remove Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Remove HTTP Request node by name const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeNode', nodeName: 'HTTP Request' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; expect(updated.nodes).toHaveLength(1); expect(updated.nodes.find((n: any) => n.name === 'HTTP Request')).toBeUndefined(); }); it('should return error for non-existent node', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Remove Non-existent'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeNode', nodeName: 'NonExistentNode' } ] }, repository, mcpContext ); expect(response.success).toBe(false); }); }); describe('updateNode', () => { it('should update node parameters', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Update Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Update webhook path const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateNode', nodeName: 'Webhook', updates: { 'parameters.path': 'updated-path' } } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook'); expect(webhookNode.parameters.path).toBe('updated-path'); }); it('should update nested parameters', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Update Nested'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateNode', nodeName: 'Webhook', updates: { 'parameters.httpMethod': 'POST', 'parameters.path': 'new-path' } } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook'); expect(webhookNode.parameters.httpMethod).toBe('POST'); expect(webhookNode.parameters.path).toBe('new-path'); }); }); describe('moveNode', () => { it('should move node to new position', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Move Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const newPosition: [number, number] = [500, 500]; const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'moveNode', nodeName: 'Webhook', position: newPosition } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook'); expect(webhookNode.position).toEqual(newPosition); }); }); describe('enableNode / disableNode', () => { it('should disable a node', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Disable Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'disableNode', nodeName: 'Webhook' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook'); expect(webhookNode.disabled).toBe(true); }); it('should enable a disabled node', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Enable Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // First disable the node await handleUpdatePartialWorkflow( { id: created.id, operations: [{ type: 'disableNode', nodeName: 'Webhook' }] }, repository, mcpContext ); // Then enable it const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'enableNode', nodeName: 'Webhook' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook'); // After enabling, disabled should be false or undefined (both mean enabled) expect(webhookNode.disabled).toBeFalsy(); }); }); }); // ====================================================================== // CONNECTION OPERATIONS (5 operations) // ====================================================================== describe('Connection Operations', () => { describe('addConnection', () => { it('should add connection between nodes', async () => { // Start with workflow without connections const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Add Connection'), tags: ['mcp-integration-test'], connections: {} // Start with no connections }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Add connection const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addConnection', source: 'Webhook', target: 'HTTP Request' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; expect(updated.connections).toBeDefined(); expect(updated.connections.Webhook).toBeDefined(); }); it('should add connection with custom ports', async () => { const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Add Connection Ports'), tags: ['mcp-integration-test'], connections: {} }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addConnection', source: 'Webhook', target: 'HTTP Request', sourceOutput: 'main', targetInput: 'main', sourceIndex: 0, targetIndex: 0 } ] }, repository, mcpContext ); expect(response.success).toBe(true); }); }); describe('removeConnection', () => { it('should reject removal of last connection (creates invalid workflow)', async () => { const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Remove Connection'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to remove the only connection - should be rejected (leaves 2 nodes with no connections) const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeConnection', source: 'Webhook', target: 'HTTP Request', sourcePort: 'main', targetPort: 'main' } ] }, repository, mcpContext ); // Should fail validation - multi-node workflow needs connections expect(response.success).toBe(false); expect(response.error).toContain('Workflow validation failed'); }); it('should ignore error for non-existent connection with ignoreErrors flag', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Remove Connection Ignore'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeConnection', source: 'Webhook', target: 'NonExistent', ignoreErrors: true } ] }, repository, mcpContext ); // Should succeed because ignoreErrors is true expect(response.success).toBe(true); }); }); describe('replaceConnections', () => { it('should reject replacing with empty connections (creates invalid workflow)', async () => { const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Replace Connections'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to replace with empty connections - should be rejected (leaves 2 nodes with no connections) const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'replaceConnections', connections: {} } ] }, repository, mcpContext ); // Should fail validation - multi-node workflow needs connections expect(response.success).toBe(false); expect(response.error).toContain('Workflow validation failed'); }); }); describe('cleanStaleConnections', () => { it('should remove stale connections in dry run mode', async () => { const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Clean Stale Dry Run'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Remove HTTP Request node to create stale connection await handleUpdatePartialWorkflow( { id: created.id, operations: [{ type: 'removeNode', nodeName: 'HTTP Request' }] }, repository, mcpContext ); // Clean stale connections in dry run const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'cleanStaleConnections', dryRun: true } ], validateOnly: true }, repository, mcpContext ); expect(response.success).toBe(true); }); }); }); // ====================================================================== // METADATA OPERATIONS (4 operations) // ====================================================================== describe('Metadata Operations', () => { describe('updateSettings', () => { it('should update workflow settings', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Update Settings'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateSettings', settings: { timezone: 'America/New_York', executionOrder: 'v1' } } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; // Note: n8n API may not return all settings in response // The operation should succeed even if settings aren't reflected in the response expect(updated.settings).toBeDefined(); }); }); describe('updateName', () => { it('should update workflow name', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Update Name Original'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const newName = createTestWorkflowName('Partial - Update Name Modified'); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateName', name: newName } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; expect(updated.name).toBe(newName); }); }); describe('addTag / removeTag', () => { it('should add tag to workflow', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Add Tag'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addTag', tag: 'new-tag' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; // Note: n8n API tag behavior may vary if (updated.tags) { expect(updated.tags).toContain('new-tag'); } }); it('should remove tag from workflow', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Remove Tag'), tags: ['mcp-integration-test', 'to-remove'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeTag', tag: 'to-remove' } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; if (updated.tags) { expect(updated.tags).not.toContain('to-remove'); } }); }); }); // ====================================================================== // ADVANCED SCENARIOS // ====================================================================== describe('Advanced Scenarios', () => { it('should apply multiple operations in sequence', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Multiple Ops'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addNode', node: { name: 'Set', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300], parameters: { assignments: { assignments: [] } } } }, { type: 'addConnection', source: 'Webhook', target: 'Set' }, { type: 'updateName', name: createTestWorkflowName('Partial - Multiple Ops Updated') } ] }, repository, mcpContext ); expect(response.success).toBe(true); const updated = response.data as any; expect(updated.nodes).toHaveLength(2); expect(updated.connections.Webhook).toBeDefined(); }); it('should validate operations without applying (validateOnly mode)', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Validate Only'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateName', name: 'New Name' } ], validateOnly: true }, repository, mcpContext ); expect(response.success).toBe(true); expect(response.data).toHaveProperty('valid', true); // Verify workflow was NOT actually updated const current = await client.getWorkflow(created.id); expect(current.name).not.toBe('New Name'); }); it('should handle continueOnError mode with partial failures', async () => { const workflow = { ...SIMPLE_WEBHOOK_WORKFLOW, name: createTestWorkflowName('Partial - Continue On Error'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Mix valid and invalid operations const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'updateName', name: createTestWorkflowName('Partial - Continue On Error Updated') }, { type: 'removeNode', nodeName: 'NonExistentNode' // This will fail }, { type: 'addTag', tag: 'new-tag' } ], continueOnError: true }, repository, mcpContext ); // Should succeed with partial results expect(response.success).toBe(true); expect(response.details?.applied).toBeDefined(); expect(response.details?.failed).toBeDefined(); }); }); // ====================================================================== // WORKFLOW STRUCTURE VALIDATION (prevents corrupted workflows) // ====================================================================== describe('Workflow Structure Validation', () => { it('should reject removal of all connections in multi-node workflow', async () => { // Create workflow with 2 nodes and 1 connection const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Reject Empty Connections'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to remove the only connection - should be rejected const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeConnection', source: 'Webhook', target: 'HTTP Request', sourcePort: 'main', targetPort: 'main' } ] }, repository, mcpContext ); // Should fail validation expect(response.success).toBe(false); expect(response.error).toContain('Workflow validation failed'); expect(response.details?.errors).toBeDefined(); expect(Array.isArray(response.details?.errors)).toBe(true); expect((response.details?.errors as string[])[0]).toContain('no connections'); }); it('should reject removal of all nodes except one non-webhook node', async () => { // Create workflow with 4 nodes: Webhook, Set 1, Set 2, Merge const workflow = { ...MULTI_NODE_WORKFLOW, name: createTestWorkflowName('Partial - Reject Single Non-Webhook'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to remove all nodes except Merge node (non-webhook) - should be rejected const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'removeNode', nodeName: 'Webhook' }, { type: 'removeNode', nodeName: 'Set 1' }, { type: 'removeNode', nodeName: 'Set 2' } ] }, repository, mcpContext ); // Should fail validation expect(response.success).toBe(false); expect(response.error).toContain('Workflow validation failed'); expect(response.details?.errors).toBeDefined(); expect(Array.isArray(response.details?.errors)).toBe(true); expect((response.details?.errors as string[])[0]).toContain('Single non-webhook node'); }); it('should allow valid partial updates that maintain workflow integrity', async () => { // Create workflow with 4 nodes const workflow = { ...MULTI_NODE_WORKFLOW, name: createTestWorkflowName('Partial - Valid Update'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Valid update: add a node and connect it const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addNode', node: { name: 'Process Data', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [850, 300], parameters: { assignments: { assignments: [] } } } }, { type: 'addConnection', source: 'Merge', target: 'Process Data', sourcePort: 'main', targetPort: 'main' } ] }, repository, mcpContext ); // Should succeed expect(response.success).toBe(true); const updated = response.data as any; expect(updated.nodes).toHaveLength(5); // Original 4 + 1 new expect(updated.nodes.find((n: any) => n.name === 'Process Data')).toBeDefined(); }); it('should reject adding node without connecting it (disconnected node)', async () => { // Create workflow with 2 connected nodes const workflow = { ...SIMPLE_HTTP_WORKFLOW, name: createTestWorkflowName('Partial - Reject Disconnected Node'), tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); expect(created.id).toBeTruthy(); if (!created.id) throw new Error('Workflow ID is missing'); context.trackWorkflow(created.id); // Try to add a third node WITHOUT connecting it - should be rejected const response = await handleUpdatePartialWorkflow( { id: created.id, operations: [ { type: 'addNode', node: { name: 'Disconnected Set', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [800, 300], parameters: { assignments: { assignments: [] } } } // Note: No connection operation - this creates a disconnected node } ] }, repository, mcpContext ); // Should fail validation - disconnected node detected expect(response.success).toBe(false); expect(response.error).toContain('Workflow validation failed'); expect(response.details?.errors).toBeDefined(); expect(Array.isArray(response.details?.errors)).toBe(true); const errorMessage = (response.details?.errors as string[])[0]; expect(errorMessage).toContain('Disconnected nodes detected'); expect(errorMessage).toContain('Disconnected Set'); }); }); });

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/88-888/n8n-mcp'

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