Skip to content

feat(core): Track workflow action source for external API and MCP requests#28483

Open
RicardoE105 wants to merge 3 commits intomasterfrom
ado-5062-feature-track-workflow-actions-coming-from-outside-n8n
Open

feat(core): Track workflow action source for external API and MCP requests#28483
RicardoE105 wants to merge 3 commits intomasterfrom
ado-5062-feature-track-workflow-actions-coming-from-outside-n8n

Conversation

@RicardoE105
Copy link
Copy Markdown
Contributor

@RicardoE105 RicardoE105 commented Apr 14, 2026

Summary

Adds an optional source field to workflow telemetry events to track whether workflow actions originate from the n8n UI, public API, n8n CLI, or MCP server. Defaults to 'ui' when not specified.

Source values

Source Origin Detection
ui Editor frontend Default when not specified
api Public API (Postman, custom scripts, etc.) Set by public API middleware
n8n-cli n8n CLI package Auto-detected via User-Agent: n8n-cli header
n8n-mcp MCP server tools Set explicitly by each MCP tool handler

Events affected

Event Tracks
User created workflow Workflow creation
User saved workflow Workflow content updates
User activated workflow (new listener) Workflow publish
User deactivated workflow (new listener) Workflow unpublish

Changes

  • New WorkflowActionSource type'ui' | 'api' | 'n8n-mcp' | 'n8n-cli'
  • Optional source field on 4 workflow telemetry events — defaults to 'ui' in the telemetry relay, so existing code doesn't need to pass it
  • New telemetry listeners for workflow-activated and workflow-deactivated (previously missing — these events were emitted but never tracked)
  • n8n CLI detection — public API middleware checks User-Agent: n8n-cli and sets apiSource on the request, distinguishing CLI from other API consumers
  • apiSource on AuthenticatedRequest — typed as 'api' | 'n8n-cli', set by middleware, read by handlers — no type casts needed

How to test

  1. Create/update/publish/unpublish a workflow via the UI — telemetry events should have source: 'ui'
  2. Same via the public API — telemetry events should have source: 'api'
  3. Same via n8n CLI — telemetry events should have source: 'n8n-cli'
  4. Same via MCP tools — telemetry events should have source: 'n8n-mcp'

Related Linear tickets, Github issues, and Community forum posts

https://linear.app/n8n/issue/ADO-5062

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

…uests

Add a `source` field ('ui' | 'api' | 'n8n-mcp') to workflow telemetry
events to track whether workflow actions originate from the n8n UI,
public API, or MCP server.

- Add `WorkflowActionSource` type and `source` field to workflow-created,
  workflow-saved, workflow-activated, and workflow-deactivated events
- Thread `source` through service methods from each entry point
- Add missing telemetry listeners for workflow-activated and
  workflow-deactivated events
- MCP tools pass `source: 'n8n-mcp'`, public API passes `source: 'api'`,
  UI defaults to `source: 'ui'`
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

❌ 7 Tests Failed:

Tests completed Failed Passed Skipped
46426 7 46419 2
View the top 3 failed test(s) by shortest run time
create-workflow-from-code MCP tool handler tests passes provided projectId to service
Stack Traces | 0.001s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  {"connections": {}, "meta": {"aiBuilderAssisted": true}, "name": "Code Workflow", "nodes": [{"id": "node-1", "name": "Webhook", "parameters": {}, "position": [0, 0], "type": "n8n-nodes-base.webhook", "typeVersion": 1}, {"id": "node-2", "name": "Set", "parameters": {}, "position": [200, 0], "type": "n8n-nodes-base.set", "typeVersion": 1}], "pinData": {}, "settings": {"availableInMCP": true, "executionOrder": "v1"}},
  Object {
+   "parentFolderId": undefined,
    "projectId": "custom-project-id",
+   "source": "n8n-mcp",
  },

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/create-workflow-from-code.tool.test.ts:244:51)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
publish-workflow MCP tool handler tests successful publish publishes specific version when versionId provided
Stack Traces | 0.001s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  "wf-1",
  Object {
+   "source": "n8n-mcp",
    "versionId": "41f00e20-3710-4225-b67e-1cb2897687c3",
  },

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/publish-workflow.tool.test.ts:129:46)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
unpublish-workflow MCP tool handler tests successful unpublish unpublishes workflow successfully
Stack Traces | 0.002s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  "wf-1",
+ {"source": "n8n-mcp"},

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/unpublish-workflow.tool.test.ts:94:48)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
create-workflow-from-code MCP tool handler tests passes undefined projectId to service when not provided
Stack Traces | 0.003s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  {"connections": {}, "meta": {"aiBuilderAssisted": true}, "name": "Code Workflow", "nodes": [{"id": "node-1", "name": "Webhook", "parameters": {}, "position": [0, 0], "type": "n8n-nodes-base.webhook", "typeVersion": 1}, {"id": "node-2", "name": "Set", "parameters": {}, "position": [200, 0], "type": "n8n-nodes-base.set", "typeVersion": 1}], "pinData": {}, "settings": {"availableInMCP": true, "executionOrder": "v1"}},
  Object {
+   "parentFolderId": undefined,
    "projectId": undefined,
+   "source": "n8n-mcp",
  },

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/create-workflow-from-code.tool.test.ts:234:51)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
publish-workflow MCP tool handler tests successful publish publishes workflow successfully
Stack Traces | 0.003s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  "wf-1",
  Object {
+   "source": "n8n-mcp",
    "versionId": undefined,
  },

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/publish-workflow.tool.test.ts:98:46)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
update-workflow MCP tool handler tests passes correct workflowId to service
Stack Traces | 0.003s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"id": "user-1"},
  {"connections": {}, "meta": {"aiBuilderAssisted": true}, "name": "Updated Workflow", "nodes": [{"id": "node-1", "name": "Webhook", "parameters": {}, "position": [0, 0], "type": "n8n-nodes-base.webhook", "typeVersion": 1}, {"id": "node-2", "name": "Set", "parameters": {}, "position": [200, 0], "type": "n8n-nodes-base.set", "typeVersion": 1}], "pinData": {}},
  "custom-wf-id",
  Object {
    "aiBuilderAssisted": true,
+   "source": "n8n-mcp",
  },

Number of calls: 1
    at Object.<anonymous> (.../mcp/__tests__/update-workflow.tool.test.ts:247:35)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)
createWorkflow (public API) delegates to WorkflowCreationService with publicApi and strips projectId from the entity
Stack Traces | 0.004s run time
Error: expect(received).toEqual(expected) // deep equality

- Expected  - 0
+ Received  + 1

  Object {
    "projectId": "proj-1",
    "publicApi": true,
+   "source": "api",
  }
    at Object.<anonymous> (.../workflows/__tests__/workflows.service.test.ts:42:16)
    at processTicksAndRejections (node:internal/process/task_queues:104:5)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Add 'n8n-cli' to WorkflowActionSource type. The public API middleware
detects User-Agent: n8n-cli and sets apiSource on the request, so
workflow telemetry events distinguish CLI usage from other API consumers.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 14, 2026

Performance Comparison

Comparing currentlatest master14-day baseline

docker-stats

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
docker-image-size-n8n 1269.76 MB 1269.76 MB 1269.76 MB (σ 0.00) +0.0% +0.0%
docker-image-size-runners 393.00 MB 393.00 MB 391.63 MB (σ 11.06) +0.0% +0.3%

Idle baseline with Instance AI module loaded

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
instance-ai-rss-baseline 389.06 MB 388.20 MB 372.63 MB (σ 22.95) +0.2% +4.4%
instance-ai-heap-used-baseline 186.25 MB 186.52 MB 186.34 MB (σ 0.24) -0.1% -0.1%

Memory consumption baseline with starter plan resources

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
memory-heap-used-baseline 114.83 MB 114.05 MB 113.86 MB (σ 0.84) +0.7% +0.8% ⚠️
memory-rss-baseline 221.77 MB 287.98 MB 284.98 MB (σ 42.51) -23.0% -22.2% ⚠️
How to read this table
  • Current: This PR's value (or latest master if PR perf tests haven't run)
  • Latest Master: Most recent nightly master measurement
  • Baseline: Rolling 14-day average from master
  • vs Master: PR impact (current vs latest master)
  • vs Baseline: Drift from baseline (current vs rolling avg)
  • Status: ✅ within 1σ | ⚠️ 1-2σ | 🔴 >2σ regression

@RicardoE105 RicardoE105 marked this pull request as ready for review April 14, 2026 15:16
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/public-api/v1/handlers/workflows/workflows.handler.ts">

<violation number="1" location="packages/cli/src/public-api/v1/handlers/workflows/workflows.handler.ts:31">
P1: Custom agent flagged.

According to linked Linear issue ADO-5062, the implementation must generate a `request_id` that combines the source and a UUID (e.g., `api-<uuid>`) at the ingress point, and propagate this ID to telemetry events to correlate related actions. The current implementation only extracts and passes the source string, missing the unique identifier required by the Data Team's proposed solution for correlating downstream side-effects.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as External Client (UI/CLI/API)
    participant MCP as MCP Tool Handler
    participant Middleware as Public API Middleware
    participant Handler as Workflow Handlers
    participant Service as Workflow Service
    participant Events as Event Service
    participant Relay as Telemetry Relay

    Note over Client,Relay: Workflow Action Source Tracking Flow

    alt Request via Public API
        Client->>Middleware: HTTP Request
        Middleware->>Middleware: CHANGED: Check User-Agent header
        alt User-Agent is 'n8n-cli'
            Middleware->>Middleware: NEW: Set req.apiSource = 'n8n-cli'
        else Other API client
            Middleware->>Middleware: NEW: Set req.apiSource = 'api'
        end
        Middleware->>Handler: Forward Request
        Handler->>Service: Call method with source
    else Request via MCP
        MCP->>Service: NEW: Call method with source: 'n8n-mcp'
    else Request via UI (Legacy/Default)
        Client->>Handler: Direct API Call
        Handler->>Service: Call method (source undefined)
    end

    Service->>Service: Process Logic (Create/Save/Activate)
    Service->>Events: emit('workflow-saved' | 'workflow-activated', { ..., source })

    Note over Events,Relay: Event Processing

    Events->>Relay: Trigger listeners
    
    alt Event: Activated/Deactivated
        Relay->>Relay: NEW: Receive workflow-activated/deactivated
    else Event: Created/Saved
        Relay->>Relay: CHANGED: Process workflow-created/saved
    end

    Relay->>Relay: CHANGED: Extract source (default to 'ui')
    Relay->>Relay: telemetry.track('User ... workflow', { ..., source })

    Relay-->>Service: Done
    Service-->>Handler: Return Workflow
    Handler-->>Client: 200 OK / Response
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

@@ -28,7 +28,8 @@ export = {
createWorkflow: [
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent flagged.

According to linked Linear issue ADO-5062, the implementation must generate a request_id that combines the source and a UUID (e.g., api-<uuid>) at the ingress point, and propagate this ID to telemetry events to correlate related actions. The current implementation only extracts and passes the source string, missing the unique identifier required by the Data Team's proposed solution for correlating downstream side-effects.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/public-api/v1/handlers/workflows/workflows.handler.ts, line 31:

<comment>According to linked Linear issue ADO-5062, the implementation must generate a `request_id` that combines the source and a UUID (e.g., `api-<uuid>`) at the ingress point, and propagate this ID to telemetry events to correlate related actions. The current implementation only extracts and passes the source string, missing the unique identifier required by the Data Team's proposed solution for correlating downstream side-effects.</comment>

<file context>
@@ -28,7 +28,8 @@ export = {
 		publicApiScope('workflow:create'),
 		async (req: WorkflowRequest.Create, res: express.Response): Promise<express.Response> => {
-			const createdWorkflow = await createWorkflow(req.user, req.body);
+			const source = req.apiSource ?? 'api';
+			const createdWorkflow = await createWorkflow(req.user, req.body, source);
 			return res.json(createdWorkflow);
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant