termcp
Not only an MCP that lets AI act like a human at the terminal β also a cross-platform terminal management platform: local & remote hosts, one session layer for humans, Agents, and scripts.
English | δΈζ
Introduction
termcp is not only an MCP that lets AI act like a human at the terminal β typing into running processes, answering prompts, driving TUIs and REPLs across conversation turns. More than that, it is a cross-platform terminal management platform written in Go. It treats the terminal session as its unifying primitive and connects two classes of machines: the termcp host itself (built-in loopback profile, zero config) and any remote host (SSH profiles with password / key / jump-host support). Every session is a real PTY channel β hosting multiple shell tabs, port forwards, and SFTP file transfer β opened simultaneously to every kind of user:
- You (human) β a browser-based Web UI for live observation and instant takeover of any session;
- AI Agents β drive the same real terminals through MCP or through SKILLS: the instance ships an installable skill (
/skills.md) that drives it with plain curl, termcp:// locators included;
- Scripts / programs β a full REST API plus WebSocket channel for programmatic session, forward, and file operations.
As an MCP, termcp gives AI a human's hands on a real terminal; as a platform, MCP is just one of its interface layers β the instance also ships an installable Agent Skill (/skills.md) that drives the identical session layer over plain curl. The platform itself is a complete terminal service β suspendable/archivable sessions, parallel multi-session orchestration, a closed-loop SSH connection lifecycle β with a browser terminal, history replay, and a human-in-the-loop control model, forming an observable, programmable, human-and-AI handoff terminal platform.
Written in Go, it ships as a single lightweight binary that runs persistently with low overhead; compiled Go and goroutine concurrency keep it high-throughput and low-latency.
Demo Video
https://github.com/user-attachments/assets/d06a3c36-250a-4eeb-aefa-e80d13d1551c
Why termcp
| Entrance | For | Form |
|---|
| Web UI | Humans | Browser live terminals, session dashboard, tabs, history replay, file/forward panels |
| MCP server | AI Agents | Sessions as persistent connections; Agents manage/drive interactive programs across turns |
SKILLS (/skills.md) | AI Agents | One-file install; drives termcp with curl alone, termcp:// locators included |
| REST API + WebSocket | Scripts | Programmatic session creation, terminal I/O, port forwarding, SFTP file operations |
Breaking the Boundary
Agents can natively only execute one-shot commands β they run and return. But a huge amount of real-world work is multi-turn interaction, for example:
- SSH into a host: enter a password first, then run commands.
- Debug code line by line in a Python REPL.
- Answer a
[Y/n] prompt buried deep inside an installer.
- Drive terminal-dependent tools like
top, htop, or impacket.
In these scenarios the process keeps running, and the Agent must read and write the process's I/O across multiple conversation turns. Plenty of specialized MCPs have sprung up to handle these β but why not just give the Agent hands so it can interact directly? termcp breaks that boundary for AI Agents: no more writing or installing a separate MCP for every interactive tool. The Agent can directly and continuously manage and drive interactive programs like TUIs, REPLs, GDB, msfconsole, vim, and more β through MCP or through the instance's own Agent Skill over plain curl.
Visual Management
termcp provides a session management UI that gives you and the Agent a clear view of everything happening inside the processes:
- Multi-session dashboard: every running session lives here, distinguished by name β switch between them or take over at any time.
- Real-time Agent behavior observation: just like a local terminal, watch
htop's live display, vim's editing process, or an installer's colorful prompts right in the browser β no more guessing at a "black box".
- Tab-based management: under a single SSH session you can open multiple operating shells, each rendered as an independent tab in the UI. The Agent can debug in tab A and tail logs in tab B without interference.
- Port forwarding at a glance: every port-forwarding rule tied to a session is listed in the panel β local/remote ports and protocols, all visible at a glance.
- File management: browse directories, upload/download, rename, and create folders directly from the management UI.
- Centralized connection templates: a unified SSH config store. If you'd rather not expose the actual SSH credentials to the Agent, just tell it the name of the SSH config to use.
Quick Navigation
Features
- β‘ One-command install β
go install github.com/open-mcp-ai/termcp@latest; just a Go toolchain.
- π One port, four entrances β Web UI (humans), MCP / SKILLS (Agents), and REST + WebSocket (scripts) share one port.
- π€ HumanβAI relay β You and the Agent share one live session and you can take over or interrupt at any time; the Agent pauses at
sudo / password / MFA prompts for you to type in the Web UI; input is serialized so keystrokes never collide.
- π¦ Multi-turn interaction on a real terminal β The process keeps running, so an Agent drives TUIs, REPLs, GDB, msfconsole, or vim across conversation turns; a full PTY (ConPTY on Windows) behaves the same on every platform.
- π« Local or remote, one workflow β Zero-config access to the termcp host (
ssh_config="internal") or any remote machine over SSH profiles; commands, file transfer (SFTP plus resumable HTTP URLs), and port forwarding (-L / -R / -D) all run over that single connection.
- π§ Built-in visual management β Browser live terminals, session dashboard, tabbed shells, tiling workspace, history replay, file and forward panels;
/api.html holds the API / MCP / SKILLS cheat sheet.
- π¨ Multiple Agents, no lost history β Parallel readers of one session keep independent cursors; exited or crashed sessions are archived with their full output, survive restarts, and stay searchable, renamable, taggable, and screenshot-able until explicitly deleted. After a drop, open a fresh session from the same entry (
termcp://<entry>) and carry on.
- π₯ Proactive notifications, no polling β
shell_notify wakes the Agent on process exit, silence, or new output β signal only, no payload (pull the text when needed); channel="sampling" sends sampling/createMessage directly.
- π Credential-safe by design β Passwords, private keys, and passphrases written through
ssh_config are never readable back, so plaintext never enters the Agent's context; config-writing tools stay off unless --mcp-manage-ssh-configs is set.
Quick Start
The fastest way to install β one command, no clone, no build:
go install github.com/open-mcp-ai/termcp@latest
go install resolves the module through the Go proxy (use GOPROXY=https://goproxy.cn,direct in mainland China) and drops the termcp binary into $(go env GOPATH)/bin β make sure that directory is on your PATH. termcp is written in Go, so install is go install or a prebuilt Release binary: there is no npx/uvx variant, and it needs no Node or Python runtime. Being a Go module, it also supports source-level integration: go get github.com/open-mcp-ai/termcp to bring it in as a dependency, or fork and build a customized binary from source. Then run:
Download
Head to the Releases page and download the pre-built binary for your platform:
Build
git clone https://github.com/open-mcp-ai/termcp.git
cd termcp
go build -o termcp .
./termcp
Open http://127.0.0.1:18765 in your browser to enter the Web UI.
Usage
Command Line
| Flag | Default | Description |
|---|
--host | 127.0.0.1 | HTTP bind address. 0.0.0.0 listens on all interfaces. A non-loopback bind requires an auth token/hash (startup fails otherwise). |
--port | 18765 | HTTP port. Shared by the Web UI, MCP SSE, MCP streamable HTTP, and the docs/skill endpoints (/api.md, /skills.md). |
--data-dir | ~/.termcp | Persistence directory (sessions, messages, SSH configs). Auto-created. Default overridable via $TERMCP_DATA_DIR. |
--log-level | info | Log level: debug / info / warn / error. debug shows all MCP tool calls; failed tool calls and session-create errors log at warn/error regardless. |
--no-internal | false | Disable the built-in loopback SSH profile. |
--mcp-manage-ssh-configs | false | Enable MCP tools to create/edit/delete SSH configs (secrets are never exposed). |
--auth-token | (unset) | Static token for HTTP authentication (or $TERMCP_AUTH_TOKEN). Every client β API, MCP, browser β must present it. Mutually exclusive with --auth-hash. |
--auth-hash | (unset) | Salted SHA-256 hash of the token (sha256-<salt_hex>-<digest_hex>) so the server never holds the plaintext (or $TERMCP_AUTH_HASH). Generate with termcp --gen-auth-hash. Mutually exclusive with --auth-token. |
--gen-auth-hash | (action) | Generate the salted SHA-256 hash of a token for --auth-hash, then exit (token from an argument, or from stdin without echo on a terminal). |
These flags are your capability gates: --no-internal narrows Agents to remote hosts only, and --mcp-manage-ssh-configs is what opens SSH-config write access. Tighten or loosen what Agents can touch per scenario. See Authentication below.
Examples
./termcp --host 0.0.0.0 --auth-token "your-long-random-token"
./termcp --host 0.0.0.0 --auth-hash "$(./termcp --gen-auth-hash)"
./termcp --mcp-manage-ssh-configs
./termcp --no-internal
Authentication
A single static token protects the whole HTTP surface β the Web UI, REST API, MCP SSE, MCP streamable HTTP, and the browser WebSocket. (The read-only docs /api.md and /skills.md stay public, so an agent can fetch them before it has a token.) Configuring it is optional for loopback-only binds (127.0.0.1 keeps its no-setup default); exposing a non-loopback bind without a token is a startup error.
./termcp --auth-token "your-long-random-token"
TERMCP_AUTH_TOKEN="your-long-random-token" ./termcp
./termcp --gen-auth-hash
TERMCP_AUTH_HASH='sha256-...' ./termcp
How each client presents the token:
| Client | Credential |
|---|
| API / MCP / curl | Authorization: Bearer <token> header |
| Browser (Web UI) | Native login prompt on 401 β the username is ignored (leave it empty), the token is the password. A termcp_token cookie is then set automatically so same-origin WebSocket handshakes authenticate too. |
Behavior notes:
--auth-token and --auth-hash are mutually exclusive; a flag value overrides the environment variable of the same setting.
- A colon inside the token is fine: the server also accepts the whole decoded
user:pass string when it equals the token, so clients that split at the first colon (e.g. curl -u user:pass) still authenticate. curl -u :<token> remains the canonical form.
- Without a token or hash, startup fails on any non-loopback host (
0.0.0.0, a LAN IP, or a hostname other than localhost), so an accidentally exposed instance can never run unauthenticated.
- Browsers use HTTP Basic, which is Base64, not encryption. When serving termcp beyond your own machine, terminate TLS in a reverse proxy in front of it β the
termcp_token cookie then gets the Secure flag automatically only when the request arrived over TLS.
Connecting to Remote Hosts
Zero setup: ssh_config="internal" drives the termcp host itself. To reach a remote machine, create an SSH profile β in the Web UI's new-connection dialog (it ships a TOML template and a Test connection button), or via the REST API PUT /api/connections/<name> with a TOML body:
kind = "remote"
host = "192.168.1.100"
user = "pi"
trust_unknown_host = true
password = "..."
private_key = """-----BEGIN OPENSSH PRIVATE KEY-----
<paste the full content of ~/.ssh/id_ed25519>
-----END OPENSSH PRIVATE KEY-----"""
key_passphrase = "..."
[jump]
host = "bastion.example.com"
user = "ops"
password = "..."
Profiles live in data-dir/ssh_configs/<name>/config.toml; list them with ssh_config(action=list). Credentials written this way are never readable back. Agents can create profiles too, but only when termcp was started with --mcp-manage-ssh-configs.
Docker Deployment
Multi-stage build: add termcp to any container
Place the following Dockerfile in your application project. The build stage installs termcp with go install, then COPY --from copies the binary into the target image. The target container does not need the Go runtime:
# syntax=docker/dockerfile:1
# Replace this at build time with an accessible Go base image if needed
ARG GO_IMAGE=golang:1.25-alpine
FROM ${GO_IMAGE} AS termcp-build
# Go module proxy; use https://proxy.golang.org,direct outside China if preferred
ARG GOPROXY=https://goproxy.cn,direct
ENV GOPROXY=${GOPROXY}
ENV GOBIN=/out
# Pin latest to a concrete version in production, for example @vX.Y.Z
RUN go install github.com/open-mcp-ai/termcp@latest
# Replace with any target base image
FROM alpine
COPY --from=termcp-build /out/termcp /usr/local/bin/termcp
go install downloads termcp and its dependencies through the Go module proxy. GOPROXY defaults to goproxy.cn and can be replaced with --build-arg GOPROXY=.... If Docker Hub is slow or unavailable, use --build-arg GO_IMAGE=... to select an accessible Go base-image mirror.
Startup command examples
Containers must bind to 0.0.0.0, and a non-loopback bind requires authentication β pass the token (or its hash) via TERMCP_AUTH_TOKEN / TERMCP_AUTH_HASH or the matching flags, or startup fails.
docker build \
--build-arg GOPROXY=https://goproxy.cn,direct \
-t my-app-with-termcp .
docker run -d --name my-app-termcp \
-p 18765:18765 \
-v termcp-data:/data \
-e TERMCP_AUTH_TOKEN=change-me-to-a-long-random-secret \
--entrypoint /usr/local/bin/termcp \
my-app-with-termcp \
--host 0.0.0.0 --port 18765 --data-dir /data
docker run -d --name my-app-termcp \
-p 18765:18765 -v termcp-data:/data \
-e TERMCP_AUTH_TOKEN=change-me-to-a-long-random-secret \
--entrypoint /usr/local/bin/termcp \
my-app-with-termcp \
--host 0.0.0.0 --data-dir /data --mcp-manage-ssh-configs
docker logs -f my-app-termcp
If the original application must run in the same container, start termcp from the existing entrypoint or process manager:
export TERMCP_AUTH_TOKEN="change-me-to-a-long-random-secret"
/usr/local/bin/termcp --host 0.0.0.0 --port 18765 --data-dir /data
A container typically runs one foreground process. If the application must remain the main process, run termcp as a separate service on the same Docker network and connect to it at http://termcp:18765/stream.
Docker Compose startup
services:
termcp:
build:
context: .
args:
GOPROXY: https://goproxy.cn,direct
entrypoint: ["/usr/local/bin/termcp"]
command: ["--host", "0.0.0.0", "--port", "18765", "--data-dir", "/data"]
environment:
- TERMCP_AUTH_TOKEN=change-me-to-a-long-random-secret
ports:
- "18765:18765"
volumes:
- termcp-data:/data
volumes:
termcp-data:
docker compose up -d --build
Connecting AI Clients (MCP)
termcp speaks both MCP transports on the same port (18765). Choose whichever your client supports β the tool surface is identical.
termcp is a long-running service: the same port serves the Web UI, any number of MCP clients, and session persistence. It therefore offers HTTP transports only β Streamable HTTP and SSE β and does not support stdio (there is no local subprocess mode).
Alternative: the Agent Skill drives the same sessions over plain curl β the instance serves it at /skills.md. The MCP server is one interface layer of the platform, embeddable into any MCP-capable host β Claude Code, Cursor, Codex, Open WebUI, or your own client.
Option A β Streamable HTTP (/stream)
The modern MCP transport; a single endpoint, no separate message path. Use this for Claude Code, Open WebUI, and most current clients.
{
"mcpServers": {
"termcp": {
"type": "http",
"url": "http://your-server:18765/stream"
}
}
}
claude mcp add --transport http termcp http://localhost:18765/stream
- Same machine:
http://127.0.0.1:18765/stream.
- Open WebUI in Docker, termcp on the host:
http://host.docker.internal:18765/stream (macOS/Windows), or the host's LAN IP.
- Both in Docker on the same network (see Docker Deployment):
http://termcp:18765/stream.
Option B β SSE (/sse)
The legacy transport. Configure only /sse; the SDK posts JSON-RPC to /message automatically.
{
"mcpServers": {
"termcp": {
"type": "sse",
"url": "http://your-server:18765/sse"
}
}
}
claude mcp add --transport sse termcp http://localhost:18765/sse
Cheat sheet
- Streamable HTTP β
http://<host>:18765/stream
- SSE β
http://<host>:18765/sse (JSON-RPC goes to POST /message)
The Web UI's API / MCP / SKILLS page (/api.html) offers copy-ready config for both transports, plus the Agent-docs and skill-download addresses for this instance.
Agent Skill (curl-only, no MCP)
Don't want to configure an MCP client? The instance ships an installable
Agent Skill that teaches any agent to drive termcp with curl alone β
including the termcp:// locators users paste from the Web UI.
curl -fsS http://<host>:18765/skills.md -o /tmp/termcp-SKILL.md
mkdir -p ~/.claude/skills/termcp && cp /tmp/termcp-SKILL.md ~/.claude/skills/termcp/SKILL.md
mkdir -p ~/.agents/skills/termcp && cp /tmp/termcp-SKILL.md ~/.agents/skills/termcp/SKILL.md
Restart the agent session after installing (skills are loaded at session start).
Claude Code has no per-skill CLI command β adding is "drop the file in", removing
is rm -rf ~/.claude/skills/termcp (or claude plugin install/uninstall when the
skill ships as a plugin).
Once installed, a request as simple as "open termcp://rock64 and run uname -a"
works end to end: the skill resolves the locator via
GET /api/resolve?url=..., creates the session with that ssh_config, sends the
command, and polls the output. The same skill is registered as the MCP resource
<origin>/skills.md, and /api.html shows the exact install command for the
instance you are looking at.
Connecting Scripts / Programs (REST API)
Skip MCP and use the same session layer programmatically: the full REST API and live WebSocket channel.
curl -H "Authorization: Bearer $TERMCP_AUTH_TOKEN" http://127.0.0.1:18765/api/sessions
curl -X POST http://127.0.0.1:18765/api/sessions \
-H "Authorization: Bearer $TERMCP_AUTH_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"ssh_config":"internal","command":"bash","mode":"pty"}'
Live terminal I/O runs over WebSocket /api/ui/ws; files support direct HTTP URLs with Range resume. Full endpoint list in docs/api.md.
With authentication enabled
When the server runs with --auth-token/--auth-hash, every MCP request needs the token as an Authorization: Bearer header:
claude mcp add --transport http termcp http://your-server:18765/stream \
--header "Authorization: Bearer $TERMCP_AUTH_TOKEN"
{
"mcpServers": {
"termcp": {
"type": "http",
"url": "http://your-server:18765/stream",
"headers": { "Authorization": "Bearer <your-token>" }
}
}
}
Keep the token out of URLs and out of shared configs/screenshots. curl and scripts use the same header:
curl -H "Authorization: Bearer $TERMCP_AUTH_TOKEN" http://your-server:18765/api/sessions
termcp exposes 31 MCP tools. Full parameters, return shapes, and error codes live in docs/mcp-tools.md.
| Area | Tools |
|---|
| Sessions (connection containers) | session_start, session_list, session_info, session_terminate |
| Shells (terminal channels) | shell_open, shell_list, shell_close, shell_input, shell_key, shell_output, shell_resize, shell_reader_register, shell_reader_unregister |
| Notifications | shell_notify (wakes the AI Agent), notify_user (toasts the human at the Web UI) |
| SSH profiles | ssh_config (list; create/edit/copy/delete with --mcp-manage-ssh-configs) |
| Port forwarding | forward (-L / -R / -D / list / close) |
| Files (SFTP) | file_read, file_write, file_stat, file_delete, file_rename, file_mkdir, file_urls, file_perm, file_link, file_fs, file_getwd |
| History & messages | history (list / search / rename / meta / purge / screenshot), message (list / get) |
| Host discovery | shell_detect |
Run a command as shell_input + shell_key(key="enter") + shell_output. Failed tools return isError=true with a JSON body carrying a stable error_code.
Known Limitations
history screenshots are ASCII-only. history(action=screenshot) renders the persisted text as a fixed-bitmap terminal image; it is not a pixel-accurate rendering of non-ASCII glyphs.
- File and forward tools need a live connection. On
exited/archived sessions those tools return session_not_running; output reading still works via shell_output.
- No command allowlisting or directory jail. termcp does not enforce command whitelists, path restrictions, or policy-based risk tiers. Risk control is human-in-the-loop instead: interrupt the Agent from the Web UI at any time, and privileged prompts (
sudo / password / MFA) are by default handed to you β Agents follow a no-guessing, no-echoing convention and pause for you to type. Whether the Agent may type them anyway is your call; termcp does not forbid it.
- Basic authentication needs TLS outside localhost. The browser login challenge uses HTTP Basic, whose credentials are only Base64-encoded. Put a TLS-terminating reverse proxy in front of termcp when exposing it beyond a trusted local network; the static token is still never logged or placed in a URL.