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 navigates the hub, types into chat, and launches agents 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; navigation, filtering, and agent launches 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

tui_install_agent needs an install keybinding on the hub screen, and the hub does not have one yet — the tool reads the bindings off the footer rather than guessing a key, so today it returns “not yet available” and lists what the hub does offer. It starts working the moment the binding lands, with no change here. Neither tool takes “the key was pressed” as success: tui_uninstall_agent requires the agent to be gone from the hub’s list, and tui_install_agent requires the screen to have changed at all (an install promotes the row to a different tab, so it may legitimately leave the current one). A refusal quotes the hub’s own status line.
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 --debug to log every injected key, state transition, 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

The assistant calls tui_status, tui_send_keys(["tab"]), tui_wait_for, and tui_screen. You watch the tab change in your terminal.

Launch an agent and ask it something

tui_launch_agent("email") walks the hub tabs until the agent is visible, moves the selection onto it, presses Enter, and waits for the chat view to open. Then tui_send_text + tui_send_keys(["enter"]) sends the query.

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 or confirmation dialog swallows keys until it is dismissed (esc). If the hub still shows Loading..., 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 --debug logs every injected key and every wait resolution to stderr.

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.