mcp-server://¶
Expose Helix routes as tools on an MCP (Model Context Protocol) server. The consumer stands up an HTTP, SSE, or stdio MCP endpoint, walks the engine's registered workflows, and turns each invocable route into an MCP tool that external clients (Claude desktop, IDE plugins, other Helix nodes via mcp://) can list and call.
The server is consumer-only — CreateProducer returns an error. Wire it as a from: trigger; the steps below become the per-tool pipeline (rarely used — most servers fan out to other workflows).
URI shape¶
<host:port>is the listen address for HTTP / SSE transports.- The literal
stdio(without scheme) switches to stdio transport — the MCP server reads/writes the process's stdin/stdout (used by IDE-launched MCP servers).
Parameters¶
| Param | Type | Default | Producer/Consumer/Both | Description |
|---|---|---|---|---|
(path) addr |
string | — | Consumer | Listen address (0.0.0.0:8181) or stdio. |
transport |
enum | auto | Consumer | Force http (Streamable), sse, or stdio. Default: stdio when path is stdio, otherwise http. |
name |
string | helix-mcp |
Consumer | Server identity (advertised to MCP clients). |
version |
string | 1.0.0 |
Consumer | Server version. |
workflows |
string | (all) | Consumer | Comma-separated workflow IDs to expose. Other workflows are skipped. |
api_keys |
string | — | Consumer | Comma-separated API keys. When set, every request must carry Authorization: Bearer <key> or X-API-Key: <key> (constant-time compared). |
cors_origins |
string | — | Consumer | Comma-separated allowed CORS origins. * allows any. |
ip_allowlist |
string | — | Consumer | Comma-separated client IPs that are allowed to connect. |
trusted_proxies |
string | — | Consumer | Comma-separated IPs that bypass IP allowlist + API-key auth (gateway already authenticated upstream). |
allowed_tools |
string | (all) | Consumer | Tool-name whitelist. Empty = all exposed tools are callable. |
blocked_tools |
string | — | Consumer | Tool-name blacklist (applied before whitelist). |
rate_limit |
integer | — | Consumer | Requests per minute (token-bucket). 0 / unset = unlimited. |
max_body_bytes |
integer | 1048576 |
Consumer | Max accepted request body size in bytes. |
How routes are exposed¶
A workflow is exposed as an MCP tool when any of:
- The workflow's
from:URI istool://...,agui://..., ora2a-server://...(the AI-native triggers — always invocable). - The workflow's
from:URI isdirect://,seda://, orhttp-server://and the workflow has aninput-schema:set (explicit opt-in for engineering-style routes).
Workflows without an input schema are skipped with a debug log. Tool names are derived from the workflow ID (sanitizeToolName replaces any character outside [a-zA-Z0-9_\-.] with _).
The route's description: becomes the MCP tool description. The input-schema: becomes the MCP tool inputSchema (with type: object and properties: {} filled in when missing, so strict validators like OpenAI's accept it). Agent-backed routes that lack an input-schema get an auto-generated one with {message, thread_id}.
Resources¶
In addition to tools, the server publishes one resource:
helix://workflows— JSON list of every workflow's{id, from, description, state}. Useful for clients that want to discover what's available before calling.
Headers and session¶
Each tool call materializes an exchange.NewWithPattern(InOut) with:
| Header | Description |
|---|---|
HelixMcpToolName |
The tool name (route ID) being invoked. |
HelixMcpRouteID |
Same as above — workflow ID. |
The session ID is read from _meta.sessionId in the request arguments. Sessions persist helix.session.state between calls so a tool can carry conversation state.
Return-body shaping:
string/[]byte→ text content.map[string]any/[]any→ JSON-marshalled text content.- everything else →
ex.In().BodyAsString().
If ex.IsFailed() or the pipeline returns an error, the result is sent as IsError: true with the error text.
Examples¶
Expose two specific workflows over HTTP with API-key auth:
workflows:
- id: mcp-server
trigger:
uri: mcp-server://0.0.0.0:8181?workflows=search,translate&api_keys=${MCP_KEY}&cors_origins=*
steps: []
- id: search
description: "Search the knowledge base"
input-schema:
type: object
properties:
query: { type: string }
required: [query]
trigger: { uri: direct://search }
steps:
- target: tool://kb-search
- id: translate
description: "Translate text"
input-schema:
type: object
properties:
text: { type: string }
target: { type: string }
required: [text, target]
trigger: { uri: direct://translate }
steps:
- target: agent://translator
Use it from Claude desktop (~/.config/claude/claude_desktop_config.json):
{
"mcpServers": {
"helix": {
"url": "http://helix.local:8181",
"headers": { "X-API-Key": "${MCP_KEY}" }
}
}
}
Stdio (IDE-launched) variant:
Behind a gateway that already authenticates (skip MCP-level auth from those IPs):
Behavior notes¶
- Per-call security stack — CORS → trusted-proxy check → IP allowlist → API-key auth → rate limit → body-size cap. All five are independent; configure only what you need.
- Constant-time auth comparison —
crypto/subtle.ConstantTimeCompareis used so timing attacks against keys are not viable. - Skips its own route — the server never exposes the workflow that hosts it (id
mcp-server). - Reuses the engine pipeline — every tool call runs through
engine.BuildPipeline(route.Processors, hc.Interceptors(), route.ErrorHandler), so interceptors, tracing, and error handlers configured for the route apply to MCP-triggered calls. - Resources are read-only —
helix://workflowslists workflows; there is no MCP write operation.
Source¶
pkg/component/mcp/server.go, pkg/component/mcp/tools.go