Skip to content

IA Router, Free Text and MCP Confirmation

Three N8N sub-workflows form the intelligent routing layer of the Telegram bot. The IA Router detects the intent of a free-text message and picks the target service. The Free Text Handler dispatches plain-text replies sent in answer to an interactive question opened by the cli-ollama API. The MCP Confirmation Handler triggers a Telegram approval flow before executing a critical MCP tool (create, update, delete N8N workflow).

These three workflows share the same philosophy: they carry no business logic of their own. Their job is to steer the main flow towards the right handler based on context.

WorkflowIDTypeTrigger
IA Router - Intent DetectionHWryf7aJkZa5FuUAwait-for-resultExecute Workflow from Orchestrator
Telegram Free Text HandlerxwGcEOpzHkvx0HTRfire-and-forgetExecute Workflow from Orchestrator
MCP Confirmation HandlerlyDYsxzTrDzZrfJkwebhookInternal POST /webhook/mcp-confirmation

None of these workflows is called directly by Telegram: they are internal entry points triggered by the Orchestrator or the cli-ollama gateway at the right moment.


Without this routing layer, the Orchestrator would have to carry the entire intent detection logic and the sensitive-action validation. The workflow size would explode, and a strategy change (adding an AI provider, changing the confirmation policy) would require touching the core of the central system.

ProblemWithout this layerWith this layer
Rigid intent detectionKeyword pattern matching onlyLLM with fallback chain plus regex as last resort
Provider overloadDirect call without quota check/api/usage/summary pre-computes the available provider
Orphan free-text repliesNo link between question and free-text answerquestion_mappings ties each expected message
Uncontrolled critical MCPAgent silently executes destructive toolsMandatory Telegram confirmation with 90s timeout
Telegram callback > 64 bytesSilent API errormcp_approve_{id} / mcp_reject_{id} (39 chars) below limit

Why split these three workflows rather than inline them in the Orchestrator?

ApproachProCon
InlineNo Execute Workflow overheadOrchestrator > 200 nodes, fragile editing
Extracted sub-workflowsIsolated refactoring, independent evolutionAPI surface between parent and sub to maintain

Refactoring #273 explicitly extracted the Free Text Handler as SW-8, and #295 delivered the MCP Confirmation Handler as a standalone webhook to decouple the cli-ollama gateway from the main graph.


WorkflowIDNodesRole
IA Router - Intent DetectionHWryf7aJkZa5FuUA13Multi-provider intent detection with fallback
Telegram Free Text HandlerxwGcEOpzHkvx0HTR10Dispatch free reply to /api/questions/{id}/answer
MCP Confirmation HandlerlyDYsxzTrDzZrfJk3Webhook + format + Telegram send with inline keyboard

MCP Confirmation Handler · 3 nodes

Conversation Agent

Free Text Handler · 10 nodes

IA Router · 13 nodes

no

yes

yes

no

critical tool

Telegram message

Orchestrator · 124 nodes

Active conversation?

Awaiting text reply?

GET /api/usage/summary

Switch fallback_chain[0]

Codex · codex-yolo

Gemini · gemini-flash-yolo

Keyword regex routing

Parse JSON response

DT question_mappings

POST /api/questions/{id}/answer

Edit message + delete DT row

Detect tool/plan/mcp blocks

MCP tool call

POST /webhook/mcp-confirmation

Format message + callbacks

Send Telegram + [Approve] [Reject]

Button click

SW-11 Conv Callback Handler

mcp-gateway:3001/confirm/{id}/respond

Tool executes or rejected

The workflow starts by hitting GET http://cli-ollama:11434/api/usage/summary which returns a pre-computed fallback_chain based on quotas and provider availability:

{
"can_use": true,
"fallback_chain": ["codex", "gemini"],
"providers": {
"codex": { "available": true, "can_use": true },
"gemini": { "available": true, "can_use": true }
}
}

The Switch node Route by Availability reads fallback_chain[0] and routes to the matching branch:

BranchProviderModelTypical confidence
codexOpenAI Codex via cli-ollamacodex-yolo0.85 - 0.95
geminiGoogle Gemini via cli-ollamagemini-flash-yolo0.80 - 0.90
keywordRegex fallback (no LLM)0.6 - 0.7

Each LLM branch calls POST /api/generate with a prompt that forces a strict JSON response of the form {"intent": ..., "service": ..., "action": ..., "params": {...}, "confidence": ...}. The Parse Response node extracts the JSON via regex, applies safe defaults, and returns everything to the parent.

When an interactive question is opened by cli-ollama (for instance: “what is the title of the new workflow?”), a row is inserted into the question_awaiting_text Data Table with a qid_short. At the next message the Orchestrator detects this state and delegates to SW-8:

Execute Workflow Trigger
-> Handle Free Text Answer (extract qid_short, chat_id, free_text)
-> Load Question for Answer (DT question_mappings by qid_short)
-> POST Free Text Answer (cli-ollama /api/questions/{id}/answer)
-> Cleanup Messages (prepare message_ids for edit)
-> Edit Header Free Text (edit original message with answer)
-> Loop Over Items
[0] -> Send Confirmation "Your answer has been forwarded"
[1] -> Delete Awaiting (DT question_awaiting_text)
-> Delete Question Mapping (DT question_mappings)

Two Data Tables are involved: question_awaiting_text (YYEsLEk9s3XDbo89) stores pending inputs, question_mappings (mXrPedR5PKYCUSpo) ties a qid_short to the Telegram message_id so the message holding the question can be edited retroactively.

MCP Confirmation Handler: approving critical tools

Section titled “MCP Confirmation Handler: approving critical tools”

The MCP gateway (mcp-gateway:3001) keeps a list of tools considered critical (any write operation against N8N: create, update, delete, activate, deactivate). When Codex CLI invokes one of these tools during a conversation, the gateway pauses execution, generates a confirmation_id (12 hex chars via secrets.token_hex(6)), and POSTs the following payload to /webhook/mcp-confirmation:

{
"type": "mcp_confirmation",
"confirmation_id": "mcp_a1b2c3d4e5f6",
"session_id": "tg_5883063462_1234567890",
"tool_name": "n8n_create_workflow",
"arguments_summary": "{\"name\": \"Test Workflow\", ...}",
"chatId": "5883063462",
"callback_url": "http://mcp-gateway:3001/confirm/mcp_a1b2c3d4e5f6/respond",
"timeout_seconds": 90
}

The workflow has three nodes:

  1. Webhook Trigger — Receives the POST in responseMode: immediately (200 OK direct)
  2. Format Confirmation Message (Code) — Builds the HTML text and the mcp_approve_{id} / mcp_reject_{id} callbacks
  3. Send Confirmation (Telegram sendMessage) — Sends the message with a 2-button inline keyboard
PatternMeaning
mcp_approve_{confirmation_id}Approve the critical tool execution
mcp_reject_{confirmation_id}Reject the execution

The total length of a mcp_approve_mcp_a1b2c3d4e5f6 callback is 39 characters, well under the Telegram 64-byte limit. The confirmation_id coming from secrets.token_hex(6) stays non-guessable, which prevents an attacker with access to the conversation from forging a callback for another confirmation_id.

Callbacks flow through the normal Telegram path: Orchestrator -> Callback Router -> SW-11 (Conversation Callback Handler, e0bLff6av97daYvi, 87 nodes). SW-11 detects the mcp_ prefix and routes to Parse MCP Action, which splits confirm actions (the approve/reject pair) from other MCP actions. For confirm two nodes are added:

  • Call MCP Confirm (HTTP Request) — POST to http://mcp-gateway:3001/confirm/{confirmation_id}/respond with body {"approved": true|false}, header Authorization: Bearer {{ $env.N8N_MCP_AUTH_TOKEN }}
  • Edit Confirm Result (Telegram editMessageText) — Replaces the original message with “MCP tool approved.” or “MCP tool rejected.”

Each conversation carries its own MCP config stored in the conversations Data Table (column mcp_config, JSON string). The /mcp command opens a paginated menu (SW-11) that lets the user enable or disable each tool. The derived mcp_enabled flag is computed by the Orchestrator on every turn:

  • If no tool is enabled: mcp_enabled = false -> Codex CLI is launched with -c 'mcp_servers={}' (no MCP server loaded)
  • If at least one tool is enabled: mcp_enabled = true + allowed_tools whitelist -> Codex can call tools, but cli-ollama filters unauthorized calls (403 if a tool is outside the list)

This per-conversation configuration allows a “read-only” conversation against N8N (list_workflows only) and an “admin” conversation on the same infrastructure, without mixing permissions.


LimitImpactMitigation
IA Router latency 2-5sFirst hop on every free-text messageAcceptable, masked by Telegram latency
Approximate keyword fallbackConfidence 0.6 - 0.7 when no LLM is availableWide regex patterns, route to general when in doubt
MCP timeout fixed at 90sNo per-tool configurationSufficient for human confirmation on mobile
Free Text not context-awareNo N8N-side validation of the expected formatcli-ollama validates downstream, error visible if malformed

If more LLM providers are needed:

  • Add a case in the Route by Availability Switch
  • Add a Prepare Prompt + Call Provider + Parse Response node trio
  • Update /api/usage/summary so it exposes the new provider in fallback_chain

If MCP pre-approval is needed:

  • Add a “whitelisted user” mode in the gateway that bypasses confirmation for pre-validated tools
  • Or add a per-conversation policy (auto-approve when mcp_auto_approve = true)

If IA Router precision needs measuring:

  • Log decisions into an intent_decisions Data Table (text, predicted_service, actual_service after feedback)
  • Compute weekly precision via a scheduled workflow

  • AI Stack — cli-ollama, MCP gateway, Codex CLI
  • Glossary — MCP, Intent, Callback, Sub-workflow