terminal-mcp

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT

README

terminal-mcp

English | 简体中文

Go Reference MCP Stars

A real PTY terminal for AI agents — with full observability and seamless human takeover.

terminal-mcp is a Model Context Protocol server that gives an LLM agent a real PTY terminal (not a one-shot exec pipe). A human can watch every step the agent takes in real time, take over the exact same session the instant the agent goes down the wrong path, and the agent then resumes with the full context of everything the human just did. Local shells, ssh, containers, gdb, python, top, vim — the agent drives them like a person, and you're never locked out.

One terminal, shared by human and agent. The agent does the driving, you watch live, grab the wheel when it matters, and hand it back — without losing a beat.

Built in Go, single binary, official MCP SDK, Streamable HTTP. Drop it into Cursor, Claude, Comate, or any MCP client.


Why terminal-mcp

Most "run a command" tools hand the model a stateless pipe: one command in, one blob out. It breaks on anything interactive (a login, a REPL, a long-running session), and — worse — it gives the human no way to see what's happening or step in when the agent goes wrong.

terminal-mcp is built around five things:

  • 1. A real PTY, not a pipe. A genuine pseudo-terminal with a persistent session. Interactive and full-screen programs (gdb, python, mysql, top, vim), line editing, colors, prompts, control keys — all work exactly as they do for a human. Not exec one-shots.
  • 2. Full observability — watch the agent live. Every session has a live web terminal URL. Open it and see, in real time, every keystroke and byte the agent produces — the same screen the agent sees. No guessing what your agent did.
  • 3. Instant human takeover — correct course the moment it's wrong. See the agent heading the wrong way? Click take over and type directly into the running PTY. The agent's writes are paused immediately; you fix things by hand — enter a password, run the right command, back it out of a hole.
  • 4. Seamless resume — the agent picks up with full context. When you release control, everything you typed is fed back to the agent as [rc=n] $ command with output, so it continues with complete knowledge of what you just did — no re-explaining, no lost state.
  • 5. A fence around the agent — transparent resource limits, inescapable across shell switches. Configure a model-invisible ulimit that's injected at session start and re-injected every time the agent hops into a new shell (ssh remote / su / container). The hard limit is inherited by every child process, so unprivileged commands can't raise it back — the agent can't escape the cap by switching shells or running other commands. A safety net that keeps an autonomous agent from running away with resources, with zero awareness on the model side.

Plus the machinery that makes the above reliable:

  • Precise command boundaries. A sentinel prompt tells the server exactly when a command finished and its exit code — the agent never mistakes "still running" for "done".
  • Survives shell switches. ssh, su, docker exec, entering a container — the sentinel is re-armed automatically so tracking keeps working across hops.
  • LLM-friendly output. ANSI escapes stripped, partial escape sequences held back — the model never gets corrupted bytes. The session log is an append-only file on disk (the source of truth) with a bounded in-memory tail cache, so a runaway cat/yes can't blow up memory; oversized results are returned as a log-range reference and paged on demand.
  • Local & remote. mode=local runs a shell/command on the host; mode=ssh connects out.
  • Audit & replay. Every tool call logged as JSON (caller IP, user, tool, args, result); every session's raw byte stream saved for replay, with configurable retention.

Quick start

Requires Go 1.25+.

go install github.com/fzxbl/terminal-mcp/cmd/terminal-mcp@latest
terminal-mcp --listen 127.0.0.1:8900

The MCP endpoint is at /mcp; a session's web terminal is at /debug/terminal/<session_id>.

Use it from your AI coding tool

terminal-mcp speaks MCP over Streamable HTTP. Point your client at http://127.0.0.1:8900/mcp.

Generic MCP client config (mcp.json):

{
  "mcpServers": {
    "terminal": { "url": "http://127.0.0.1:8900/mcp" }
  }
}

Then just ask your agent:

"Open a terminal, ssh into staging, tail the service log, and tell me why it's 500ing."

The agent opens a session, runs commands, and streams back results. If it needs a password or you want to intervene, open the returned terminal_url and take over — the agent waits, watches, and resumes.

Human takeover, in 20 seconds

  1. Agent calls terminal_open → gets a terminal_url.
  2. You open it in a browser, click take over.
  3. The session reports held=true; the agent's writes pause.
  4. You type (enter a password, run a risky command, poke around). The agent sees each of your commands reconstructed as [rc=n] $ cmd.
  5. You click release. The agent picks up right where you left off.

Tools

Tool What it does
terminal_open(mode, command?, host?) Start a persistent PTY session. mode=local or mode=ssh. Returns session_id + terminal_url.
terminal_send(session_id, input, wait_ms?) Type a command, wait for it to settle. Returns output, state, exit_code.
terminal_read(session_id, wait_ms?, mode?, from?, to?) Observe output. tail (peek), since_last (full increment; also the record of human-takeover commands), or range (page an absolute log byte window [from,to); use the range returned in a truncated result).
terminal_control(session_id, key) Send control keys (ctrl-c, ctrl-d, ctrl-z, …) or recovery actions (flush, hard, rearm).
terminal_status(session_id) Lightweight state / prompt / exit_code / held query.
terminal_close(session_id) Close the session, reclaim the process group.
terminal_list() List active sessions.

Configure

Copy config.example.toml. Highlights: listen_addr, data_dir, default_shell, ssh_user, ssh_opts, shell_switch_commands (commands that trigger auto re-arm — add your own, e.g. container-enter commands), auto_rearm, max_buffer_bytes (in-memory tail-cache cap; the full log lives on disk), exec_output_max_bytes (per-call return cap; larger results come back as a paging range), transcript_retention_days, log_dir / log_rotate / log_max_age_days.

Transparent resource guardrails (resource_limit_cmd): a model-invisible ulimit injected alongside the sentinel at session start, and re-injected on every shell switch (ssh/su/docker exec/matrix_jail …) and on hard reset. A ulimit without -S/-H sets both soft and hard limits; the hard limit is inherited by child processes and can't be raised by unprivileged commands, so switching shells or running other commands can't escape the cap:

resource_limit_cmd = "ulimit -v 4194304; ulimit -t 600; ulimit -u 4096"

Boundary: ulimit can't constrain a process that escalates to root and raises its own hard limit, nor a process tree started by a daemon that doesn't inherit this session. For a privilege-independent cap on the local process tree, use cgroup v2 instead.

Customize tool descriptions: use [tool_descriptions] to override the model-facing description per tool (handy when embedding into an external MCP and you want your own wording; missing/empty entries keep the built-in default):

[tool_descriptions]
terminal_open = "Open a persistent terminal session; returns session_id and a read-only web terminal URL."
terminal_send = "Type a command into the session and wait for it to finish; returns new output and status."

Embed it as a library (share one MCP server)

Already run an MCP server and want terminal tools on the same /mcp? go get github.com/fzxbl/terminal-mcp and register onto your own official-SDK server:

mcpserver.Init("config.toml")
mcpserver.SetAdvertiseAddr("10.0.0.5:8080") // host:port used to build terminal_url
mcpserver.SetToolDescriptions(map[string]string{ // optional: reword tool descriptions
    "terminal_open": "Open a persistent terminal session; returns session_id and a web terminal URL.",
})
mcpserver.StartIdleGC(ctx)

mcpserver.RegisterTools(server, auditWriter)          // terminal_* tools
mux.Handle("/debug/terminal/", mcpserver.TerminalHandler()) // web terminal (human takeover)

/mcp path is yours to choose; the web terminal prefix /debug/terminal/ is fixed. Tool descriptions can also be overridden via the [tool_descriptions] config table (precedence: SetToolDescriptions > config > built-in default). See docs/EMBEDDING.md for the full public API reference, the shared-/mcp integration pattern, path conventions, authorization notes, and the config reference.

Security

The HTTP service (MCP endpoint + web terminal) is unauthenticated by default. Calling these tools is equivalent to shell access on the host (or over ssh). Bind it to loopback, or put it behind an authenticating reverse proxy, before exposing it beyond a trusted network.

License

MIT. Contributions and stars welcome.

Directories

Path Synopsis
cmd
terminal-mcp command
internal
logging
Package logging 提供按时间(小时/天)切割、按保存周期清理的日志写入器。
Package logging 提供按时间(小时/天)切割、按保存周期清理的日志写入器。
oplog
Package oplog 是会话输出的 append-only 日志:磁盘 .raw 文件为唯一真相源, 内存只保留最近 cacheBytes 字节的尾部缓存;偏移为日志绝对字节位置(单调)。
Package oplog 是会话输出的 append-only 日志:磁盘 .raw 文件为唯一真相源, 内存只保留最近 cacheBytes 字节的尾部缓存;偏移为日志绝对字节位置(单调)。
pty

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL