- AI & Agents
- Agent Tools Dispatch
Agent Tools Dispatch
The Ironflow server’s agent_tools.Dispatcher POSTs HMAC-signed callbacks to
SDK-registered tool handlers. SDKs (@ironflow/node/agent, github.com/sahina/ironflow-go/ironflow/agent,
ironflow.agent for Python) mount the handler at /ironflow/agent-tools/dispatch.
Request
Section titled “Request”| Header | Value |
|---|---|
X-Ironflow-Timestamp | unix seconds, must be within 5 minutes past / 1 minute future |
X-Ironflow-Signature | sha256=<hex> of HMAC-SHA256(secret, "{ts}.{rawBody}") |
Body: {"qualified_name": "<agent>.<tool>", "input": {...}}
Response codes
Section titled “Response codes”| Status | Code | When |
|---|---|---|
| 200 | — | Handler ran. {output} on success, {error: {code: "HANDLER_ERROR", message}} on throw. |
| 400 | INVALID_REQUEST | Body not JSON, or qualified_name missing. The Go and Python SDKs also refuse a body over 1 MiB. |
| 400 | INPUT_SCHEMA_INVALID | Input failed Zod / Validate() schema check (Node and Go SDKs). |
| 401 | TIMESTAMP_SKEW | Timestamp outside replay window (5min past / 1min future), or unparseable. |
| 401 | SIGNATURE_MISMATCH | Missing signature or timestamp header, a signature without the sha256= prefix, HMAC verification failed, OR qualified_name not registered. Missing-header and unknown-tool cases collapse to the same code so an unauthenticated caller cannot probe the registered tool roster (see below). |
What the server does
Section titled “What the server does”- One attempt. The server does not retry a dispatch. The MCP client owns retries.
- Timeout. The call uses the
timeout_msthat the SDK registered with the tool, or 30 seconds when it is zero. The server caps it at 60 seconds. No flag or environment variable changes the cap. For a longer wait, useapprove(). - Unreachable callback. When the connection is refused, DNS fails or the network is unreachable, the server removes the tool registration. The SDK registers it again when it restarts. An HTTP 5xx response is an error, and the server keeps the registration.
- Callback URL guard. The server checks the callback URL when it registers the tool and again when it connects. Private addresses need
IRONFLOW_AGENT_TOOLS_ALLOW_PRIVATE=true(orserve --dev). See the configuration reference.
Why unknown-tool collapses to SIGNATURE_MISMATCH
Section titled “Why unknown-tool collapses to SIGNATURE_MISMATCH”An unauthenticated caller could probe the dispatch endpoint with arbitrary
qualified_name values and distinguish 404 (unknown) from 401 (registered, bad
sig) — leaking the registered tool roster to anyone who can reach the callback
URL. The SDK collapses both cases to 401 SIGNATURE_MISMATCH "HMAC mismatch"
so the response shape is identical.
Operators distinguish legitimate stale-process drift (SDK restarted without re-registering) from attack via the SDK’s structured warn log:
ironflow.agent.dispatch unknown_tool qualified_name="<name>"Emitted once per unknown-tool dispatch. Aggregating these by source IP /
caller key in your log pipeline highlights probe attempts. The line is
diagnostic only; the wire response remains SIGNATURE_MISMATCH. The
qualified_name is rendered with Go %q / JS JSON.stringify / Python %r so
embedded control chars (\n, \r, etc.) are escaped — log injection
via crafted callback payloads is blocked.