mcp_client
Stateful client for the stable
MCP 2025-11-25
contract over stdio or Streamable HTTP.
initialize performs the complete MCP handshake atomically: it sends the
initialize request, validates the negotiated protocol and capabilities, and
sends notifications/initialized. It returns an opaque IronFlow session handle
in {output_key}_session. Pass that handle to later list_tools, call_tool,
and close actions. The server's HTTP session ID and JSON-RPC envelopes are
kept inside the client and are not written to workflow context.
Parameters
Common parameters
- actionstringdefault "initialize"
- One of
initialize,list_tools,call_tool, orclose. - output_keystringdefault "mcp"
- Prefix for output context keys.
- timeoutnumberdefault 30
- Positive per-action timeout in seconds. A shorter enclosing step timeout wins.
- sessionstringfor non-initialize actionsdefault none
- Opaque
{output_key}_sessionreturned byinitialize.
Initialization parameters
Initialization reads the following parameters. The params.name and
params.arguments fallbacks described under tool calls are the only later use
of params; transport configuration is initialization-only.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
transport | string | no | "stdio" | stdio or streamable_http. http is accepted as an alias; the obsolete sse name is rejected. |
protocol_version | string | no | "2025-11-25" | Must be 2025-11-25, the stable revision supported by this build. |
client_name | string | no | "ironflow" | Client implementation name sent during initialization. |
client_version | string | no | crate version | Client implementation version sent during initialization. |
params | object | no | {} | Optional initialization fields. protocolVersion, capabilities, and clientInfo are validated against the supported MCP model. |
command | string | stdio only | none | Executable used to start the MCP server. |
args | string array | no | [] | Arguments passed to the stdio server. |
env | string map | no | {} | Environment variables passed to the stdio server. |
cwd | string | no | current directory | Working directory for the stdio server. |
url | string | Streamable HTTP only | none | MCP Streamable HTTP endpoint. |
headers | string map | no | {} | Additional HTTP headers, such as Authorization. Transport-managed headers cannot be overridden. |
IronFlow rejects custom Accept, Content-Type, MCP-Session-Id, and
MCP-Protocol-Version headers. The transport owns these fields so callers
cannot weaken content negotiation, corrupt response correlation, or substitute
another server session.
Tool-call parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
tool_name | string | yes* | none | Tool to invoke. params.name is accepted as a fallback. |
arguments | object | no | {} | Tool arguments. Non-object values are rejected. params.arguments is accepted as a fallback. |
*Required for action = "call_tool".
list_tools and call_tool also require the server to have negotiated the
MCP tools capability.
Session lifecycle
initializecreates one isolated session and returns its opaque handle.- A stdio session keeps the same child process and pipes for the entire MCP
lifecycle. JSON-RPC frames are newline-delimited, strictly correlated, and
bounded by
IRONFLOW_MAX_SHELL_OUTPUT_BYTESin each direction. Server stderr remains attached as logging and is not interpreted as a protocol response. - Partial stdio frames remain buffered while IronFlow writes outgoing messages, including replies to server pings. The input size limit applies to the whole accumulated frame, including its newline delimiter, not each read fragment. Writing an outgoing message does not itself cancel the MCP operation or invalidate its session. EOF before the delimiter or an input error fails the transport and invalidates the affected operation's session.
- A Streamable HTTP session retains its negotiated version, transport-managed
headers, and server-issued session ID internally. Request responses may be
application/jsonortext/event-streamas defined by MCP 2025-11-25. - Sessions are process-local. A handle persisted in run context cannot restore a session after IronFlow restarts.
- Call
closeafter the final operation. For stdio, shutdown closes stdin, waits for the server, and escalates process termination if necessary. The session handle is invalid after close. - A timed-out, failed, or cancelled operation invalidates its session and cleans up the owned transport. A completed external tool side effect cannot be rolled back.
IRONFLOW_MCP_SESSION_CACHE_SIZEandIRONFLOW_MCP_SESSION_TTL_SECSbound abandoned sessions as a safety net. Idle expiry is swept when another session is inserted or leased; it is not a replacement forclose.
Context output
Every successful action writes:
{output_key}_transport—"stdio"or"streamable_http".{output_key}_action— resolved action.{output_key}_session— opaque IronFlow session handle. Thecloseresult reports the handle that was closed; it is no longer usable.{output_key}_result— typed action result, without a raw JSON-RPC envelope.{output_key}_success—true.
Action-specific output:
initialize{output_key}_protocol_version{output_key}_capabilities{output_key}_server_info— the server's implementation identity, ornullwhen the server does not supply one (the MCP handshake treats it as optional)
list_tools{output_key}_tools{output_key}_tool_names{output_key}_tool_count
call_tool{output_key}_tool_name{output_key}_tool_result{output_key}_tool_content{output_key}_tool_text— text content blocks joined with newlines, ornullwhen no text block is present.
close{output_key}_closed—true.
Raw JSON-RPC request IDs, request and response envelopes, and server-issued HTTP session IDs are intentionally not exposed.
Example: persistent stdio session
local flow = Flow.new("mcp_stdio_demo")
flow:step("initialize", nodes.mcp_client({
transport = "stdio",
command = "python3",
args = { "examples/17-mcp/mcp_stdio_mock.py", "--mode", "fragmented-ping" },
action = "initialize",
output_key = "mcp_init"
}))
flow:step("list_tools", nodes.mcp_client({
action = "list_tools",
session = "${ctx.mcp_init_session}",
output_key = "mcp_tools"
})):depends_on("initialize")
flow:step("call_tool", nodes.mcp_client({
action = "call_tool",
session = "${ctx.mcp_init_session}",
tool_name = "search",
arguments = { query = "IronFlow MCP integration" },
output_key = "mcp_call"
})):depends_on("list_tools")
flow:step("close", nodes.mcp_client({
action = "close",
session = "${ctx.mcp_init_session}",
output_key = "mcp_close"
})):depends_on("call_tool")
return flow
Example: Streamable HTTP
local flow = Flow.new("mcp_streamable_http_demo")
flow:step("initialize", nodes.mcp_client({
transport = "streamable_http",
url = env("MCP_STREAMABLE_HTTP_URL"),
action = "initialize",
headers = {
Authorization = "Bearer " .. env("MCP_TOKEN")
},
output_key = "mcp_remote_init"
}))
flow:step("list_tools", nodes.mcp_client({
action = "list_tools",
session = "${ctx.mcp_remote_init_session}",
output_key = "mcp_remote_tools"
})):depends_on("initialize")
flow:step("close", nodes.mcp_client({
action = "close",
session = "${ctx.mcp_remote_init_session}",
output_key = "mcp_remote_close"
})):depends_on("list_tools")
return flow