The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Forge listing page.
Terminal MCP server for AI coding agents. Spawn, manage, and monitor real PTY sessions via the Model Context Protocol.
AI coding agents (Claude Code, Codex, etc.) typically run one command at a time. Forge gives them persistent terminals — run your React frontend, Java API, and Postgres migrations in parallel, monitor all three, and only read what changed. Full-stack work without the bottleneck.
Works with any MCP-compatible client — Claude Code, Codex, Gemini CLI, or your own agent.
Key differentiators:
node-pty (same lib as VS Code terminal) — interactive programs, colors, TUI apps all workread_terminal only returns NEW output, saving context window tokens@xterm/headless renders the terminal server-side, so read_screen returns exactly what a human would see (no ANSI escape codes)npx command or HTTP MCP endpointAfter install, the forge command is available globally:
Note: Always use HTTP transport so Claude Code connects to the running daemon. This ensures sessions appear in the dashboard and avoids conflicts from isolated processes.
Codex stores this in ~/.codex/config.toml:
Start the daemon (choose one launch mode), then point any MCP client at the HTTP endpoint:
Restart your agent and Forge tools are available.
Important: Claude Code, Codex, and Gemini CLI load MCP servers at process start. If you add/remove servers, restart the current agent session.
Expected:
codex mcp list shows forge as enablednode dist/cli.js status reports running and http://127.0.0.1:3141| Symptom | Fix |
|---|---|
forge: command not found | Use node dist/cli.js ... from the repo root, or npx forge-terminal-mcp .... |
No MCP servers configured yet in Codex | Run codex mcp add forge --url http://127.0.0.1:3141/mcp, then restart Codex. |
listen EPERM ... 127.0.0.1:3141 | Run Forge in an environment that allows local port binding, or use a different port with --port and update the MCP URL to match. |
| A message appears typed but agent does not answer | The input may be queued; press Enter (or use submit=true when writing programmatically). |
| MCP server is configured but tools do not appear | Restart the current agent session so MCP servers reload. |
Your agent now has access to 23 tools across 7 categories:
Session Lifecycle
I/O
Search & Wait
Execution
Events
Agent Delegation
Ops
You: Start a Next.js dev server and run the test suite in parallel
Agent: (uses
create_from_templatewith "next-dev",wait_for"Ready", then creates a second session fornpm test, usesread_multipleto poll both)
You: Spin up 3 sub-agents to research different parts of the codebase
Agent: (uses
spawn_claudethree times with tag "research", monitors withlist_terminalsfiltered by tag, cleans up withclose_group)
You: Build and test, just give me the result
Agent: (uses
run_commandwithnpm run build && npm test— creates terminal, waits for exit, returns output, auto-cleans up)
run_command vs create_terminalUse run_command when you want a result and don't need the session afterwards:
npm run build, cargo build)npm test, pytest)npm install, pip install)Use create_terminal when you need an ongoing session:
npm run dev, vite, next dev)npm run watch, tsc --watch)read_terminalwaitForExit vs pattern matchingUse pattern matching (default) when the process stays alive after printing the signal:
Use waitForExit: true when the process exits naturally and you want all output:
fromSession for sub-agentsWhen spawning a sub-agent to work on the same project, use fromSession instead of hardcoding paths:
This ensures the sub-agent works in the correct directory even when Forge is used across different machines or worktrees.
When running multiple agents on the same codebase, use worktree: true to isolate changes:
create_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
command | string | User's $SHELL | Command to run |
args | string[] | [] | Command arguments |
cwd | string | Process cwd | Working directory |
env | object | {} | Additional env vars (merged with process env) |
cols | number | 120 | Terminal width |
rows | number | 24 | Terminal height |
name | string | — | Human-readable session name |
tags | string[] | — | Tags for filtering/grouping (max 10) |
bufferSize | number | Server default | Ring buffer size in bytes (1 KB – 10 MB) |
Returns session info including the id used by all other tools.
create_from_template| Parameter | Type | Default | Description |
|---|---|---|---|
template | string | required | Template name (see list_templates) |
cwd | string | — | Working directory override |
env | object | — | Additional env vars |
name | string | Template name | Session name override |
Built-in templates:
| Template | Command | Tags | Wait For |
|---|---|---|---|
shell | $SHELL | shell | — |
next-dev | npx next dev | dev-server, next | "Ready" |
vite-dev | npx vite | dev-server, vite | "Local:" |
docker-compose | docker compose up | docker | — |
npm-test | npm test | test | — |
npm-test-watch | npm run test:watch | test, watch | — |
Templates with waitFor automatically block until the pattern appears (30s timeout).
spawn_claude| Parameter | Type | Default | Description |
|---|---|---|---|
prompt | string | required | Prompt to send to Claude |
cwd | string | — | Working directory (explicit path) |
fromSession | string | — | Copy cwd from an existing session ID (alternative to setting cwd manually) |
model | string | — | Model (e.g., "sonnet", "opus") |
name | string | Auto from prompt | Session name |
tags | string[] | ["claude-agent"] | Tags (claude-agent always included) |
maxBudget | number | — | Max budget in USD |
bufferSize | number | Server default | Ring buffer size |
worktree | boolean | false | Create a git worktree (isolates file changes) |
branch | string | — | Branch name for worktree (required when worktree: true) |
oneShot | boolean | false | Run in --print mode (process prompt and exit) |
run_command| Parameter | Type | Default | Description |
|---|---|---|---|
command | string | required | Command to run (supports && chaining) |
cwd | string | — | Working directory |
timeout | number | 300000 | Timeout in ms (max 5 minutes) |
Creates a terminal, waits for the process to exit, returns all output, and auto-cleans up the session. Ideal for build/test/install commands.
write_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
input | string | required | Text to send |
newline | boolean | true | Append \n after input |
read_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
Returns { status, data, bytes, droppedBytes? }. Only returns output produced since the last read. If droppedBytes > 0, some output was lost because the ring buffer wrapped.
read_screen| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
Returns the current terminal viewport as plain text — rendered through a headless xterm instance. No ANSI codes. Useful for TUI apps like htop, vim, or interactive prompts.
read_multiple| Parameter | Type | Default | Description |
|---|---|---|---|
ids | string[] | required | Session IDs (1–20) |
mode | string | "incremental" | "incremental" or "screen" |
Returns a JSON array with per-session results. Sessions that error (e.g., not found) include an inline error field — the tool never fails as a whole, so partial results are always returned.
grep_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
pattern | string | required | Regex pattern |
context | number | 0 | Lines of context around each match (0–10) |
Returns { matches: [{ lineNumber, text, context? }], totalMatches }.
wait_for| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
pattern | string | — | Regex pattern to wait for |
timeout | number | 30000 | Timeout in ms (100–300000) |
waitForExit | boolean | false | Wait for process to exit instead of pattern match |
Checks the existing buffer first (instant match if pattern already appeared), then watches new output. Returns { matched, data?, reason?, elapsed }.
subscribe_events| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
events | string[] | required | ["exit"] and/or ["pattern_match"] |
pattern | string | — | Regex (required if pattern_match in events) |
Notifications are delivered as MCP logging messages with JSON payloads. Pattern match subscriptions auto-unsubscribe after the first match.
unsubscribe_events| Parameter | Type | Default | Description |
|---|---|---|---|
subscriptionId | string | required | Subscription ID from subscribe_events |
list_terminals| Parameter | Type | Default | Description |
|---|---|---|---|
tag | string | — | Filter sessions by tag |
Returns all sessions with id, pid, command, cwd, status, cols, rows, createdAt, lastActivityAt, name, tags.
close_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
close_group| Parameter | Type | Default | Description |
|---|---|---|---|
tag | string | required | Tag to match |
Closes all active sessions with the matching tag. Returns the count closed.
send_control| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
key | string | required | Control key name |
Available keys: ctrl+c, ctrl+d, ctrl+z, ctrl+\, ctrl+l, ctrl+a, ctrl+e, ctrl+k, ctrl+u, ctrl+w, ctrl+r, ctrl+p, ctrl+n, up, down, right, left, home, end, tab, enter, escape, backspace, delete, pageup, pagedown
resize_terminal| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
cols | number | required | New width (1–500) |
rows | number | required | New height (1–200) |
health_checkNo parameters. Returns { version, uptime, sessions: { active, max }, memory: { rss, heapUsed, heapTotal } }.
get_session_history| Parameter | Type | Default | Description |
|---|---|---|---|
id | string | required | Session ID |
Returns timestamped tool call history for agent sessions (Claude, Codex).
clear_historyNo parameters. Clears persisted stale session entries from previous server runs.
Sessions are also exposed as MCP resources at terminal://sessions/{sessionId}, returning session metadata and rendered screen content. The resource list updates automatically when sessions are created or closed.
Enable the real-time web dashboard to monitor all terminals from your browser:
Open http://localhost:3141 to see:
If --auth-token is enabled, open the dashboard with ?token=YOUR_TOKEN so browser API/WebSocket calls are authorized.
The dashboard is built with Preact + htm + Preact Signals, loaded from CDN with zero build step. All UI code is bundled as string constants inside the server binary.
Note: The pre-built macOS app is currently unavailable for general download. macOS requires apps to be code-signed with an Apple Developer certificate ($99/year) before they can be opened without security warnings. We're working on getting this set up — in the meantime, you can run the desktop app from source (see below) or use the CLI via
npx forge-terminal-mcp.
Forge includes an Electron-based desktop app for macOS with native window management, system tray, and notifications.
Or from the repo root: npm run desktop:dev
Produces a signed Forge.app in desktop/release/. Requires Apple Developer certificate for notarization (see desktop/forge.entitlements.plist).
All settings follow the precedence: CLI flag > environment variable > default.
| Flag | Env Var | Default | Description |
|---|---|---|---|
--max-sessions | FORGE_MAX_SESSIONS | 10 | Max concurrent PTY sessions |
--idle-timeout | FORGE_IDLE_TIMEOUT | 1800000 | Session idle timeout in ms (30 min) |
--buffer-size | FORGE_BUFFER_SIZE | 1048576 | Ring buffer size per session (1 MB) |
--shell | SHELL | /bin/bash | Default shell for create_terminal |
--claude-path | FORGE_CLAUDE_PATH | claude | Path to Claude CLI binary |
--auth-token | FORGE_AUTH_TOKEN | unset | Require Bearer token for /mcp, /api, and /ws |
--dashboard | FORGE_DASHBOARD | off | Enable web dashboard |
--port | FORGE_DASHBOARD_PORT | 3141 | Dashboard port |
--verbose | — | off | Enable debug logging to stderr |
Example with custom config:
droppedBytes tells the consumer how much was lostread_screen returns the rendered viewport, correctly handling cursor positioning, alternate screen, line wrapping~/.forge/sessions.json, reloaded as stale entries on restartCLAUDECODE) removed to prevent nesting errors| Suite | Tests | Covers |
|---|---|---|
| Ring Buffer | 13 | Circular writes, multi-consumer, wrap-around, dropped bytes |
| Config | 10 | CLI parsing, env vars, defaults, precedence, codex path |
| Control Chars | 6 | Key resolution, case insensitivity, unknown keys |
| State Store | 4 | Load/save round-trip, corruption handling |
| Templates | 3 | Lookup, unknown template, list all |
| Stream JSON Parser | 11 | Claude event parsing, tool use extraction |
| Terminal Session | 8 | PTY spawn, read/write, screen render, resize, exit |
| Session Manager | 7 | CRUD, max limit, close all, stale entries |
| MCP Tools E2E | 51 | All 23 tools end-to-end via MCP client |
| Forge 0.7 Features | 28 | Codex spawn, worktree, dashboard API, chat history |
| Command History | 6 | Event tracking, retrieval, cleanup |
| Claude Chats | 14 | Session discovery, message parsing, search |
| Total | 161 |
node-pty builds)MIT