portal
Portal is an MCP server for starting, interacting with, and managing interactive terminal programs (SSH, REPLs, TUIs, etc.) via tool calls.
Start processes with arguments, working directory, environment, idle timeout, and optional PTY mode for interactive/TUI programs
Read process output by time window or incrementally since last read, from stdout/stderr/stdin, with ANSI stripping
Write to stdin including sending Ctrl+C interrupts to PTY processes
Send OS signals to processes (full signal set in pipe mode; limited PTY signal support)
List and inspect running/managed processes with metadata and I/O counts
Kill individual processes or all processes; kill-all is immediate
Clear or clean up process I/O records and terminated process data
Snapshot live PTY screens for full-screen TUIs like vim/htop/less, with optional resize
Query and record persistent program registry facts about whether executables need a PTY
Allows interaction with the MySQL command-line client as an interactive process, providing tools to start, monitor, read/write I/O, and control it.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@portal Start a psql session and run: SELECT * FROM users;"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Portal — MCP Server for Interactive Programs
An MCP (Model Context Protocol) server purpose-built for interactive programs. Start, monitor, read/write I/O, and control processes that need sustained bidirectional interaction — all through MCP tool calls. Where a one-shot Shell tool hangs on interactive programs, Portal is designed for them.
Why Portal
AI agents (Claude, Cursor, …) can drive interactive programs the way they drive a shell — minus the hanging:
Interactive programs, end to end — start, read/write I/O, signal, and kill processes that require sustained bidirectional interaction (SSH, GDB, database CLIs, REPLs, full-screen TUIs).
Real terminal (PTY) support — ConPTY on Windows (10 1809+) and ptyprocess on POSIX. Programs that check
isatty()(ssh, gdb, psql, REPLs) behave exactly as they do in a human-run terminal.Full-screen TUI snapshots —
process_screencaptures the live screen of vim, htop, less and other full-screen TUIs, whose raw record streams are garbled fragments.Never hangs, never orphans — a per-process idle timeout kills stuck programs and cleans up their data.
Learns from experience — a persistent, machine-global registry (
program_query/program_record) remembers which executables need a PTY, with a confirmation counter so settled facts stick.Clean output — ANSI escape sequences stripped; I/O history stored in SQLite with timestamps, queryable per source (stdout / stderr / stdin).
Cross-platform — Windows and POSIX, Python 3.11+, run via uv with zero installation.
When to use it
Situation | Examples | Use |
Interactive programs | ssh, gdb, psql, mysql, telnet, REPLs | Portal |
Full-screen TUIs | vim, htop, less, top, man | Portal |
One-shot commands |
| built-in Shell tool |
Related MCP server: PTY MCP Server
Quick Start
No package installation needed — Portal runs directly with uv:
uv sync # optional: pre-create .venv; `uv run` auto-syncs on first launchRequires uv and Python 3.11+. Dependencies are
managed by uv from pyproject.toml: mcp (1.x), aiosqlite, pyte, plus
pywinpty (Windows) / ptyprocess (POSIX).
MCP client configuration
Add to your MCP client configuration (e.g., Claude Code), substituting
<PORTAL_PATH> with the absolute path of this repo:
{
"mcpServers": {
"portal": {
"type": "stdio",
"command": "uv",
"args": ["run", "--project", "<PORTAL_PATH>", "portal-mcp"]
}
}
}Claude Code one-liner:
claude mcp add portal -- uv run --project "<PORTAL_PATH>" portal-mcpThe PORTAL_DB_PATH environment variable controls the SQLite database
location. By default each server instance uses its own file,
.portal/portal-<pid>-<timestamp>.db in the working directory — created
fresh on every startup, kept as timestamped history, and named
per-instance so a lingering or concurrent server can never lock the
file and block a new one.
One-Click Install
Copy the prompt from llms-install.md into your AI
agent to automatically install and configure Portal.
MCP Tools
Tool | Description |
| Start a subprocess with optional args, cwd, env, timeout |
| Read output records (stdout/stderr/stdin/both) within a time window |
| Read output produced since the last read (per source, server-side cursor) |
| Write content to a process's stdin |
| Send an OS-native signal to a process |
| List all managed processes with summary info |
| Get detailed info about a process |
| Kill a single process (output data retained) |
| Kill all managed processes |
| Clear I/O records for a process |
| Remove a terminated process and all its data |
| Snapshot the live screen of a PTY process (for full-screen TUIs) |
| Look up whether a program needs a PTY (persistent registry) |
| Record a confirmed program fact in the persistent registry |
Tools Reference
process_start
Start a subprocess and begin capturing its output.
command(required): Executable or command to runargs(optional): List of command-line argumentscwd(optional): Working directoryenv(optional): Environment variables (merged with current env)timeout_ms(optional): Idle timeout in milliseconds (0 = no timeout)pty(optional, defaultfalse): Run on a virtual PTY instead of pipes — settruefor programs that checkisatty()(ssh, gdb, psql, REPLs) or render full-screen TUIs (vim, htop, less); when in doubt, usetrue(see Virtual PTY support)
Returns: id, os_pid, status
process_read
Read captured output from a process. Resets the idle timer.
id(required): Internal process IDsource(optional):stdout,stderr,stdin, orboth(default:both)duration(optional): How far back to read (default: 1000)unit(optional): Time unit —ns,us,ms,s(default:ms)
Returns: List of records with timestamp, source, content
process_read_new
Read output produced since the last call to this tool, for the requested source. Resets the idle timer.
id(required): Internal process IDsource(optional):stdout,stderr, orboth(default:both)
The server remembers the read position per process and per source, so
repeated calls return each record exactly once, in insertion order.
Only the requested source's cursor advances — reading stdout never
causes stderr records to be skipped and vice versa. process_read
(time-window) does not affect these cursors; process_clear resets
them.
Returns: List of records with timestamp, source, content
process_write
Write content to the process's stdin. Only works while running.
id(required): Internal process IDcontent(required): String to write
process_signal
Send an OS signal. On Windows, supports CTRL_C_EVENT (0) and
CTRL_BREAK_EVENT (1). On POSIX, supports the full signal set
(SIGTERM, SIGKILL, SIGINT, etc.).
id(required): Internal process IDsignal(required): Signal name or number
process_list
List all managed processes.
Returns: Array of {id, os_pid, status, timeout_ms, inactive_duration_ms, io_count}
process_inspect
Get full details of a single process.
id(required): Internal process ID
Returns: All metadata plus io_count, stdout_count, stderr_count, stdin_count
process_kill
Kill a single process. Output data is retained for later reading.
id(required): Internal process ID
process_kill_all
Kill all managed processes immediately.
process_clear
Clear all I/O records for a process. The table remains, records are deleted.
id(required): Internal process ID
process_cleanup
Remove a terminated process and all its data. Only allowed for exited
or killed processes.
id(required): Internal process ID
Virtual PTY Support
By default Portal runs programs on OS pipes. Programs that check
isatty() (ssh, gdb, psql, interactive REPLs) or render full-screen
TUIs (vim, htop, less) need a virtual PTY instead: pass "pty": true
to process_start. On Windows this uses ConPTY (Windows 10 1809+);
on POSIX it uses ptyprocess.
PTY mode differences from pipe mode:
stdout and stderr are merged into one console stream (
process_readwithsource="stderr"returns empty)records are arbitrary chunks, not lines — a line may span multiple records, and prompts may arrive without a trailing newline
input you write is echoed back into the output stream (real terminal behavior) — treat echoes as your own input
to interrupt a PTY process, write
and a carriage return as two separate writes (Ctrl+C then Enter — split delivery is measurably more reliable) viaprocess_write; aKeyboardInterrupttraceback is expected outputprocess_signalon PTY processes supports only SIGTERM (terminate, a hard kill on Windows), SIGKILL (kill) and CTRL_C_EVENT (graceful Ctrl+C); other signals are rejectedexit code is
nullon Windows (ConPTY exposes none)
process_screen snapshots the live screen of a PTY process — use it
for full-screen TUIs, whose record streams are garbled fragments.
Passing cols/rows resizes the live PTY first; omitting them is a
pure snapshot. The screen stays queryable after exit until
process_cleanup.
Choosing pty
Signal | Examples | Decision |
Checks | ssh, gdb, psql, mysql, telnet, REPLs |
|
Full-screen TUI | vim, htop, top, less, man |
|
Interactive flags |
|
|
One-shot / batch |
|
|
Paged one-shots | git log/diff, less | pipe mode + |
When in doubt, use pty: true — a non-interactive program tolerates
a PTY; an interactive one without one hangs.
Program registry
program_query / program_record maintain a persistent, machine-global
registry (programs.db in the platform app-data dir, or
PORTAL_DATA_DIR if set) of which executables need a PTY. Agents
record conclusions after first encounters — including negatives.
Repeated confirmation increments confirmed_count (>= 2 means
settled); recording the opposite value resets it (a correction).
Process Lifecycle
START → RUNNING → EXITED → (read-only, data retained)
→ KILLED → (read-only, data retained)
→ timeout → KILL + CLEANUP (data removed)READ and WRITE operations reset the idle timer, preventing timeout kills.
ANSI Stripping
All color codes and cursor movement sequences (\x1b[...m, \x1b[...J,
etc.) are stripped from output before storage. In pipe mode content is
otherwise stored as-is, preserving whatever newline conventions the
process uses; in PTY mode bare carriage returns (\r) are also removed
(ConPTY emits \r\n).
Architecture
MCP Client
│ JSON-RPC (stdio)
▼
Portal MCP Server
├── ProcessManager (lifecycle + timeout monitor)
│ └── ManagedProcess (per-process wrapper)
│ └── asyncio.subprocess.Process
└── Database (SQLite via aiosqlite)
├── processes table (metadata)
└── proc_<id> tables (I/O records)Development
# Sync the uv environment (includes pytest)
uv sync
# Run tests
uv run pytest tests/ -vLicense
Dual-licensed: MIT AND the Anti 996 License — free to use, modify, distribute, and integrate into commercial projects, with attribution, provided both licenses are satisfied. See LICENSE for the summary.
Author
Developed and maintained by TieFeiyu.
Available Tools
13 toolsprocess_cleanupA
Remove a terminated process and all its data. Only allowed for processes with status 'exited' or 'killed'.
Inputs: id (required).
process_clearA
Clear all I/O records for a process. The process table remains, but records are deleted.
Inputs: id (required).
process_inspectA
Get detailed information about a single process including all metadata and per-stream I/O counts.
For PTY processes: stderr_count is always 0 (merged stream) and I/O counts are chunk-based, not line-based; exit_code is null on Windows (ConPTY exposes none).
Inputs: id (required).
process_killA
Kill a single process. Its output data is retained for reading. Use process_cleanup to remove it.
Inputs: id (required).
process_kill_allB
Kill all managed processes.
Inputs: no named parameters.
process_listA
List all managed processes with summary info: id, os_pid, status, timeout, idle duration, and I/O record count.
Inputs: no named parameters.
process_readA
Read output from a process. Reads records from the specified time window. Resets the process idle timer.
For PTY processes stderr is merged into stdout (source=stderr reads return empty) and records are arbitrary chunks, not lines — a line may span multiple records. Prompts may arrive without a trailing newline. Read with a generous duration — the default window is only 1s.
Inputs: id (required), unit, source, duration.
process_screenA
Snapshot the live screen of a PTY process. For full-screen TUIs (vim, htop, less): the record stream is garbled fragments — use this tool instead of process_read. Screen remains queryable after exit until process_cleanup. Snapshot is pure; passing cols/rows resizes the live PTY first. buffer is 'primary' or 'alternate' (the TUI's alternate screen).
Inputs: id (required), cols, rows.
process_signalA
Send a signal to a process by name or number. Available signals: SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIO, SIGIOT, SIGKILL, SIGPIPE, SIGPOLL, SIGPROF, SIGPWR, SIGQUIT, SIGRTMAX, SIGRTMIN, SIGSEGV, SIGSTKFLT, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ. For PTY processes only SIGTERM -> terminate, SIGKILL -> kill, and CTRL_C_EVENT -> Ctrl+C are supported; other signals are rejected with an error. For a graceful interrupt use process_write: send and a carriage return as two separate writes(split delivery).
Inputs: id (required), signal (required).
process_startA
Start a subprocess for interactive use. Use this for interactive programs like SSH, GDB, psql, python REPL, etc. — not for simple one-shot commands. Returns the internal process ID, OS PID, and initial status.
pty — default false for backward compatibility; this is NOT a recommendation. Set true for anything interactive or TUI (ssh, gdb, psql/mysql, REPLs, vim, htop, top, less; anything with -i/-it/-t flags). Consult program_query for this executable BEFORE starting. When in doubt, true — a non-interactive program tolerates a PTY; an interactive one without one hangs. Exception: one-shot commands that page output (git log/diff, less) — prefer pipe mode with --no-pager/GIT_PAGER=cat.
Inputs: command (required), cwd, env, pty, +2 more.
process_writeA
Write content to a process's stdin. Only available while the process is running.
Input you write reappears in the output stream (terminal echo) — treat it as your own input, not program output, and do not re-send it. To interrupt a PTY process, send and a carriage return as two separate writes (Ctrl+C then Enter — ConPTY is line-buffered; split delivery is measurably more reliable); a KeyboardInterrupt traceback in output is expected, not an error. process_signal/process_kill are fallbacks — see their descriptions for the PTY signal mapping (CTRL_C_EVENT is a graceful interrupt, not a hard stop).
Inputs: id (required), content (required).
program_queryA
Look up whether a program needs a PTY in the persistent program registry. Call BEFORE process_start. A miss is a normal result, not an error — it means apply the decision rules in the instructions. confirmed_count >= 2 means settled; a single confirmation is a hint — re-verify on first use.
Inputs: program (required).
program_recordA
Record a confirmed program fact in the persistent registry after observing its behavior: needs_pty true if it required a terminal (TTY error, hang in pipe mode, or TUI rendering with pty:true), false if it ran fine without one. Record every first-encounter conclusion, including negatives. notes should carry flag-specific caveats (e.g. 'docker run -it only, not docker build').
Inputs: program (required), needs_pty (required), notes.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
Each tool has a clearly distinct purpose: listing vs inspecting, killing one vs all, clearing data vs removing process, and reading vs writing vs signaling. The process_* and program_* prefixes further separate management from registry operations, leaving no ambiguity.
All tools follow a consistent noun_verb snake_case pattern, with process_ for process operations and program_ for registry operations. Verbs are descriptive and uniform (list, inspect, kill, read, write, etc.), making the pattern predictable.
With 13 tools, the set is well-scoped for a process management server, covering start, interaction, monitoring, control, and cleanup. Each tool earns its place and the count is appropriate for the domain.
The tool surface covers the full lifecycle of managed processes: start, read, write, signal, kill, clear, cleanup, list, inspect, and screen snapshot. The program registry adds query/record for PTY decisions, completing the workflow without evident gaps.
Maintenance
Related MCP Connectors
Securely control computers you explicitly pair through files, terminals, processes, screenshots, desktop UI/input, clipboard, browser automation, diagnostics, and document tools.
Remote shell and detached long-running jobs on your own machines — no SSH, open ports or VPN.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Manage Sprites: sandboxed compute environments with exec, services, and checkpoints.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides tools for executing shell commands both synchronously and asynchronously with real-time output streaming and process management capabilities. It enables users to start background tasks, monitor progress, and manage long-running processes via Stdio or HTTP transports.511 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage interactive pseudo-terminal (PTY) sessions for programs requiring full terminal emulation like vim, ssh, and interactive REPLs. It provides tools to spawn sessions, send input with escape sequences, and read buffered terminal output.12 npmMIT
- FlicenseAqualityDmaintenanceEnables AI agents to start and manage pseudo-terminal sessions, run shell commands and interact with REPLs programmatically.7-
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to launch and interact with terminal/TUI programs, providing tools to inspect and control live CLI/TUI sessions.23 npm1Apache 2.0