This guide explains how CAAL discovers and uses n8n workflows as voice-callable tools.
CAAL transforms n8n workflows into LLM tools via the Model Context Protocol (MCP). Any n8n workflow with a webhook trigger and MCP enabled becomes a tool the voice assistant can call.
┌─────────────────────────────────────────────────────────────────┐
│ Voice Request │
│ "What's the weather?" │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ CAAL Agent │
│ │
│ 1. LLM sees tool: weather_aus │
│ Description: "Weather tool - forecast and current │
│ conditions for Australia. Parameters: action..." │
│ │
│ 2. LLM decides to call tool with: │
│ {"action": "current", "location": "Sydney"} │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ n8n Workflow │
│ │
│ POST http://n8n:5678/webhook/weather_aus │
│ Body: {"action": "current", "location": "Sydney"} │
│ │
│ Returns: { │
│ "message": "Sydney is 24 degrees and sunny", │
│ "weather": {"temp": 24, "condition": "sunny"} │
│ } │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Voice Response │
│ "Sydney is 24 degrees and sunny" │
└─────────────────────────────────────────────────────────────────┘
When CAAL starts (or when /reload-tools is called), it discovers workflows via n8n's MCP server:
- Calls
search_workflowsto get list of MCP-enabled workflows - Calls
get_workflow_detailsfor each to extract the webhook node's notes - Creates tool definitions with the workflow name and description
The webhook node's notes field becomes the tool description the LLM sees. This is how the LLM knows what the tool does and what parameters to pass.
Descriptions should start broad then scope, and clearly document parameters:
Weather tool - forecast and current conditions for Australia.
Parameters:
- action (required): 'forecast' or 'current'
- location (required): city, suburb, or postcode
- days (optional, default 7): forecast days ahead (1-7), use for 'next week' queries
- target_day (optional): specific weekday like 'Monday', use only for 'on Friday' style queries
Examples: 'Sydney weather', 'Melbourne next week' (days=7), 'Brisbane Thursday' (target_day='Thursday')
Key principles:
- Start broad, then scope - "Weather tool - forecast for Australia" not "Australian weather tool" (the latter won't match "Sydney weather" queries)
- Use exact parameter names -
querynot "search term",daysnot "number of days" - Disambiguate similar params - explain when to use
daysvstarget_day - Include examples mapping natural language to parameters
The LLM reads this and knows to call the tool with {"action": "current", "location": "Sydney"}.
When the LLM decides to call a tool:
- CAAL receives the tool call with arguments
- CAAL POSTs to
{n8n_url}/webhook/{workflow_name}with the arguments as JSON body - The n8n workflow executes and returns a response
- CAAL speaks the
messagefield to the user
Workflows must return both a voice message and structured data:
return {
message: "Brief voice response here", // Spoken aloud
data: [...] // Enables follow-up questions
};Why both?
message- Read aloud by the voice assistantdata- Cached in conversation context for follow-up questions
Example:
return {
message: "Bills 20, Browns 10.",
games: [
{ away: "BUF", awayScore: 20, home: "CLE", homeScore: 10, status: "live" },
{ away: "SEA", awayScore: 38, home: "LAR", homeScore: 37, status: "final" }
]
};Now if the user asks "What about the Seahawks game?", the LLM can check the cached games array without re-calling the tool.
Tool Suites (preferred for related actions) - a single workflow handling multiple actions via a Switch node:
- Name is just
service(snake_case):google_tasks,truenas,espn_nhl - Routes on an
actionparameter:get,add,complete, etc.
Individual Tools - single-purpose tools:
- Name follows
service_action_object:weather_get_forecast,date_calculate_days_until
The workflow name becomes both the webhook path (/webhook/google_tasks) and the tool name the LLM sees.
All responses are read aloud. Follow these rules:
| Do | Don't |
|---|---|
| Brief and conversational | Long detailed explanations |
| Plain text | Markdown, JSON, symbols |
| Natural language | Technical formats |
| Short names ("Falcons") | Full names ("Atlanta Falcons") |
| Limit lists (3-5 items) | Long enumerated lists |
Good: "Falcons 29, Bucs 28. Seahawks 16, Bears 7."
Bad: "Game 1: Atlanta Falcons versus Tampa Bay Buccaneers, final score 29 to 28..."
Webhook → HTTP Request → Code (format) → Respond
Webhook → HTTP Request → Code (filter) → HTTP Request 2 → Code (format) → Respond
For tasks taking >5 seconds, respond immediately then announce when done:
Webhook → Respond ("On it, I'll let you know")
↓
[Long work]
↓
HTTP Request → CAAL /announce endpoint
The announce call:
{
"method": "POST",
"url": "http://caal-agent:8889/announce",
"body": {"message": "Your task is complete."}
}- Open n8n Settings
- Go to MCP Access
- Enable MCP
- Set connection method to Access Token
- Copy the token
In CAAL's Settings Panel > Integrations > n8n:
- Enable n8n
- Enter your n8n host URL (e.g.,
http://192.168.1.100:5678) - Paste the MCP access token
- Test the connection
| Issue | Solution |
|---|---|
| Tool not appearing | Check workflow is active and "Available in MCP" is enabled |
| Wrong parameters | Update webhook node notes with clear parameter docs |
| Empty response | Ensure Code node returns {message: "...", data: ...} |
| Webhook 404 | Verify workflow name matches webhook path exactly |
| Timeout | Use async pattern for long-running tasks |
After creating or modifying workflows, reload CAAL's tool cache:
curl -X POST http://localhost:8889/reload-toolsOr say "reload tools" to the voice assistant.
Browse community tools or submit your own via the CAAL Tool Registry. Tools can be installed directly from the Tools panel in the CAAL web UI.
See the CONTRIBUTING guide for how to build and submit tools.