apply_ops
Apply multiple graph operations atomically to a workflow, enabling batch updates to nodes, connections, parameters, and properties in a single transaction.
Instructions
Apply multiple graph operations atomically to a workflow
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ops | Yes | Array of operations to apply | |
| workflowId | Yes | The workflow ID |
Implementation Reference
- src/index.ts:625-635 (handler)Main handler method for the 'apply_ops' tool. Resolves workflow ID, delegates to N8nClient.applyOperations, handles success/error responses.private async handleApplyOps(args: ApplyOpsRequest) { const workflowId = this.resolveWorkflowId(args.workflowId); const result = await this.n8nClient.applyOperations(workflowId, args.ops); if (result.success) { if (result.workflow) this.withAlias(result.workflow); return { content: [{ type: 'text', text: JSON.stringify(jsonSuccess(result.workflow), null, 2) }] }; } else { return { content: [{ type: 'text', text: JSON.stringify(jsonError('Operations failed', 'APPLY_OPS_FAILED', { errors: result.errors }), null, 2) }] }; } }
- src/index.ts:76-101 (registration)Tool registration in the listTools response, including name, description, and input schema.{ name: 'apply_ops', description: 'Apply multiple graph operations atomically to a workflow', inputSchema: { type: 'object', properties: { workflowId: { oneOf: [{ type: 'string' }, { type: 'number' }], description: 'The workflow ID' }, ops: { type: 'array', description: 'Array of operations to apply', items: { type: 'object', properties: { type: { type: 'string', enum: ['addNode', 'deleteNode', 'updateNode', 'setParam', 'unsetParam', 'connect', 'disconnect', 'setWorkflowProperty', 'addTag', 'removeTag'], description: 'The type of operation' } }, required: ['type'] } } }, required: ['workflowId', 'ops'], }, },
- src/types.ts:389-418 (schema)Type definitions for PatchOperation (union of all ops), ApplyOpsRequest, ApplyOpsResponse, and OperationError.export type PatchOperation = | AddNodeOperation | DeleteNodeOperation | UpdateNodeOperation | SetParamOperation | UnsetParamOperation | ConnectOperation | DisconnectOperation | SetWorkflowPropertyOperation | AddTagOperation | RemoveTagOperation; export interface ApplyOpsRequest { workflowId: string | number; ops: PatchOperation[]; } export interface OperationError { operationIndex: number; operation: PatchOperation; error: string; details?: any; } export interface ApplyOpsResponse { success: boolean; workflow?: N8nWorkflow; errors?: OperationError[]; }
- src/operations.ts:25-73 (helper)Core operations processor: applies batch of PatchOperations atomically to workflow copy, fails fast on errors, returns mutated workflow or errors.async applyOperations(workflow: N8nWorkflow, operations: PatchOperation[] | undefined | null): Promise<ApplyOpsResponse> { // Create a deep copy of the workflow to work with const workflowCopy = JSON.parse(JSON.stringify(workflow)) as N8nWorkflow; const errors: OperationError[] = []; try { // Normalize operations to an empty array if undefined/null const ops = Array.isArray(operations) ? operations : []; // Apply each operation for (let i = 0; i < ops.length; i++) { const operation = ops[i]; try { this.applyOperation(workflowCopy, operation); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push({ operationIndex: i, operation, error: errorMessage, details: error instanceof Error ? error.stack : undefined }); // Atomic behavior: if any operation fails, return errors without applying changes return { success: false, errors }; } } // All operations succeeded return { success: true, workflow: workflowCopy }; } catch (error) { // Unexpected error during processing const errorMessage = error instanceof Error ? error.message : 'Unknown error during batch processing'; return { success: false, errors: [{ operationIndex: -1, operation: (Array.isArray(operations) && operations.length > 0 ? operations[0] : ({ type: 'unknown' } as any)), error: errorMessage }] }; } }
- src/n8n-client.ts:313-371 (helper)N8nClient bridge method: fetches current workflow, processes ops, persists via updateWorkflow with conflict handling.async applyOperations(workflowId: string | number, operations: PatchOperation[]): Promise<ApplyOpsResponse> { try { // Get the current workflow const currentWorkflow = await this.getWorkflow(workflowId); // Apply operations using the processor const result = await this.operationsProcessor.applyOperations(currentWorkflow, operations); if (!result.success) { return result; } // If operations were successful, update the workflow try { const updatedWorkflow = await this.updateWorkflow(workflowId, result.workflow!); return { success: true, workflow: updatedWorkflow }; } catch (error) { // Handle version drift or other update errors const errorMessage = error instanceof Error ? error.message : 'Failed to update workflow'; // Check if it's a version drift error (this depends on n8n's error response format) if (errorMessage.includes('version') || errorMessage.includes('conflict') || errorMessage.includes('409')) { return { success: false, errors: [{ operationIndex: -1, operation: operations[0], // fallback error: 'Version drift detected: workflow was modified by another process', details: errorMessage }] }; } return { success: false, errors: [{ operationIndex: -1, operation: operations[0], // fallback error: `Failed to save workflow: ${errorMessage}`, details: error }] }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, errors: [{ operationIndex: -1, operation: operations[0] || { type: 'unknown' } as any, error: `Failed to retrieve workflow: ${errorMessage}`, details: error }] }; } }