Skip to main content
Glama
portal
by tiefeiyu

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.

Python 3.11+ MCP 1.x Windows POSIX uv License LICENSE 996.icu

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_screen captures 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

ls, echo, build 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 launch

Requires 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-mcp

The 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

process_start

Start a subprocess with optional args, cwd, env, timeout

process_read

Read output records (stdout/stderr/stdin/both) within a time window

process_read_new

Read output produced since the last read (per source, server-side cursor)

process_write

Write content to a process's stdin

process_signal

Send an OS-native signal to a process

process_list

List all managed processes with summary info

process_inspect

Get detailed info about a process

process_kill

Kill a single process (output data retained)

process_kill_all

Kill all managed processes

process_clear

Clear I/O records for a process

process_cleanup

Remove a terminated process and all its data

process_screen

Snapshot the live screen of a PTY process (for full-screen TUIs)

program_query

Look up whether a program needs a PTY (persistent registry)

program_record

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 run

  • args (optional): List of command-line arguments

  • cwd (optional): Working directory

  • env (optional): Environment variables (merged with current env)

  • timeout_ms (optional): Idle timeout in milliseconds (0 = no timeout)

  • pty (optional, default false): Run on a virtual PTY instead of pipes — set true for programs that check isatty() (ssh, gdb, psql, REPLs) or render full-screen TUIs (vim, htop, less); when in doubt, use true (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 ID

  • source (optional): stdout, stderr, stdin, or both (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 ID

  • source (optional): stdout, stderr, or both (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 ID

  • content (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 ID

  • signal (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_read with source="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) via process_write; a KeyboardInterrupt traceback is expected output

  • process_signal on PTY processes supports only SIGTERM (terminate, a hard kill on Windows), SIGKILL (kill) and CTRL_C_EVENT (graceful Ctrl+C); other signals are rejected

  • exit code is null on 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 isatty()

ssh, gdb, psql, mysql, telnet, REPLs

pty: true

Full-screen TUI

vim, htop, top, less, man

pty: true

Interactive flags

-i / -it / -t

pty: true

One-shot / batch

python -c, build commands

pty: false

Paged one-shots

git log/diff, less

pipe mode + --no-pager/GIT_PAGER=cat

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/ -v

License

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 tools
process_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

    A4.2/5.0

    Scored across 13 tools

    Disambiguation5/5

    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.

    Naming Consistency5/5

    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.

    Tool Count5/5

    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.

    Completeness5/5

    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

    ActivityMaintained
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Provides 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 npm
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables 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 npm
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      Enables AI agents to start and manage pseudo-terminal sessions, run shell commands and interact with REPLs programmatically.
      7
      -