# `Hermolaos.Transport.Stdio`
[🔗](https://github.com/nyo16/hermolaos/blob/v0.5.0/lib/hermolaos/transport/stdio.ex#L1)

Stdio transport for MCP communication with local subprocess servers.

This transport launches an MCP server as a subprocess and communicates
via stdin/stdout using newline-delimited JSON messages.

## How It Works

1. The transport spawns the server command as a subprocess using Erlang ports
2. JSON-RPC messages are written to the server's stdin (one per line)
3. Responses are read from stdout and buffered until complete
4. The owner process receives messages via `{:transport_message, pid, msg}`

## Example

    {:ok, transport} = Hermolaos.Transport.Stdio.start_link(
      owner: self(),
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    )

    :ok = Hermolaos.Transport.Stdio.send_message(transport, %{
      "jsonrpc" => "2.0",
      "id" => 1,
      "method" => "initialize",
      "params" => %{}
    })

    # Wait for response
    receive do
      {:transport_message, ^transport, message} ->
        IO.inspect(message)
    end

## Messages Sent to Owner

- `{:transport_ready, pid}` - Transport is ready
- `{:transport_message, pid, map}` - Received a decoded JSON message
- `{:transport_closed, pid, reason}` - Server process exited
- `{:transport_error, pid, error}` - Error occurred

## Options

- `:owner` - PID to receive messages (required)
- `:command` - Command to execute (required)
- `:args` - Command arguments (default: [])
- `:env` - Environment variables as keyword list (default: [])
- `:cd` - Working directory for the command (default: current directory)

# `option`

```elixir
@type option() ::
  {:owner, pid()}
  | {:command, String.t()}
  | {:args, [String.t()]}
  | {:env, [{String.t(), String.t()}]}
  | {:cd, String.t()}
  | {:name, GenServer.name()}
```

# `state`

```elixir
@type state() :: %{
  owner: pid(),
  port: port() | nil,
  command: String.t(),
  args: [String.t()],
  env: [{charlist(), charlist()}],
  cd: String.t() | nil,
  buffer: Hermolaos.Transport.MessageBuffer.t(),
  connected: boolean(),
  exit_status: integer() | nil
}
```

# `cast_message`

```elixir
@spec cast_message(GenServer.server(), map()) :: :ok
```

Sends a message asynchronously (non-blocking).

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `close`

```elixir
@spec close(GenServer.server()) :: :ok
```

Closes the transport, terminating the subprocess.

# `connected?`

```elixir
@spec connected?(GenServer.server()) :: boolean()
```

Checks if the transport is connected to a running subprocess.

# `info`

```elixir
@spec info(GenServer.server()) :: map()
```

Returns transport information and statistics.

# `send_message`

```elixir
@spec send_message(GenServer.server(), map()) :: :ok | {:error, term()}
```

Sends a JSON-RPC message to the server via stdin.

The message map is JSON-encoded and sent as a single line.

## Examples

    :ok = Hermolaos.Transport.Stdio.send_message(transport, %{
      "jsonrpc" => "2.0",
      "id" => 1,
      "method" => "ping"
    })

# `start_link`

```elixir
@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}
```

Starts the stdio transport.

## Options

- `:owner` - PID to receive transport messages (required)
- `:command` - The command to execute (required)
- `:args` - List of command arguments (default: `[]`)
- `:env` - Environment variables as `[{name, value}]` (default: `[]`)
- `:cd` - Working directory for the command (optional)
- `:name` - GenServer name (optional)

## Examples

    {:ok, pid} = Hermolaos.Transport.Stdio.start_link(
      owner: self(),
      command: "/usr/bin/python3",
      args: ["-m", "mcp_server"]
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
