Skip to main content
Prerequisites: the GAIA TUI binary, started with the control API on: gaia tui --control.

Overview

The GAIA TUI can expose a loopback control API, and this MCP server wraps it as tools. An assistant then reads the screen, sends keys and types into chat in the session you are looking at — same process, same terminal, frames updating live. It is not a headless replica and not a replay. That makes two things possible that were not before:
  • Testing the TUI without a human at the keyboard. Send keys, wait for a condition, read the rendered screen, assert.
  • Watching an assistant work. You keep your terminal open; the readiness gate, the questions, and the answers happen in front of you.
Keys are injected into the running tea.Program with Send; the screen served back is the exact frame the renderer just drew.

Available Tools

What view can be

gaia-tui boots straight into one agent, so there is nothing to browse and no agent to pick. tui_status reports view as one of:
Wait on state, not on a substring, wherever you can: tui_wait_for(state={"view": "chat"}) returns the moment the gate hands off, and tui_wait_for(state={"blocker": "lemonade"}) tells you which row is holding without parsing the remedy text — that wording is allowed to change, the row key is not.
There is no tui_launch_agent, tui_install_agent or tui_uninstall_agent. They drove the hub browser, which no longer exists. Launching is running gaia tui; a different agent is gaia tui chat --agent <id>; and installing a hub agent is gaia hub install <id> in the Python CLI.
tui_wait_for is the one that makes automation reliable. Without it, every caller busy-polls tui_screen and races the render. On timeout it reports what the screen actually contained, so a failure is debuggable.
tui_send_keys, tui_send_text, and tui_resize return only once the input has been consumed and redrawn, so reading tui_screen immediately after is race-free. They report settled: false if the model was still busy — the input is queued, so re-read the screen rather than assume it was dropped. That is not the same as “the consequences finished”. Pressing enter to launch an agent is consumed at once, while the view switch it starts arrives a moment later — so wait on the outcome (tui_wait_for) rather than reading the screen once and concluding nothing happened. If the user has quit the TUI (or it is still starting), these tools fail with “the TUI is not accepting input” instead of reporting a success. Bubble Tea discards messages sent outside its event loop, so a success there would be a lie. tui_status and tui_screen keep working, and tui_status reports running: false.

Setup with Claude Code

1

Start the TUI with the control API

The TUI prints the port it bound and writes ~/.gaia/tui/control.json (mode 0600) holding the pid, port, and bearer token. The token is never printed. Add --dev to log input counts, state transitions, and wait resolution to stderr.For a fixed port: gaia tui --control-port 8770.
2

Add the MCP server to Claude Code

3

Start a new Claude Code conversation

MCP tools are loaded at conversation start, so open a new one. Ask for tui_status first — it confirms the assistant can see your session.
The server can also run over Streamable HTTP, the same as the Agent UI MCP server:

Usage Examples

See why a launch is stuck

The assistant calls tui_status. A preflight view with blocker: "lemonade" names the row without reading a single pixel; tui_screen then shows the remedy the gate resolved for this machine.

Ask the agent something

tui_wait_for(state={"view": "chat"}) waits out the readiness gate, then tui_send_text + tui_send_keys(["enter"]) sends the query, and tui_wait_for(state={"streaming": False}) returns the moment the turn ends.

Check the layout at a small terminal

tui_resize(80, 24) followed by tui_screen.

Discovery and Trust

The MCP server finds the TUI by reading ~/.gaia/tui/control.json — there is no fixed port. Before trusting it, it checks both:
  1. the recorded pid is alive, and
  2. GET /control/v1/status answers with our service id and a matching pid.
Either check failing means the file is stale: after a crash it can point at a port some unrelated process now owns. The tool then says so and names the remedy rather than talking to a stranger’s socket. Every error is structured and actionable — no tracebacks, no leaked URLs, and never the token. With no TUI running, every tool answers with exactly that, plus the command to start one.
Anything that can read ~/.gaia/tui/control.json can drive your TUI. Start the TUI without --control (the default) when you do not want that.

Configuration

Environment:

Troubleshooting

The control file does not exist. Start the TUI with gaia tui --control (plain gaia tui does not expose the API).
A previous TUI crashed without cleaning up. Start a fresh one with gaia tui --control — it overwrites the registration.
Check tui_status first. A help overlay swallows keys until it is dismissed (esc), and the splash view takes no input at all — wait for preflight or chat. If the screen looks unlaid-out, it has not received a terminal size: call tui_resize.
The timeout reports the screen it actually saw. Compare it with what you were matching on — styling is stripped in plain mode, so match on the text, not on box-drawing characters. gaia tui --control --dev records input counts, state changes, and waits in the TUI log. Input contents are never logged.

TUI control API reference

The HTTP endpoints, flags, and discovery file this server wraps.

Agent UI MCP Server

The same idea for the browser-based Agent UI.