ExMCP.Transport.Stdio (ex_mcp v1.0.0-rc.8)

Copy Markdown View Source

This module implements the standard MCP specification.

stdio transport implementation for MCP.

This transport communicates with MCP servers over standard input/output, typically by spawning a subprocess. This is one of the two official MCP transports defined in the specification.

Options

  • :command - Command and arguments to spawn (required)
  • :cd - Working directory for the process
  • :env - Environment variables as a list of {"KEY", "VALUE"} tuples; use {"KEY", false} to remove an inherited variable from the child
  • :environment_policy - :isolated (default) passes only a small runtime allowlist plus explicit :env; :inherit preserves the parent environment for explicitly trusted deployments
  • :max_frame_bytes - maximum inbound or outbound JSON-RPC frame size (default: 1 MiB)

Example

{:ok, client} = ExMCP.Client.start_link(
  transport: :stdio,
  command: ["node", "my-mcp-server.js"],
  cd: "/path/to/server",
  env: [{"NODE_ENV", "production"}]
)

Summary

Functions

Receives a single message, waiting at most timeout milliseconds.

Subscribe to receive transport events (push model).

Functions

receive_message(state, timeout)

@spec receive_message(
  %ExMCP.Transport.Stdio{
    line_buffer: term(),
    max_frame_bytes: term(),
    os_pid: term(),
    port: term(),
    reader_pid: term(),
    subscriber: term()
  },
  timeout()
) ::
  {:ok, binary(),
   %ExMCP.Transport.Stdio{
     line_buffer: term(),
     max_frame_bytes: term(),
     os_pid: term(),
     port: term(),
     reader_pid: term(),
     subscriber: term()
   }}
  | {:error, any()}

Receives a single message, waiting at most timeout milliseconds.

Callers must run this in the process that owns the port (or in one that may take ownership): port ownership is transferred to the caller, and an OTP port is closed when its owner exits. Running it in a short-lived helper process would therefore kill the spawned program — which is why the handshake path uses this timeout-aware clause in-process instead of wrapping receive_message/1 in a task.

subscribe(pid, state)

Subscribe to receive transport events (push model).

Spawns an internal reader process that takes over port ownership, reads and parses JSON messages, and pushes them to the subscriber.