Google ADK · MCP · n8n · Cross-Protocol Architecture

Workflow-Driven
Triage Agent

I built a personal experiment that sorts support requests and gathers information before routing them by priority.

Google ADK · Gemini 2.5 Flash 3 MCP tools · stdio transport n8n workflow · 3 routes docker compose up
At a glance 30-second read

What it is

I connected an n8n workflow to an AI assistant and three tools: classify a request, find customer history, and search a knowledge base. The workflow routes the request using the assistant’s result. Technical setup details are below.

What I explored

I explored how to divide a support workflow between fixed steps and an AI assistant.

Skills and tools

  • Google ADK agent definition + tool registration
  • MCP server (FastMCP) + stdio client transport
  • n8n workflow design — webhook + Switch + multi-output routing
  • FastAPI wrapper around an async agent runtime
  • Vertex AI or Gemini API — env-var swap, no code change
  • Cross-protocol architecture (ADK + MCP + n8n)
Google ADK Gemini 2.5 MCP n8n FastAPI Docker Compose Python
01 — The Three Pieces each picked for what it's best at

How the parts work together.

I used n8n for the workflow, Google’s Agent Development Kit for the assistant, and Model Context Protocol for its tools. Each part has a separate job.

Workflow Layer
n8n
Workflow runtime · ops-team-readable

Webhook intake, branching on the agent's decision, downstream HTTP calls — helpdesk reply, triage queue, Slack escalation. Exported as JSON so the workflow lives in version control alongside the agent code.

Agent Layer
Google ADK
Gemini 2.5 Flash · structured tool use

A single Agent with three tools and a system instruction that pins the output schema. Vertex AI for production; Gemini API for dev. Env-var switch, no code change.

Tool Layer
MCP
FastMCP · stdio transport

Three tools defined once: retrieval (customer history, KB search) and critique (intent classification). MCP means the same tools can be wired into Claude Desktop, Cursor, or another agent runtime without rewriting.

02 — Architecture end-to-end data flow

From webhook to routed response.

One ticket in, one of three outcomes: auto-reply with a drafted response (pending human approval), into the triage queue with the agent's category, or paged to Slack on escalation. The agent's decision shape is the contract.

data flow · request → decision → route [ ARCHITECTURE ]
┌─────────────┐         ┌─────────────┐         ┌──────────────────┐
│  Helpdesk   │ POST    │             │ POST    │  ADK Agent       │
│  webhook    │ ──────▶ │     n8n     │ ──────▶ │ (Gemini 2.5 Flash)│
└─────────────┘         │   workflow  │         │                  │
                        │             │         │  Tools via MCP:  │
                        │  webhook    │         │   - classify     │
                        │     │       │         │   - get_history  │
                        │     ▼       │         │   - search_kb    │
                        │  http call  │         └──────────────────┘
                        │     │       │                  ▲
                        │     ▼       │                  │ stdio
                        │   switch    │         ┌────────┴─────────┐
                        │   /     \   │         │  MCP server      │
                        │  /   |   \  │         │  (FastMCP)       │
                        │ auto queue  │         │                  │
                        │ reply  esc  │         │  data/           │
                        └─────┬─┬─┬───┘         │   customers.json │
                              │ │ │             │   kb.json        │
                  ┌───────────┘ │ └─────────┐   └──────────────────┘
                  ▼             ▼           ▼
            ┌─────────┐   ┌─────────┐  ┌──────────┐
            │Helpdesk │   │ Triage  │  │  Slack   │
            │  reply  │   │  queue  │  │ #escal-  │
            │ (gated) │   │         │  │ ations   │
            └─────────┘   └─────────┘  └──────────┘
03 — A Real Run critical incident scenario

Walk-through, one ticket.

Customer C-1008 (Heliotrope Media · Enterprise) reports the API is returning 503s for the last 20 minutes. Here's the event sequence the agent and n8n produce.

01
Helpdesk → n8n webhook
POST to /webhook/support-triage with the ticket payload (id, customer_id, subject, body).
02
n8n → ADK agent
HTTP node forwards to http://adk-agent:8080/triage. n8n waits up to 30s for the agent's structured response.
03
ADK agent → MCP classify_intent
Returns {intent: "outage", category: "incident", priority: "critical", matched_signals: ["503", "slow", "down"]}. The agent has its baseline.
04
ADK agent → MCP get_customer_history
C-1008 is Enterprise · $8,400/mo, owned by Devon Park, with one open ticket already this week. Confirms the critical priority — Enterprise SLA, account at risk.
05
ADK agent → MCP search_knowledge_base
Top hit: KB-008 "Service degradation — what to do". Second: KB-007 "Webhook signature mismatch troubleshooting". Drafted response references KB-008 and points to the status page.
06
ADK agent → emits decision
Returns the structured JSON: priority: critical, auto_responseable: false, escalation_reason: "Enterprise customer reporting production-impacting outage". Three tool calls in the trace.
07
n8n Switch → escalation branch
Priority = critical sends to the Slack branch. Posts to #support-escalations with the ticket id, the customer name, the escalation reason, and the linked KB articles for the on-call to use.
04 — Decision Schema the contract n8n routes on

Structured output, every time.

The agent's system instruction pins the output to a JSON schema. n8n's Switch node reads priority and auto_responseable to branch — no parsing fragility, no prompt-injection surface.

/triage · response shape [ AGENT OUTPUT ]
{
  "ticket_id": "ZD-9182",
  "decision": {
    "priority":           "critical",           // critical | high | medium | low
    "category":           "incident",
    "auto_responseable":  false,
    "draft_response":     "Hi Devon, we're investigating reports of...",
    "escalation_reason":  "Enterprise customer reporting production-impacting outage",
    "kb_articles":        ["KB-008", "KB-007"]
  },
  "trace": [
    { "tool": "classify_intent",        "arguments": { "ticket_text": "..." } },
    { "tool": "get_customer_history",   "arguments": { "customer_id": "C-1008" } },
    { "tool": "search_knowledge_base",  "arguments": { "query": "API 503 outage degraded" } }
  ]
}

The trace array is what makes the system auditable. Every tool call is logged with its arguments — useful for debugging, compliance review, and tuning the agent's instruction over time.

05 — Routes three outcomes, one switch

What n8n does with the decision.

A single Switch node on priority + auto_responseable. Three outcomes, each a real HTTP call to the right downstream system.

When priority = low / medium AND auto_responseable = true
Auto-reply (gated)
POST drafted reply to the helpdesk with status: pending_review. A human approves before the customer sees it. The agent is a recommender, not a sender.
When priority = low / medium AND auto_responseable = false
Triage queue
POST to the triage queue with the agent's category, draft, and KB references. Saves a tier-1 analyst 5–10 minutes of context-gathering per ticket.
When priority = critical OR high
Escalate to Slack
Page #support-escalations with the escalation reason and the relevant KB articles already linked. On-call has context before they open the ticket.
06 — Try it demo without n8n · full stack with

Two ways to run.

demo · no n8n required [ FAST PATH ]
$ git clone https://github.com/iambrucedavis/adk-mcp-n8n-flow
$ cd adk-mcp-n8n-flow
$ python3 -m venv .venv && source .venv/bin/activate
$ pip install -r requirements.txt
$ export GEMINI_API_KEY=...
$ python scripts/demo-end-to-end.py                          # critical incident
$ python scripts/demo-end-to-end.py examples/sample-ticket-howto.json  # how-to
full stack · n8n + agent [ DOCKER ]
$ echo "GEMINI_API_KEY=..." > .env
$ docker compose up                                          # n8n on :5678, agent on :8080

# In another terminal:
$ curl -X POST http://localhost:5678/webhook/support-triage \
       -H 'Content-Type: application/json' \
       -d @examples/sample-ticket.json