Skip to main content
Glama

add_tracking

Add execution tracking to n8n workflows by storing data via HTTP requests, enabling monitoring of workflow progress, checkpoints, and outputs.

Instructions

Add generic workflow execution tracking to a workflow (stores data via HTTP requests)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesPath to the workflow file to add tracking to
storageUrlNoBase URL for storage API (or use WORKFLOW_STORAGE_URL env var)
optionsNoTracking options

Implementation Reference

  • Main execution logic for the 'add_tracking' tool. Loads the workflow, configures and uses TrackingInjector to add tracking nodes, persists the changes, and returns a summary of modifications.
    case 'add_tracking':
      const trackingPath = args?.path as string;
      const storageUrl = args?.storageUrl || process.env.WORKFLOW_STORAGE_URL;
      const trackingOptions = args?.options || {};
    
      if (!storageUrl) {
        return {
          content: [{
            type: 'text',
            text: '❌ Storage URL is required. Please provide storageUrl parameter or set WORKFLOW_STORAGE_URL environment variable.'
          }]
        };
      }
    
      // Read workflow
      const fullTrackingPath = path.join(this.workflowsPath, trackingPath);
      const workflowContent = await fs.readFile(fullTrackingPath, 'utf-8');
      const workflow = JSON.parse(workflowContent);
    
      // Create injector with configuration
      const injector = new TrackingInjector({
        enabled: true,
        storageUrl,
        enableCheckpoints: trackingOptions.checkpoints?.length > 0,
        enableErrorTracking: trackingOptions.addErrorTracking
      });
    
      // Inject tracking
      const trackedWorkflow = await injector.injectTracking(workflow, trackingOptions);
    
      // Save modified workflow
      await fs.writeFile(fullTrackingPath, JSON.stringify(trackedWorkflow, null, 2));
    
      // Count added nodes
      const addedNodes = trackedWorkflow.nodes.length - workflow.nodes.length;
    
      return {
        content: [{
          type: 'text',
          text: `✅ Added ${addedNodes} tracking nodes to workflow\\n\\n` +
            `Storage URL: ${storageUrl}\\n` +
            `Start tracking: ${trackingOptions.addStartTracking !== false ? 'Yes' : 'No'}\\n` +
            `End tracking: ${trackingOptions.addEndTracking !== false ? 'Yes' : 'No'}\\n` +
            `Error tracking: ${trackingOptions.addErrorTracking ? 'Yes' : 'No'}\\n` +
            `Checkpoints: ${trackingOptions.checkpoints?.length || 0}\\n` +
            `Stored outputs: ${trackingOptions.storeOutputNodes?.length || 0}`
        }]
      };
  • Tool registration object defining the 'add_tracking' tool name, description, and detailed input schema for MCP integration.
      name: 'add_tracking',
      description: 'Add generic workflow execution tracking to a workflow (stores data via HTTP requests)',
      inputSchema: {
        type: 'object',
        properties: {
          path: {
            type: 'string',
            description: 'Path to the workflow file to add tracking to',
          },
          storageUrl: {
            type: 'string',
            description: 'Base URL for storage API (or use WORKFLOW_STORAGE_URL env var)',
          },
          options: {
            type: 'object',
            properties: {
              addStartTracking: {
                type: 'boolean',
                description: 'Add tracking at workflow start (default: true)',
              },
              addEndTracking: {
                type: 'boolean',
                description: 'Add tracking at workflow end (default: true)',
              },
              addErrorTracking: {
                type: 'boolean',
                description: 'Add error tracking with error trigger',
              },
              checkpoints: {
                type: 'array',
                items: {
                  type: 'object',
                  properties: {
                    afterNode: {
                      type: 'string',
                      description: 'Node name to add checkpoint after',
                    },
                    checkpointName: {
                      type: 'string',
                      description: 'Name for this checkpoint',
                    },
                  },
                },
                description: 'Checkpoints to add after specific nodes',
              },
              storeOutputNodes: {
                type: 'array',
                items: { type: 'string' },
                description: 'Node names whose outputs should be stored',
              },
            },
            description: 'Tracking options',
          },
        },
        required: ['path'],
      },
    },
  • Core helper class responsible for injecting tracking nodes (start/end/error/checkpoint/storage) into n8n workflow JSON by analyzing structure, creating HTTP Request nodes, and updating connections.
    export class TrackingInjector {
      private tracker: WorkflowTracker;
    
      constructor(config: TrackingConfig) {
        this.tracker = new WorkflowTracker(config);
      }
    
      /**
       * Inject tracking into a workflow with intelligent placement
       */
      async injectTracking(workflow: any, options: InjectionOptions = {}): Promise<any> {
        const modifiedWorkflow = JSON.parse(JSON.stringify(workflow)); // Deep clone
        
        if (!this.tracker.isConfigured()) {
          return modifiedWorkflow;
        }
    
        // Ensure connections object exists
        if (!modifiedWorkflow.connections) {
          modifiedWorkflow.connections = {};
        }
    
        // Find entry and exit points
        const entryNodes = this.findEntryNodes(modifiedWorkflow);
        const exitNodes = this.findExitNodes(modifiedWorkflow);
    
        // Add start tracking
        if (options.addStartTracking !== false && entryNodes.length > 0) {
          this.addStartTracking(modifiedWorkflow, entryNodes);
        }
    
        // Add end tracking
        if (options.addEndTracking !== false && exitNodes.length > 0) {
          this.addEndTracking(modifiedWorkflow, exitNodes);
        }
    
        // Add error tracking
        if (options.addErrorTracking) {
          this.addErrorTracking(modifiedWorkflow);
        }
    
        // Add checkpoints
        if (options.checkpoints && options.checkpoints.length > 0) {
          this.addCheckpoints(modifiedWorkflow, options.checkpoints);
        }
    
        // Add node storage
        if (options.storeOutputNodes && options.storeOutputNodes.length > 0) {
          this.addNodeStorage(modifiedWorkflow, options.storeOutputNodes);
        }
    
        return modifiedWorkflow;
      }
    
      /**
       * Find entry nodes (triggers, manual starts, webhooks)
       */
      private findEntryNodes(workflow: any): string[] {
        const entryNodes: string[] = [];
        
        for (const node of workflow.nodes) {
          // Check if node is a trigger or start node
          if (
            node.type.includes('trigger') ||
            node.type.includes('webhook') ||
            node.type === 'n8n-nodes-base.manualTrigger' ||
            node.type === 'n8n-nodes-base.start' ||
            node.type === 'n8n-nodes-base.scheduleTrigger'
          ) {
            entryNodes.push(node.name);
          }
        }
    
        // If no triggers found, find nodes with no incoming connections
        if (entryNodes.length === 0) {
          const nodesWithIncoming = new Set<string>();
          
          for (const [_, targets] of Object.entries(workflow.connections || {})) {
            const targetList = targets as any;
            for (const targetArray of Object.values(targetList)) {
              for (const connections of targetArray as any[]) {
                for (const connection of connections) {
                  nodesWithIncoming.add(connection.node);
                }
              }
            }
          }
    
          for (const node of workflow.nodes) {
            if (!nodesWithIncoming.has(node.name)) {
              entryNodes.push(node.name);
            }
          }
        }
    
        return entryNodes;
      }
    
      /**
       * Find exit nodes (nodes with no outgoing connections)
       */
      private findExitNodes(workflow: any): string[] {
        const exitNodes: string[] = [];
        const hasOutgoing = new Set<string>();
    
        for (const nodeName of Object.keys(workflow.connections || {})) {
          if (workflow.connections[nodeName]?.main?.length > 0) {
            hasOutgoing.add(nodeName);
          }
        }
    
        for (const node of workflow.nodes) {
          if (!hasOutgoing.has(node.name)) {
            exitNodes.push(node.name);
          }
        }
    
        return exitNodes;
      }
    
      /**
       * Add start tracking after entry nodes
       */
      private addStartTracking(workflow: any, entryNodes: string[]) {
        const trackingNode = this.tracker.createStartTrackingNode();
        
        // Position tracking node after first entry node
        const firstEntry = workflow.nodes.find((n: any) => n.name === entryNodes[0]);
        if (firstEntry) {
          trackingNode.node.position = [
            firstEntry.position[0] + 250,
            firstEntry.position[1]
          ];
        }
    
        // Add tracking node to workflow
        workflow.nodes.push(trackingNode.node);
    
        // Connect entry nodes to tracking node
        for (const entryName of entryNodes) {
          const existingConnections = workflow.connections[entryName]?.main?.[0] || [];
          
          // Insert tracking node between entry and its targets
          workflow.connections[trackingNode.node.name] = {
            main: [existingConnections]
          };
          
          // Connect entry to tracking node
          workflow.connections[entryName] = {
            main: [[{ node: trackingNode.node.name, type: 'main', index: 0 }]]
          };
        }
      }
    
      /**
       * Add end tracking before exit nodes
       */
      private addEndTracking(workflow: any, exitNodes: string[]) {
        for (const exitName of exitNodes) {
          const exitNode = workflow.nodes.find((n: any) => n.name === exitName);
          if (!exitNode) continue;
    
          const trackingNode = this.tracker.createEndTrackingNode();
          
          // Position tracking node after exit node
          trackingNode.node.position = [
            exitNode.position[0] + 250,
            exitNode.position[1]
          ];
    
          // Make unique name for each end tracking node
          trackingNode.node.name = `Track End - ${exitName}`;
          trackingNode.node.id = `track_end_${exitName.replace(/\s+/g, '_').toLowerCase()}_${Date.now()}`;
    
          // Add tracking node to workflow
          workflow.nodes.push(trackingNode.node);
    
          // Connect exit node to tracking node
          if (!workflow.connections[exitName]) {
            workflow.connections[exitName] = {};
          }
          workflow.connections[exitName].main = [
            [{ node: trackingNode.node.name, type: 'main', index: 0 }]
          ];
        }
      }
    
      /**
       * Add error tracking with error trigger
       */
      private addErrorTracking(workflow: any) {
        // Add error trigger node
        const errorTrigger = {
          parameters: {},
          id: 'error_trigger_' + Date.now(),
          name: 'Error Trigger',
          type: 'n8n-nodes-base.errorTrigger',
          typeVersion: 1,
          position: [100, 400]
        };
    
        // Add error tracking node
        const trackingNode = this.tracker.createErrorTrackingNode();
        trackingNode.node.position = [350, 400];
    
        // Add nodes to workflow
        workflow.nodes.push(errorTrigger);
        workflow.nodes.push(trackingNode.node);
    
        // Connect error trigger to tracking node
        workflow.connections[errorTrigger.name] = {
          main: [[{ node: trackingNode.node.name, type: 'main', index: 0 }]]
        };
      }
    
      /**
       * Add checkpoint nodes after specific nodes
       */
      private addCheckpoints(workflow: any, checkpoints: Array<{ afterNode: string; checkpointName: string }>) {
        for (const checkpoint of checkpoints) {
          const targetNode = workflow.nodes.find((n: any) => n.name === checkpoint.afterNode);
          if (!targetNode) continue;
    
          // Create checkpoint node
          const checkpointNode = this.tracker.createCheckpointNode(
            checkpoint.checkpointName,
            {
              x: targetNode.position[0] + 250,
              y: targetNode.position[1] + 50
            }
          );
    
          // Add to workflow
          workflow.nodes.push(checkpointNode.node);
    
          // Insert checkpoint between target and its connections
          const existingConnections = workflow.connections[checkpoint.afterNode]?.main?.[0] || [];
          
          // Connect checkpoint to target's destinations
          if (existingConnections.length > 0) {
            workflow.connections[checkpointNode.node.name] = {
              main: [existingConnections]
            };
          }
          
          // Connect target to checkpoint
          workflow.connections[checkpoint.afterNode] = {
            main: [[{ node: checkpointNode.node.name, type: 'main', index: 0 }]]
          };
        }
      }
    
      /**
       * Add storage nodes after specific nodes
       */
      private addNodeStorage(workflow: any, nodeNames: string[]) {
        for (const nodeName of nodeNames) {
          const targetNode = workflow.nodes.find((n: any) => n.name === nodeName);
          if (!targetNode) continue;
    
          // Create storage node
          const storageNode = this.tracker.createNodeStorageNode(
            nodeName,
            {
              x: targetNode.position[0] + 150,
              y: targetNode.position[1] + 100
            }
          );
    
          // Add to workflow
          workflow.nodes.push(storageNode.node);
    
          // Insert storage node in parallel (not blocking main flow)
          if (!workflow.connections[nodeName]) {
            workflow.connections[nodeName] = { main: [[]] };
          }
          
          // Add storage node as additional output
          const existingConnections = workflow.connections[nodeName].main[0] || [];
          existingConnections.push({ node: storageNode.node.name, type: 'main', index: 0 });
          workflow.connections[nodeName].main[0] = existingConnections;
        }
      }
    
      /**
       * Add checkpoint restore capability at workflow start
       */
      async addCheckpointRestore(workflow: any, checkpointName: string): Promise<any> {
        const modifiedWorkflow = JSON.parse(JSON.stringify(workflow));
    
        // Create checkpoint restore node
        const restoreNode = this.tracker.createCheckpointRestoreNode(
          checkpointName,
          { x: 100, y: 200 }
        );
    
        // Create IF node to check if checkpoint exists
        const ifNode = {
          parameters: {
            conditions: {
              boolean: [
                {
                  value1: '={{$json.checkpointData}}',
                  operation: 'isNotEmpty'
                }
              ]
            }
          },
          id: 'if_checkpoint_' + Date.now(),
          name: `Check ${checkpointName} Exists`,
          type: 'n8n-nodes-base.if',
          typeVersion: 2,
          position: [350, 200]
        };
    
        // Add nodes
        modifiedWorkflow.nodes.push(restoreNode);
        modifiedWorkflow.nodes.push(ifNode);
    
        // Connect restore to IF
        modifiedWorkflow.connections[restoreNode.name] = {
          main: [[{ node: ifNode.name, type: 'main', index: 0 }]]
        };
    
        // IF node will have two outputs: true (has checkpoint) and false (no checkpoint)
        // These need to be connected to appropriate workflow paths
    
        return modifiedWorkflow;
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions that tracking 'stores data via HTTP requests', which hints at external communication, but doesn't clarify what data is stored, whether this modifies the workflow file, what permissions are needed, error handling, or rate limits. For a tool that appears to modify workflows and make HTTP calls, this leaves significant behavioral gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that states the core purpose without unnecessary words. It's appropriately sized and front-loaded with the essential information, making it easy for an agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 3 parameters (including a nested object), no annotations, and no output schema, the description is insufficient. It doesn't explain what the tool returns, how the tracking integrates with the workflow, what the HTTP storage entails, or potential side effects. The high parameter complexity and lack of structured metadata require more descriptive context than provided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds no additional parameter semantics beyond what's in the schema. This meets the baseline of 3 where the schema does the heavy lifting, but the description doesn't compensate with any extra context about parameter interactions or usage examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('add generic workflow execution tracking') and the target resource ('to a workflow'), with the additional detail that it 'stores data via HTTP requests'. This provides a specific verb+resource combination, though it doesn't explicitly differentiate from sibling tools like 'configure_tracking' or 'add_checkpoint'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'configure_tracking' or 'add_checkpoint'. There's no mention of prerequisites, typical use cases, or exclusions. The agent must infer usage from the tool name and description alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/mckinleymedia/mcflow-mcp'

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