MCP server that lets AI agents drive interactive terminal programs via persistent PTY sessions. Start a shell, send keystrokes, capture screen snapshots, and stop the session -- all through four simple tools.
Works with any interactive program: vim, ssh, python REPL, htop, psql, and more.
Run the server:
uv run --script server.pyRequires Python 3.10+. Dependencies (mcp[cli]>=1.0, pydantic>=2) are declared inline and installed automatically by uv.
Add to your ~/.claude.json to make it available in Claude Code:
{
"mcpServers": {
"interactive-shell": {
"command": "uv",
"args": ["run", "--script", "/path/to/interactive-shell-mcp/server.py"]
}
}
}Replace /path/to/ with the actual path to the project directory.
Launch a command in a persistent terminal session.
| Parameter | Type | Default | Description |
|---|---|---|---|
| command | string | required | Shell command to run |
| cwd | string | /tmp | Working directory for the process |
| rows | int | 30 | Terminal height (minimum 10) |
| cols | int | 120 | Terminal width (minimum 40) |
| session | string | "" | Optional stable session ID. Auto-generated UUID if omitted |
Returns: {session, pid, rows, cols, alive}
Capture the current screen content.
| Parameter | Type | Default | Description |
|---|---|---|---|
| session | string | required | Session ID from start |
Returns: {ok, alive, lines} where lines is an array of {line: int, text: str} (1-based line numbers, only non-empty lines included).
Send text and special keys to the terminal.
| Parameter | Type | Default | Description |
|---|---|---|---|
| session | string | required | Session ID from start |
| input | string | required | Input payload (plain text + key tokens) |
Returns: {ok: true}
Terminate the session and its child process.
| Parameter | Type | Default | Description |
|---|---|---|---|
| session | string | required | Session ID from start |
Returns: {ok: true, stopped: true}
The input parameter supports plain text mixed with special key tokens and escape sequences.
| Token | Key |
|---|---|
<CR> |
Enter / Return |
<ENTER> |
Enter / Return |
<ESC> |
Escape |
<TAB> |
Tab |
<BS> |
Backspace |
<BACKSPACE> |
Backspace |
<SPACE> |
Space |
<UP> |
Arrow Up |
<DOWN> |
Arrow Down |
<LEFT> |
Arrow Left |
<RIGHT> |
Arrow Right |
<HOME> |
Home |
<END> |
End |
<PAGEUP> |
Page Up |
<PAGEDOWN> |
Page Down |
<DELETE> |
Delete |
<INSERT> |
Insert |
<F1> |
F1 |
<F2> |
F2 |
<F3> |
F3 |
<F4> |
F4 |
| Token | Key |
|---|---|
<C-c> |
Ctrl+C |
<C-d> |
Ctrl+D |
<C-z> |
Ctrl+Z |
<C-a> |
Ctrl+A |
<C-l> |
Ctrl+L |
<C-S-t> |
Ctrl+Shift+T |
Any single letter works with <C-x>. The <C-S-x> form includes Shift (though for letters, the control byte is the same).
| Sequence | Meaning |
|---|---|
\n |
Newline (LF) |
\r |
Carriage return |
\t |
Tab |
\< |
Literal < |
\\ |
Literal \ |
| Input string | Effect |
|---|---|
ls -la<CR> |
Type "ls -la" then press Enter |
<C-c> |
Send Ctrl+C (interrupt) |
hello<TAB> |
Type "hello" then press Tab |
<ESC>:wq<CR> |
Escape, type ":wq", press Enter |
<UP><UP><CR> |
Press Up twice, then Enter |
A typical session follows this pattern:
1. START -- launch the program
2. SNAPSHOT -- see what's on screen
3. INPUT -- send keystrokes
4. SNAPSHOT -- verify the result
5. ...repeat 3-4 as needed...
6. STOP -- terminate when done
Concrete example: running a Python REPL
# 1. Start a Python session
interactive_shell_start(command="python3", session="py-repl")
# 2. See the initial prompt
interactive_shell_snapshot(session="py-repl")
# -> lines: [{line: 1, text: "Python 3.x.x ..."}, {line: 3, text: ">>>"}]
# 3. Send a command
interactive_shell_input(session="py-repl", input="print('hello world')<CR>")
# 4. See the output
interactive_shell_snapshot(session="py-repl")
# -> lines include "hello world" and a new ">>>" prompt
# 5. Exit cleanly
interactive_shell_input(session="py-repl", input="exit()<CR>")
# 6. Stop the session
interactive_shell_stop(session="py-repl")
- Wait before snapshot: After sending input, give the program a moment to process. Complex commands may need a brief pause before snapshotting.
- Use named sessions: Pass a stable
sessionID (e.g.,"vim-editor") instead of relying on auto-generated UUIDs. This makes multi-step workflows easier to follow. - Check
alive: The snapshot response includesalive: bool. If the process has exited, stop the session to clean up. - Use
<CR>not\n: To press Enter, use<CR>or<ENTER>. Using\nsends a literal newline character, which may behave differently in some programs. - Interrupt stuck programs: Send
<C-c>to interrupt, or<C-d>to send EOF. - Set appropriate dimensions: Use
rowsandcolsto match what the program expects. Vim and htop render differently at different sizes. - One thing at a time: Send a command, snapshot to verify, then send the next command. Do not blindly chain many inputs without checking results.