Skip to main content
Glama
dblmca
by dblmca
README.md
# PTY MCP Server

An MCP (Model Context Protocol) server that provides interactive PTY (pseudo-terminal) sessions for AI assistants like Claude Code. This enables interaction with programs that require full terminal emulation such as vim, ssh, less, top, interactive REPLs, and more.

## LLM Deployment Instructions

This section provides complete step-by-step instructions for an LLM to deploy this MCP server autonomously.

### Prerequisites

Before starting, ensure the system has:
- Node.js >= 18.0.0 (`node --version` to check)
- npm (comes with Node.js)
- Build tools for native modules

To check/install prerequisites:

```bash
# Check Node.js version
node --version  # Should be >= 18.0.0

# If Node.js is missing or outdated, install via nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20

# Install build tools (required for node-pty native module)
# Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y python3 make g++

# macOS:
xcode-select --install

# Fedora/RHEL:
sudo dnf install -y python3 make gcc-c++
```

### Step 1: Clone the Repository

```bash
# Create MCP servers directory if it doesn't exist
mkdir -p ~/mcp-servers
cd ~/mcp-servers

# Clone the repository
git clone https://github.com/dblmca/pty-mcp.git
cd pty-mcp
```

### Step 2: Install Dependencies

```bash
npm install
```

This installs:
- `@modelcontextprotocol/sdk` - The MCP SDK for server implementation
- `node-pty` - Native PTY bindings (requires build tools)

**Troubleshooting `node-pty` build failures:**
```bash
# If npm install fails with node-pty errors, install build dependencies first
sudo apt-get install -y python3 make g++  # Ubuntu/Debian
# Then retry
npm install
```

### Step 3: Register with Claude Code

**CRITICAL**: Claude Code does NOT use `~/.claude/.mcp.json` directly. You MUST use the CLI:

```bash
# Add to user config (available in all projects)
claude mcp add -s user pty-mcp -- node ~/mcp-servers/pty-mcp/server.js
```

If using a different installation path, adjust accordingly:
```bash
claude mcp add -s user pty-mcp -- node /full/path/to/pty-mcp/server.js
```

### Step 4: Verify Installation

```bash
# List registered MCP servers
claude mcp list

# Should show pty-mcp with status
```

### Step 5: Test the Server

After restarting Claude Code, test with:
```
1. Call pty_spawn with no arguments to create a shell session
2. Call pty_read to see the shell prompt
3. Call pty_write with input: "echo hello\r"
4. Call pty_read to see the output
5. Call pty_kill to clean up
```

---

## Tools Reference

### `pty_spawn`

Create a new interactive PTY session.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `command` | string | No | User's shell | Command to run |
| `args` | string[] | No | [] | Command arguments |
| `cwd` | string | No | Current dir | Working directory |
| `cols` | number | No | 120 | Terminal columns (max: 500) |
| `rows` | number | No | 30 | Terminal rows (max: 200) |
| `env` | object | No | {} | Additional environment variables |

**Returns:**
```json
{
  "session_id": "abc123def456...",
  "pid": 12345,
  "command": "/bin/bash",
  "cols": 120,
  "rows": 30
}
```

### `pty_write`

Send input to a PTY session. Supports escape sequences.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `session_id` | string | Yes | Session ID from `pty_spawn` |
| `input` | string | Yes | Input to send |

**Escape Sequences:**

| Sequence | Key | Use Case |
|----------|-----|----------|
| `\r` | Enter | Execute commands |
| `\n` | Newline | Multi-line input |
| `\t` | Tab | Autocomplete |
| `\x03` | Ctrl-C | Interrupt/cancel |
| `\x04` | Ctrl-D | EOF/exit |
| `\x1b` | Escape | Exit modes (vim, less) |
| `\x1b[A` | Arrow Up | History/navigation |
| `\x1b[B` | Arrow Down | History/navigation |
| `\x1b[C` | Arrow Right | Cursor movement |
| `\x1b[D` | Arrow Left | Cursor movement |

**Returns:**
```json
{ "written": 5 }
```

### `pty_read`

Read buffered output from a PTY session.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `session_id` | string | Yes | - | Session ID |
| `timeout_ms` | number | No | 100 | Wait time for output (max: 5000) |
| `clear_buffer` | boolean | No | true | Clear buffer after reading |

**Returns:**
```json
{
  "output": "user@host:~$ ",
  "exited": false,
  "exit_code": null,
  "signal": null
}
```

### `pty_resize`

Resize a PTY terminal for full-screen applications.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `session_id` | string | Yes | Session ID |
| `cols` | number | Yes | New columns (max: 500) |
| `rows` | number | Yes | New rows (max: 200) |

### `pty_kill`

Terminate a PTY session and clean up resources.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `session_id` | string | Yes | - | Session ID |
| `signal` | string | No | SIGHUP | Signal: SIGHUP, SIGTERM, SIGKILL, SIGINT |

### `pty_list`

List all active PTY sessions. No parameters required.

---

## Usage Examples

### SSH Session

```
1. pty_spawn { command: "ssh", args: ["user@192.168.1.100"] }
2. pty_read { session_id: "...", timeout_ms: 5000 }  # Wait for prompt/password
3. pty_write { session_id: "...", input: "password\r" }  # If password needed
4. pty_read { session_id: "..." }  # Get remote prompt
5. pty_write { session_id: "...", input: "hostname\r" }  # Run commands
6. pty_read { session_id: "..." }
7. pty_write { session_id: "...", input: "exit\r" }  # Disconnect
8. pty_kill { session_id: "..." }  # Cleanup
```

### Python REPL

```
1. pty_spawn { command: "python3" }
2. pty_read { session_id: "..." }  # ">>> "
3. pty_write { session_id: "...", input: "import math\r" }
4. pty_write { session_id: "...", input: "print(math.pi)\r" }
5. pty_read { session_id: "..." }  # "3.141592653589793\n>>> "
6. pty_write { session_id: "...", input: "\x04" }  # Ctrl-D to exit
```

### Vim Editing

```
1. pty_spawn { command: "vim", args: ["file.txt"] }
2. pty_read { session_id: "...", timeout_ms: 1000 }  # Wait for vim
3. pty_write { session_id: "...", input: "i" }  # Insert mode
4. pty_write { session_id: "...", input: "Hello, World!" }
5. pty_write { session_id: "...", input: "\x1b" }  # Escape
6. pty_write { session_id: "...", input: ":wq\r" }  # Save and quit
7. pty_kill { session_id: "..." }
```

### Interactive Program (top, htop)

```
1. pty_spawn { command: "top" }
2. pty_read { session_id: "...", timeout_ms: 2000 }  # Get display
3. pty_write { session_id: "...", input: "q" }  # Quit top
   # Or: pty_write { session_id: "...", input: "\x03" }  # Ctrl-C
```

---

## Configuration

| Setting | Value | Description |
|---------|-------|-------------|
| Max sessions | 10 | Maximum concurrent PTY sessions |
| Session timeout | 30 minutes | Idle sessions auto-cleanup |
| Buffer size | 1 MB | Max output buffer per session |
| Input size | 1 MB | Max input per write |
| Default terminal | 120x30 | Default cols x rows |
| Max terminal | 500x200 | Maximum cols x rows |

---

## Troubleshooting

### Server not found after registration

```bash
# Verify registration
claude mcp list

# If missing, re-add
claude mcp add -s user pty-mcp -- node ~/mcp-servers/pty-mcp/server.js

# Restart Claude Code
```

### node-pty build fails

```bash
# Install build dependencies
sudo apt-get install -y python3 make g++

# Clear npm cache and retry
rm -rf node_modules package-lock.json
npm install
```

### Session commands not working

1. Ensure you're using the correct `session_id` from `pty_spawn`
2. Check if session exited: `pty_list` or check `exited` field in `pty_read`
3. For password prompts, use longer `timeout_ms` (e.g., 5000)

### Output contains escape codes

Terminal output includes ANSI escape sequences for colors and formatting. This is normal behavior for terminal applications.

---

## Security

- Commands restricted to standard system paths
- Shell metacharacters rejected in command names
- Environment variable names validated
- Input/buffer sizes limited
- Sessions auto-expire after 30 minutes

---

## Claude Code Integration (CLAUDE.md snippet)

Add this to your project's `CLAUDE.md` file to help Claude Code understand how to use this MCP server:

```markdown
### PTY MCP Server (Interactive Terminal)

The `pty-mcp` server provides interactive terminal (PTY) sessions for programs that require full terminal emulation (vim, ssh, less, top, interactive REPLs, etc.).

**Location:** `~/mcp-servers/pty-mcp/`

**When to Use:**
- Interactive programs that need terminal emulation (vim, nano, less, top, htop)
- SSH sessions
- Interactive REPLs (python, node, irb)
- Programs that require Ctrl-C, Ctrl-D, or arrow keys
- Any program that doesn't work well with the standard Bash tool

**MCP Tools:**
- `pty_spawn` - Create a new PTY session (returns session_id)
- `pty_write` - Send input to a session (supports escape sequences)
- `pty_read` - Read buffered output from a session
- `pty_resize` - Resize terminal dimensions
- `pty_kill` - Kill a session
- `pty_list` - List all active sessions

**Escape Sequences for `pty_write`:**
| Sequence | Meaning |
|----------|---------|
| `\r` | Enter/Return |
| `\x03` | Ctrl-C (interrupt) |
| `\x04` | Ctrl-D (EOF) |
| `\x1b` | Escape |
| `\x1b[A` | Arrow Up |
| `\x1b[B` | Arrow Down |
| `\x1b[C` | Arrow Right |
| `\x1b[D` | Arrow Left |

**Usage Examples:**

\`\`\`
# Basic shell session
1. pty_spawn {}                              → { session_id: "abc123" }
2. pty_read { session_id: "abc123" }         → { output: "$ " }
3. pty_write { session_id: "abc123", input: "ls -la\r" }
4. pty_read { session_id: "abc123" }         → { output: "..." }
5. pty_kill { session_id: "abc123" }

# Python REPL
1. pty_spawn { command: "python3" }
2. pty_write { session_id: "...", input: "x = 42\r" }
3. pty_write { session_id: "...", input: "print(x * 2)\r" }
4. pty_read { session_id: "..." }            → { output: "84\n>>> " }
5. pty_write { session_id: "...", input: "\x04" }  # Ctrl-D to exit

# Vim editing
1. pty_spawn { command: "vim", args: ["file.txt"] }
2. pty_write { session_id: "...", input: "i" }       # Insert mode
3. pty_write { session_id: "...", input: "Hello!" }
4. pty_write { session_id: "...", input: "\x1b" }    # Escape
5. pty_write { session_id: "...", input: ":wq\r" }   # Save and quit
\`\`\`

**Configuration:**
- Max 10 concurrent sessions
- 30-minute idle timeout (auto-cleanup)
- 1MB output buffer per session
- Default terminal: 120x30
```

---

## License

MIT