What I’m trying to do
I have a centralized Tool Hub workflow that exposes ~7 tools via the MCP Server Trigger node (@n8n/n8n-nodes-langchain.mcpTrigger). Each tool is a toolWorkflow node wired to the trigger, calling real sub-workflows (Send Email, Schedule Manager, Generate Document, Web Search).
A separate Agent workflow connects to this hub through the MCP Client Tool node (mcpClientTool, bearer auth). The agent’s webhook receives { message, userId, sessionId, timezone } and stores them in a Set node (“GetInputs”).
Several tools need the authenticated userId and the chat sessionId (to resolve the email recipient server-side, scope schedules to the owner, etc.).
The problem
Right now those identity fields are filled by the LLM via $fromAI('userId', ...) / $fromAI('sessionId', ...) on the Tool Hub’s toolWorkflow nodes. This is unreliable — the model sometimes hallucinates or transposes characters in the UUIDs, which can route actions to the wrong user/session. These values are pure context; the LLM should never choose or retype them.
Goal: completely remove userId/sessionId from the LLM-filled tool inputs and instead inject them from the client/agent execution context, while the Tool Hub stays exactly as is (centralized, exposed over MCP Server Trigger — I do NOT want to attach the tool sub-workflows directly to the agent).
What I’ve already checked (and why it doesn’t work)
- Custom HTTP headers on the MCP Client Tool → the MCP Server Trigger doesn’t expose incoming request headers to the tool sub-workflows. (Open feature request: community thread “Access HTTP Headers and URL Params in MCP Node”, still unresolved as of Feb 2026.)
- URL / query params on the endpoint → same limitation, not exposed to the workflow.
- Bearer token → static credential, shared across all users, not per-session, and not surfaced to the tools.
- Pinning a fixed value on the client side → the tool input schema is defined server-side (the
$fromAI lives in the Tool Hub’s toolWorkflow nodes), so the mcpClientTool can’t override/pin a parameter with a static expression.
My question
With the Tool Hub kept behind an MCP Server Trigger, is there any supported way to pass per-connection / per-session context values into the tool sub-workflows without involving the LLM? Specifically:
- Can the MCP Server Trigger read the incoming connection’s headers / query params / a session identifier today (any node version)? If yes, how do I reference them inside a
toolWorkflow?
- Can the MCP Client Tool pin certain tool parameters to fixed expression values (e.g.
={{ $('GetInputs').item.json.sessionId }}) so the LLM never fills them?
- Is there a recommended pattern to correlate a server-side session store with an MCP call (some stable key the trigger receives) that I can key off instead of trusting LLM-passed args?
- Is the header/param feature on the roadmap, or is there a known workaround I’m missing?
Environment
- n8n version: 2.27.4 (n8n Cloud)
- Nodes:
@n8n/n8n-nodes-langchain.mcpTrigger, @n8n/n8n-nodes-langchain.mcpClientTool, @n8n/n8n-nodes-langchain.toolWorkflow
- Running mode: Cloud
Thanks!
Hey @sawsew467, short answer is no, the MCP Server Trigger doesn’t expose headers/params to sub-workflows today, your research is correct.
Workaround that fits your setup: before calling the MCP tool, have your Agent write { userId, sessionId, timezone } to a Redis/DB row keyed by a short correlation ID. Pass only that ID via $fromAI (one short token = far less hallucination risk than a full UUID). Each toolWorkflow then looks up the real values from that ID instead of trusting the LLM’s copy.
To your questions: 1) No. 2) No, can’t pin server-side schema fields from the client. 3) Yes, correlation ID + lookup table is the current best pattern. 4) On their radar (per that feature request thread), no ETA yet.
Adds one lookup per call, but kills identity-spoofing from transcription errors entirely.
Hi @sawsew467 , Let me add some implementation detail and a security layer.
Making the correlation ID pattern safe:
The risk with any LLM-passed identifier is a hallucinated ID hitting another user’s context. Fix that by scoping the lookup:
- In your Agent workflow, before the AI Agent node runs, generate a short correlation ID (6-char alphanumeric, e.g.
{{ Math.random().toString(36).substring(2, 8) }}) and write { correlationId, userId, sessionId, timezone, expiresAt } to a Postgres row. Set expiresAt to ~30 minutes.
- Inject the correlation ID into the Agent’s system prompt, not just $fromAI:
"Your session context key is: abc123. Pass this EXACT value as the contextKey parameter in every tool call. Never modify it."
- On the Tool Hub side, each toolWorkflow starts with a Postgres lookup: query by correlationId WHERE expiresAt > now(). If not found, the tool returns an error instead of operating on the wrong user. Expired or hallucinated IDs fail cleanly.
This means the LLM still passes one short token, but userId/sessionId never touch the LLM at all. And a wrong token causes a safe failure, not a wrong-user action.
If zero LLM involvement is truly non-negotiable:
The only way to remove the LLM from identity completely is to drop MCP for the tool dispatch and use an Execute Sub-Workflow router pattern instead. Build one “dispatcher” sub-workflow that receives { toolName, userId, sessionId, ...params } via Execute Workflow node from the Agent, then routes to the right tool sub-workflow internally. You keep centralization (one router, tools behind it) and get full context passing with zero LLM involvement. You lose MCP protocol compatibility, but your current architecture is n8n-to-n8n anyway, so the protocol layer isn’t buying you much.
Up to you whether the tradeoff is worth it for your setup. The correlation ID pattern is solid and practical if you’re OK with the LLM copying one short token.
Hi @sawsew467 ,
You’ve hit a current engine limitation in n8n’s MCP implementation:
- Headers/Params: mcpTrigger strips incoming HTTP request headers/params before delegating to toolWorkflow, so sub-workflows cannot access them.
- Parameter Pinning: mcpClientTool cannot override server-side tool schemas dynamically with client-side expressions.
Here are the 3 best workarounds to solve UUID hallucination right now:
- 1. System Instruction Hardening (Easiest): Inject userId and sessionId directly into the Agent’s System Message (e.g., Current User ID: “{{ $(‘GetInputs’).item.json.userId }}”). When the LLM has the exact string in its immediate system prompt context, character transposition drops to virtually 0%.
- 2. Short Session Tokens: Map long UUIDs to short alphanumeric keys (e.g., SESS-8921) in Redis/Postgres. Pass SESS-8921 to the LLM, and resolve the actual userId inside your tool sub-workflows via database lookup.
- 3. Native toolWorkflow (If inside same n8n): If both workflows are in n8n, attach tools directly via toolWorkflow instead of MCP. Expression parameters set on native tool nodes are evaluated at execution time and hidden from the LLM schema entirely.
Hope this helps clarify the current state!
Thanks!
Strong answers already. One reframe that changes how you weigh them: the failure isn’t only the model mis-typing an ID (transposition), it’s the model being talked into passing a different valid one. Any tool that returns outside content (web search results, an email body the agent reads back) is untrusted input that can tell the model to swap the contextKey on its next call. houda_ben’s expiry + clean-fail catches a hallucinated or expired token; it doesn’t catch a live token that got deliberately swapped.
Two things follow:
-
The short 6-char / SESS-#### tokens are guessable, so past hallucination there’s a confused-deputy path: pass a neighbor’s live token. If you keep the correlation-token route, make the token long and unguessable and bind it to the session server-side (the tool checks the token maps to the same session that authenticated the MCP call), so a token lifted from another session fails even though it’s valid.
-
This is exactly why the two zero-LLM options already posted (Websensepro’s native toolWorkflow, houda_ben’s Execute Sub-Workflow router) actually meet your stated goal: they don’t just cut transposition, they take the token out of the model’s hands entirely, which closes the injection-swap too. For the userId/sessionId-scoped tools specifically, that tradeoff is worth it. Want to keep the hub? Split it: identity tools via the router/native path, everything else behind MCP.