Triggering n8n workflows from Claude Desktop

Once Claude Desktop can see your n8n workflows, actually executing them with data is surprisingly tricky. This article tests three trigger types — MCP Server Trigger, Webhook, and Chat Trigger — and explains why only Chat Trigger reliably passes data through the management API.

Triggering n8n workflows from Claude Desktop

This is a follow-up to How to Connect Claude Desktop to a Self-Hosted n8n. That article covers the initial setup: Cloudflare Tunnel, supergateway, the OAuth bug, and getting Claude to see your workflows. This one picks up where it left off — actually executing workflows and passing data to them.

In the previous article, I mentioned two options for triggering workflows: Webhook triggers and the MCP Server Trigger node. Turns out both have problems when called through n8n's management API. I spent a while working through each approach before finding one that works. Here's the full rundown.

Prerequisites

You should already have the n8n MCP server configured in claude_desktop_config.json via supergateway, as described in the previous article:

{
  "mcpServers": {
    "n8n-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "supergateway",
        "--streamableHttp",
        "https://n8n.yourdomain.com/mcp-server/http",
        "--header",
        "Authorization: Bearer YOUR_MCP_ACCESS_TOKEN"
      ]
    }
  }
}

This gives Claude tools like execute_workflow, search_workflows, get_workflow_details, and publish_workflow. The management API can do a lot, but passing input data to a workflow turned out to be the tricky part.

What I tried first: MCP Server Trigger

The MCP Server Trigger seemed like the right choice. It's n8n's dedicated node for exposing workflows as MCP tools. You add it to a workflow, connect a Tool node to it, define your input parameters, and any MCP client should be able to call it.

The problem: the MCP Server Trigger creates its own SSE endpoint at a URL like https://n8n.yourdomain.com/mcp/<webhook-id>/sse. This is a completely separate MCP server from the management API. When I tried calling execute_workflow on a workflow with an MCP Server Trigger, n8n rejected it: "Only workflows with the following trigger nodes can be executed: Schedule Trigger, Webhook Trigger, Form Trigger, Chat Trigger."

The management API doesn't know how to talk to MCP Server Trigger workflows. They live in different worlds.

The MCP Server Trigger's other quirk

Even before hitting that error, I ran into a UI issue. The MCP Server Trigger has a "Tools" output, not a standard "main" output. You can't drag a connection from it to a regular Code or HTTP Request node. It only accepts Tool-type nodes: Code Tool, Call n8n Workflow Tool, and similar.

This means you usually end up splitting your logic into two workflows — one with the MCP Server Trigger that calls a second "sub-workflow" where the actual logic lives. It works, but it's more moving parts than I wanted.

You can still use it, but...

To use the MCP Server Trigger from Claude Desktop, you'd add a second MCP server entry in claude_desktop_config.json pointing at the SSE endpoint:

{
  "mcpServers": {
    "n8n-mcp": { "..." },
    "n8n-my-workflow": {
      "command": "npx",
      "args": [
        "-y", "supergateway",
        "--sse",
        "https://n8n.yourdomain.com/mcp/<webhook-id>/sse",
        "--header",
        "Authorization: Bearer YOUR_TRIGGER_BEARER_TOKEN"
      ]
    }
  }
}

Note the --sse flag instead of --streamableHttp the MCP Server Trigger uses SSE transport. This does work, but it means a separate config entry per workflow, each with its own bearer token. It gets messy fast if you have more than a couple of workflows.

What I tried next: Webhook Trigger

OK, so the management API supports Webhook triggers. My workflow already had one. I called:

execute_workflow({
  workflowId: "...",
  inputs: {
    type: "webhook",
    webhookData: {
      "text": "Hello from Claude"
    }
  }
})

The execution succeeded. The workflow ran. But every node downstream received an empty body. I added a Code node right after the webhook to inspect the data:

const items = $input.all();

return [{
  json: {
    first_item: items[0]?.json,
    body: items[0]?.json?.body,
    headers: items[0]?.json?.headers,
    query: items[0]?.json?.query
  }
}];

All empty: headers: {}, query: {}, body: {}. The webhookData I passed through execute_workflow never made it into the webhook node's output. I ran this test multiple times to be sure. The execution always "succeeds" but with no data.

o execute_workflow is a dead end for passing data through webhooks. But there's another way: Claude can make direct HTTP requests to the webhook URL. Instead of going through the management API, Claude calls the endpoint directly with a POST body, and the data arrives in the webhook node as expected — no JSON string parsing needed.

If your webhook uses JWT authentication (which it should), you need to give Claude the token at the start of every conversation. Claude has no persistent memory between sessions, so the token can't be saved once and reused later. Just paste it into the chat and Claude will use it for all webhook calls in that session.

What actually works: Chat Trigger

The Chat Trigger is the one that reliably passes data through execute_workflow. The call:

execute_workflow({
  workflowId: "your-workflow-id",
  executionMode: "production",
  inputs: {
    type: "chat",
    chatInput: "{ \"text\": \"Hello from Claude\"}"
  }
})

The chatInput field is a string, not an object. For structured data, you pass a JSON string and parse it in a Code node:

const items = $input.all();
const data = JSON.parse(items[0].json.chatInput);
return [{ json: { title: data.title, text: data.text } }];

After the Code node, {{ $json.title }} and {{ $json.text }} work normally in all downstream nodes. I verified this end to end — the data flows through Generate cover, Create Tweet, Create Thread, all of it.

Chat Trigger -> Code (parse JSON) -> your actual nodes

That's it. The Chat Trigger receives the chatInput string, the Code node unpacks it into proper fields, and everything downstream uses {{ $json.fieldName }} as usual.

If you want the same workflow callable from other sources too (webhooks, forms), n8n supports multiple triggers on one workflow. They work independently. Just know that the Webhook trigger won't receive data from execute_workflow use it for direct HTTP calls from other services, and use the Chat Trigger for Claude.

Other gotchas

Draft vs. published versions. Editing a workflow in the n8n editor doesn't update the production version. You have to click "Publish" before Claude picks up your changes.

chatInput is a string, not an object. There's no way around this. The Chat Trigger gives you one text field. For structured data, JSON-encode it and parse it yourself. No schema validation at the trigger level.

JWT tokens must be provided every session. If you use the direct HTTP approach with JWT-authenticated webhooks, you'll need to paste the token into each new conversation with Claude.

What's next

In the previous article I mentioned wanting to run a weekly news digest workflow that posts to social networks. With the Chat Trigger approach, Claude can now call that workflow and pass the digest text. The next step is wiring up cover image generation and creating blog draft posts, but the Claude-to-n8n bridge is finally solid.