Install
$ agentstack add mcp-kitepon-rgb-aiterm-mcp Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Destructive filesystem operation.
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
aiterm-mcp
[](https://github.com/kitepon-rgb/aiterm-mcp/actions/workflows/ci.yml) [](https://www.npmjs.com/package/aiterm-mcp) [](https://nodejs.org) [](LICENSE) [](https://packagephobia.com/result?p=aiterm-mcp)
> (日本語: [README.ja.md](README.ja.md))
> Let your AI orchestrate other AIs. From any MCP client, one call spawns a coding agent (Claude, Codex, Grok, or Composer) inside a persistent terminal and hands you a session to drive: read what it's doing token-reduced, send it the next instruction. > > What it is: one persistent MCP terminal your AI drives — and can launch other coding agents into. ssh, docker exec, a REPL, or another agent's TUI all nest inside that one terminal as just text you send in. The mechanism is deliberately plain — your MCP client drives the other agent's terminal turn by turn: no hidden protocol, no shared memory, no autonomous negotiation. > > No human at a tmux required. aiterm is driven programmatically over MCP, so an AI can launch and drive another agent with no one sitting in the terminal — from an orchestration loop, a CI step, or a cron job. > > MCP = Model Context Protocol — the open standard that lets tools like Claude Code plug capabilities into an AI.
Measured, not claimed: on this repo's own 203-test suite, a pty_read puts ~7.1× fewer tokens in your context than the raw log — and the pass/fail verdict survives the fold. → [When to reach for it vs. the built-in shell](#when-to-reach-for-it-vs-the-built-in-shell)
Twelve tools: six PTY tools — pty_open / pty_send / pty_read / pty_key / pty_close / pty_list — to open, drive, and read one persistent terminal, four agent launchers — claude_agent / codex_agent / grok_agent / composer_agent — that each start another coding agent's TUI inside a fresh one, claude_turn for durable structured issue/recovery, and diagnostics for safe factory readiness. The backend is tmux, so sessions survive even if the MCP server or the AI client restarts.
v0.12.2 was published on 2026-07-13. Factory diagnostics and the local runtime-error store collect only when canonical dotagents config explicitly sets collection.enabled: true; collection is off by default and performs no network I/O. npm latest, provenance CI, tag / GitHub Release, Official MCP Registry registration, and a registry-derived isolated install were verified.
Status: actively maintained · the newcomer here, betting on a different shape (see [vs. the alternatives](#vs-the-alternatives)) · runs on Linux · WSL2 · macOS · native Windows for the core PTY tools (agent_done is POSIX/WSL/macOS only for now) · MIT · see the [CHANGELOG](CHANGELOG.md).
Why now
A lot of 2026's agent tooling is converging on orchestration: a lead model delegating a mechanical refactor to Codex, running Composer on a bulk edit while it reviews the diff, fanning one task across several agents to spare its own context window. All of those agents already live in a terminal. aiterm makes that terminal a first-class, MCP-native tool — so the model doing the orchestrating can spawn and steer the others without a human wiring up panes.
Built with Codex and GPT-5.6 for OpenAI Build Week 2026
aiterm predates Build Week, so the event work is kept visible in dated commits. During the submission window (July 14–16, 2026), I extended it with safe serialized delivery for long PTY input, correlated operation IDs and bounded result recovery, machine-readable launch and idempotent close receipts, and a hardened readiness gate that prevents prompts from disappearing during TUI startup redraws. The public comparison from the pre-event release is v0.12.2...main.
I used Codex with GPT-5.6 as an engineering collaborator: it inspected the implementation, challenged the API and recovery contracts, generated focused regression cases, and helped verify race, security, timeout, and malformed-event paths. I reviewed the diffs and test evidence and retained the final product and architecture decisions. The result is a 262-test regression suite covering normal operation as well as failure and recovery behavior.
Two ways to use it
1. Drive SSH, containers, and REPLs in one persistent terminal — the primitive
This is the base, and it works with just tmux — no other CLI. pty_open grabs one local terminal; ssh host, docker exec -it x bash, or a REPL are just text you pty_send into it — once. Every command after that rides the same already-authenticated session. Session kind is never a tool-level distinction.
pty_open() → grab one local terminal
pty_send(id, "ssh 192.168.1.2") → authenticate once, inside that terminal
pty_send(id, "uname -a") → every later command rides the SAME session
pty_read(id, { wait: true }) → read the token-reduced output, completion detected
Origin. I built aiterm for exactly this. Driving my homelab from Claude Code one command at a time meant every SSH command became its own connect → authenticate → disconnect: re-typing the passphrase and one-time code each time, short-lived sessions piling up, and eventually my own defenses (fail2ban, MaxStartups/MaxSessions, account lockout) locking me out — the security meant to stop attackers ended up stopping me. Holding one authenticated session fixes all three at once. That pain is why the persistent terminal exists; launching whole other agents inside it is what it grew into.
2. Launch other coding agents into that terminal — the orchestration flagship
The same primitive hosts another agent's TUI. Four launchers each start one vendor's interactive coding-agent TUI inside a fresh persistent terminal and return a session_id. Their existing human-readable text is accompanied by an aiterm.agent-launch-result.v1 structured receipt, so durable callers never parse display text for the session handle. From there you drive it with the same pty_read / pty_send you'd use on any shell: read its output token-reduced, send it the next step. (The TUIs are full-screen apps, so pty_read({ screen: true }) gives you the rendered view.) Agent launchers can also opt into hook-backed turn completion with agent_done: true, letting pty_send({ wait: "agent_done" }) return after the agent turn ends. A managed Claude session requires that wait mode for every turn. Durable machine callers use claude_turn({ action: "issue" | "recover", session_id, operation_id, ... }): it returns fixed accepted / pending / completed / unknown states without parsing human-facing errors, never resends during recovery, and includes exact raw_output only for a verified completion. The same operation ID is carried through the dispatch receipt, active marker, Stop event, and result. The ordinary pty_send / pty_read surface remains available for interactive callers and humans. C-c keeps the marker for a delayed Stop; if no Stop arrives, close the session. claude_agent and codex_agent can additionally wait for their initial prompt: pass prompt, agent_done: true, and wait: "agent_done"; aiterm starts the TUI first, waits until its input area is ready, submits the prompt, then returns after that first turn's Stop hook. Codex/Grok/Composer have passing live smokes for follow-up completion, and Codex initial-prompt wait is also live-smoked. Claude's real-model initial/follow-up smoke remains an explicit approval gate. Grok/Composer initial-prompt wait is intentionally not exposed until the post-OAuth smoke passes. This needs the vendor's own CLI installed and authenticated — see [Requirements](#requirements).
codex_agent({ session_name: "codex1", cwd: "/repo", agent_done: true,
wait: "agent_done", prompt: "port test/legacy.py to vitest" })
→ { session_id: "codex1", … } # Codex now live in a persistent terminal
pty_read("codex1", { screen: true }) → read what it's doing (token-reduced)
pty_send("codex1", "also fix the imports it broke", { wait: "agent_done" })
→ steer it, then return once Codex reaches its next turn boundary
One call per model, so the tool name itself tells you which model you get:
| Tool | Launches | Key args | | --- | --- | --- | | claude_agent | Claude Code CLI (Anthropic) | prompt?, model?, reasoning_effort? (low/medium/high/xhigh/max), cwd?, session_name?, agent_done?, launch_operation_id?, wait?, timeout?, screen?, lines? | | codex_agent | Codex CLI (OpenAI; terminal config/CLI default unless overridden) | prompt?, model?, reasoning_effort? (low/medium/high/xhigh/max/ultra; ultra enables proactive automatic delegation), cwd?, session_name?, agent_done?, wait?, timeout?, screen?, lines? | | grok_agent | Grok Build, model grok-4.5 by default (model? overrides) (xAI) | prompt?, model?, reasoning_effort? unsupported (an explicit value is an error; Grok CLI --effort is headless-only), cwd?, session_name?, agent_done? | | composer_agent | Grok Build, model grok-composer-2.5-fast by default (model? overrides) (xAI) | prompt?, model?, reasoning_effort? unsupported (an explicit value is an error), cwd?, session_name?, agent_done? |
The vendor CLI must be installed and authenticated (claude for claude_agent; codex for codex_agent; grok for both Grok tools). aiterm resolves the binary via CLAUDE_BIN / CODEX_BIN / GROK_BIN, then ~/.local/bin/claude / ~/.local/bin/codex / ~/.grok/bin/grok, then PATH. Prerequisites are checked before a session exists: empty model values and unsupported effort values are rejected up front; a missing CLI binary or a nonexistent cwd fails for all four. A rejected launch leaves zero leftover session behind. Claude and Codex launchers forward model and reasoning_effort through their vendor CLI's public flags; Grok/Composer reject reasoning_effort because it is headless-only. Pass an absolute path for cwd — ~ is not expanded. Durable callers can make a promptless managed Claude launch exactly replayable by passing an explicit session_name, agent_done:true, and a launch_operation_id formatted as sha256:. Repeating the identical launch returns the same structured session receipt without starting the CLI twice; a different correlation ID or launch argument for that session fails explicitly. With agent_done:true, Claude uses launch-local managed settings containing only aiterm's Stop hook: normal user/project/local hooks are not inherited, the hook event contains no answer body, and the bounded owner-only result is returned by pty_read({ agent_transcript:true }) without reading Claude's private transcript. A timeout remains is_complete=False; the same session and a late result remain recoverable without re-sending the prompt. Ordinary pty_send(wait:"none") and non-interrupt keys are rejected on this managed Claude route; use wait:"agent_done", pty_key("C-c"), and pty_close. Start with agent_done:false when unconstrained manual key-by-key driving is desired. Codex uses a managed CODEX_HOME; Grok/Composer isolate their managed homes and pass validated OAuth state through GROK_AUTH_PATH. Before the first unbound completion-waiting send, aiterm waits for the vendor TUI's input prompt and fails before sending if it is not ready. agent_done requires POSIX filesystem semantics, so it is supported on Linux, WSL2, and macOS; native Windows can still use launchers without agent_done.
There is no hidden protocol between agents: a launched Claude, Codex, Grok, or Composer is another user-visible persistent terminal session. The MCP client drives that TUI with ordinary PTY operations, and a human can attach to watch or take over.
Demo
Real captured output — each block below was just run through aiterm in this repo; the numbers, the elision marker, and every is_complete verdict are the tool's own, not mocked. The bracketed meta line is what pty_read appends; its labels are Japanese in the actual output, translated here for readability (the [Japanese README](README.ja.md) shows them verbatim).
A long output folded head+tail — the middle is elided by the reducer, not by me (166 → 56 tokens):
→ pty_send("demo", "seq 1 150")
→ pty_read("demo", { wait: true })
← 1
2
3
⋮ (head runs to line 29 — abbreviated in this README)
… ⟨102 lines elided · full=true, or line_range="A:B"⟩ … ← the tool's own marker
⋮ (tail resumes at line 132 — abbreviated in this README)
149
150
[aiterm demo: 51 lines / ~56 tok (raw 152 lines / ~166 tok); 102 lines hidden] [is_complete=True via quiescent]
A grep, folded by the per-command reducer to a count header plus just the hits:
→ pty_send("demo", "grep -rn capture-pane src/ test/")
→ pty_read("demo", { wait: true, rtk: true })
← 2 matches in 1 files:
src/core.ts:159:// maxBuffer defaults to 1 MiB; capture-pane (large scrollback) … (line truncated here)
src/core.ts:335:const args = ["capture-pane", "-p", "-J", "-t", name];
[aiterm demo: rtk:grep applied / ~46 tok (raw ~53 tok)] [is_complete=True via quiescent]
Nesting is just text you send in — here a Python REPL inside the same PTY (an ssh host, a docker exec -it … bash, or a launched coding-agent TUI nests exactly the same way):
→ pty_send("demo", "python3")
→ pty_read("demo", { until: ">>>" }) # nested prompt = "the inner shell is ready"
→ pty_send("demo", "print(sum(range(1_000_000)))")
→ pty_read("demo", { wait: true, until: ">>>" })
← 499999500000 [is_complete=True via until]
The only edits to the captures above are the two ⋮ lines (a long head/tail run abbreviated for the README) and one over-long grep line truncated to fit — the ⟨…⟩ marker, the token counts, and every is_complete verdict are exactly what the tool printed. (Use until: ">>>" without a trailing space — the captured prompt is trimmed, so ">>> " would miss and fall through to timeout.) While nested, pass until (the inner prompt) or mark: true, because quiescence cannot fire there by design — see [Completion detection](#completion-detection-5-layers) and [Known constraints](#known-constraints-by-design-not-bugs). A human can attach to the same tmux socket and watch any of this live (see [A human can watch](#a-human-can-watch)).
Quickstart (≈60 seconds)
One command registers it in Claude Code — no clone, no build, npx fetches it each run:
claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
Restart Claude Code, then verify the connection:
/mcp # aiterm should show as connected, exposing 12 tools
Your first session — four calls, one persistent terminal:
pty_open() → { session_id: "t1", attach: "tmux -S … attach -t t1" }
pty_send("t1", "echo hello") → command sent into the PTY
pty_read("t1", { wait: true }) → "hello" (token-reduced, completion detected)
pty_close("t1") → terminal released
pty_close is idempotent and returns a structured closed / already_closed receipt, so durable callers can retry the same session_id after losing the MCP response.
That's it. The terminal in t1 is real and persistent — ssh, docker exec, a REPL, or a launched agent's TUI are just things that live inside it. To launch a worker agent instead, one call does it: codex_agent() returns a session_id you drive with the same pty_read / pty_send.
Prefer a global install, or a different client?
# install globally, then register the command name
npm i -g aiterm-mcp
claude mcp add --scope user --transport stdio aiterm -- aiterm-mcp
This registers it in `~/.claude.jso
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kitepon-rgb
- Source: kitepon-rgb/aiterm-mcp
- License: MIT
- Homepage: https://www.npmjs.com/package/aiterm-mcp
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.