Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

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.

HeaderValue
X-Ironflow-Timestampunix seconds, must be within 5 minutes past / 1 minute future
X-Ironflow-Signaturesha256=<hex> of HMAC-SHA256(secret, "{ts}.{rawBody}")

Body: {"qualified_name": "<agent>.<tool>", "input": {...}}

StatusCodeWhen
200—Handler ran. {output} on success, {error: {code: "HANDLER_ERROR", message}} on throw.
400INVALID_REQUESTBody not JSON, or qualified_name missing. The Go and Python SDKs also refuse a body over 1 MiB.
400INPUT_SCHEMA_INVALIDInput failed Zod / Validate() schema check (Node and Go SDKs).
401TIMESTAMP_SKEWTimestamp outside replay window (5min past / 1min future), or unparseable.
401SIGNATURE_MISMATCHMissing 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).
  • One attempt. The server does not retry a dispatch. The MCP client owns retries.
  • Timeout. The call uses the timeout_ms that 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, use approve().
  • 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 (or serve --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.