JoshuaWink/terminal-mcp-server
If you are the rightful owner of terminal-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
A small FastMCP server that exposes terminal (PTY) management APIs over MCP.
terminal-mcp-server
A small FastMCP server that exposes terminal (PTY) management APIs over MCP.
Summary of what's new in this hotfix
- Added support for creating terminals with a specific working directory (
cwd). terminal_createnow always returns structured JSON: {"terminalId":..., "cwd":...}.terminal_sendwill create a terminal when called withoutterminalIdand returns JSON including the created terminal'scwd.terminal_listnow includes each PTY'scwd.- Fixed the recommended virtualenv install and made examples clearer.
Features
- Create and manage local PTY-backed terminals from MCP clients.
- Non-blocking send/read of terminal output.
- Interrupt (Ctrl-C), clear, dispose terminals, and list active PTYs.
Available tools (summary)
terminal_create(name?, cwd?)-> creates a PTY and returns JSON {terminalId, cwd}terminal_send(terminalId?, text)-> sends text to PTY; ifterminalIdomitted a PTY is created and returned as JSONterminal_read(terminalId, {strip_ansi:true, lines:N})-> read buffered outputterminal_interrupt(terminalId)-> send Ctrl-Cterminal_clear(terminalId)-> clear bufferterminal_dispose(terminalId)-> terminate and clean upterminal_list()-> list known PTYs (includescwdnow)terminal_events({terminalId?, since_ts?, limit?, types?})-> retrieve recent structured events (create, cmd, stdout, clear, interrupt, dispose)
Quality-of-Life (Verbose / Meta Responses)
You can opt in to structured guidance in responses by including one of the flags
verbose: true, meta: true, or return_meta: true inside the payload argument
you pass to a tool. This leaves the default (minimal) responses unchanged for
existing clients, while agents that benefit from hints can request richer JSON.
Examples
Create (verbose):
{
"terminalId": "[mcp] bright-otter",
"cwd": "/Users/me",
"created": true,
"hint": "Use terminal_send to run commands, then terminal_read to fetch output.",
"next": ["terminal_send", "terminal_read", "terminal_list"]
}
Send (auto-created + verbose):
{
"terminalId": "[mcp] bright-otter",
"status": "created",
"cwd": "/Users/me",
"hint": "Use terminal_read to fetch output.",
"next": ["terminal_read", "terminal_clear", "terminal_interrupt"]
}
Send (existing terminal, verbose):
{
"terminalId": "[mcp] bright-otter",
"status": "sent",
"bytes": 15,
"hint": "Call terminal_read to view buffered output."
}
Read (verbose, non-empty):
{
"output": "HELLO_WORLD\n",
"lines": 1,
"terminalId": "[mcp] bright-otter",
"empty": false,
"hint": "Use lines parameter to tail recent output."
}
Read (verbose, empty buffer):
{
"output": "",
"lines": 0,
"terminalId": "[mcp] bright-otter",
"empty": true,
"hint": "Buffer empty. Use terminal_send to execute a command."
}
Error (verbose send without text):
{
"error": "text required",
"hint": "Provide shell input in the \"text\" parameter."
}
These additions are backwards compatible: omit the flags to retain the original concise string / plain output responses.
Event Stream & Introspection
The server maintains an in-memory circular buffer (size 2000) of recent events
also written to a newline-delimited JSON log at .terminal-mcp/events.log (can
disable with TERMINAL_MCP_EVENT_LOG_ENABLED=0).
Event Types:
create– terminal createdcmd– a command was written (before output appears)stdout– chunk of terminal output capturedclear– buffer cleared via toolinterrupt– Ctrl-C sentdispose– terminal disposed (now includesexitCodewhen available)
Use the terminal_events tool for polling:
// Example payload
{
"terminalId": "[mcp] bright-otter",
"since_ts": "2025-09-28T12:34:56.789Z",
"limit": 100,
"types": ["cmd", "stdout"]
}
Response:
{
"events": [
{"terminalId": "[mcp] bright-otter", "type": "cmd", "text": "echo hi", "ts": "..."},
{"terminalId": "[mcp] bright-otter", "type": "stdout", "text": "hi\n", "ts": "..."}
],
"count": 2
}
Dispose now returns exit code when verbose:
{
"terminalId": "[mcp] bright-otter",
"disposed": true,
"exitCode": 0
}
Quickstart
- Create a virtualenv and install dependencies:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
- Run the server (from this folder):
python server.py
- Use a FastMCP client or the MCP Inspector to call the tools above.
Testing Run the test suite to validate functionality:
# Run smoke test (basic functionality)
python test_smoke.py
# Run comprehensive test suite
python -m pytest test_terminal.py -v
# Run all tests
python -m pytest -v
The test suite covers:
- Terminal creation and disposal
- Command execution and output reading
- Buffer management (clear, read options)
- Interrupt functionality
- Multiple terminal management
- Error handling
Usage examples
- Create a terminal in the repository directory and inspect the JSON return:
# payload example sent to the MCP server
payload = {"name": "repo-pty", "cwd": "/path/to/repo"}
# server returns: {"terminalId": "repo-pty", "cwd": "/path/to/repo"}
- Send without an explicit terminalId (server will create one and return JSON):
# calling terminal_send with only text
# returns: {"terminalId": "mcp-terminal-...,", "status": "created", "cwd": "/home/user"}
- Read output (strip ANSI sequences by default):
# terminal_read(terminalId, {"strip_ansi": True, "lines": 200})
Example mcp.json fragment
Use this snippet in your MCP client configuration so the inspector or client knows how to run this server locally via stdio:
{
"servers": {
"terminal-mcp": {
"command": "/path/to/your/venv/bin/python",
"args": ["/path/to/terminal-mcp-server/server.py"],
"type": "stdio",
"description": "MCP stdio server exposing terminal commands",
"dev": { "watch": "/path/to/terminal-mcp-server/app/**/*.py" }
}
}
}
## Adding This Server To `mcp.json` (Expanded Guide)
Below are several common configuration patterns you can paste into your client-side `mcp.json` (used by MCP Inspector, compatible agents, or orchestration layers). Adjust paths to match your system.
### 1. Local Clone + Virtualenv (Recommended for Development)
Clone the repository and create a virtual environment in the project root:
```bash
git clone https://github.com/your-org/terminal-mcp-server.git
cd terminal-mcp-server
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Then point `mcp.json` at the python executable and `server.py`:
```jsonc
{
"servers": {
"terminal-mcp": {
"command": "/absolute/path/to/terminal-mcp-server/.venv/bin/python",
"args": ["/absolute/path/to/terminal-mcp-server/server.py"],
"type": "stdio",
"description": "Local terminal management MCP server (dev virtualenv)",
"dev": { "watch": "/absolute/path/to/terminal-mcp-server/app/**/*.py" }
}
}
}
```
Tip: Run `pwd` inside the repo to copy the absolute path; then append `/.venv/bin/python` and `/server.py` accordingly.
### 2. Editable Install in a Shared Environment
If this repo is part of a monorepo or shared dev environment you may prefer an editable install:
```bash
pip install -e /absolute/path/to/terminal-mcp-server
```
Then you can invoke using module-style execution (optional). Update `mcp.json`:
```jsonc
{
"servers": {
"terminal-mcp": {
"command": "/path/to/python",
"args": ["/absolute/path/to/terminal-mcp-server/server.py"],
"type": "stdio",
"description": "Terminal MCP (editable install)"
}
}
}
```
### 3. Windows (PowerShell) Example
Paths differ and the virtualenv python lives under `Scripts`:
```jsonc
{
"servers": {
"terminal-mcp": {
"command": "C:/path/to/terminal-mcp-server/.venv/Scripts/python.exe",
"args": ["C:/path/to/terminal-mcp-server/server.py"],
"type": "stdio",
"description": "Terminal MCP on Windows"
}
}
}
```
### 4. Using `uv` (Fast Python Installer)
If you prefer `uv` for environment management (https://github.com/astral-sh/uv):
```bash
uv venv .venv
source .venv/bin/activate
uv pip install -r requirements.txt
```
Then the `mcp.json` is the same as (1) – just ensure you still reference the venv Python path.
### 5. Multiple Terminal Servers (Namespacing)
You can register more than one server (e.g. one for local machine and one for a remote wrapper) by adding multiple entries under `servers`:
```jsonc
{
"servers": {
"terminal-local": { "command": "/local/venv/bin/python", "args": ["/local/terminal-mcp/server.py"], "type": "stdio" },
"terminal-alt": { "command": "/alt/venv/bin/python", "args": ["/alt/terminal-mcp/server.py"], "type": "stdio" }
}
}
```
### 6. Environment Variables (Optional)
You can influence runtime behavior via environment variables in future expansions or by wrapping the command. For event log control today:
| Variable | Purpose | Default |
|----------|---------|---------|
| `TERMINAL_MCP_EVENT_LOG_ENABLED` | Enable/disable persistent event log | `1` |
| `TERMINAL_MCP_EVENT_DIR` | Custom directory for the event log | `.terminal-mcp/` under repo |
| `TERMINAL_MCP_EVENT_LOG` | Override full path to event log file | `<EVENT_DIR>/events.log` |
If your MCP host supports per-server environment injection you may extend the `servers.terminal-mcp` object with an `env` field (syntax depends on the host). Example (conceptual):
```jsonc
{
"servers": {
"terminal-mcp": {
"command": "/venv/bin/python",
"args": ["/repo/server.py"],
"type": "stdio",
"env": {
"TERMINAL_MCP_EVENT_LOG_ENABLED": "0"
}
}
}
}
```
### 7. Verifying Setup
After editing `mcp.json`, restart your MCP-enabled client / inspector and look for a server named "terminal-mcp" (or the name you chose). List tools; you should see:
```
terminal_create, terminal_send, runCommand, terminal_read, terminal_interrupt, terminal_clear, terminal_dispose, terminal_list, terminal_events
```
Run a quick sanity cycle:
1. Call `terminal_create` (optionally with `{ "payload": { "verbose": true } }`).
2. Call `terminal_send` with a simple command (e.g. `echo hello`).
3. Call `terminal_read` to confirm output buffering.
4. Poll `terminal_events` to observe `create`, `cmd`, and `stdout` entries.
5. Call `terminal_dispose` (verbose) to verify `exitCode` appears.
### 8. Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| No tools appear | Wrong path to python or server file | Verify absolute paths in `command` and `args` |
| Empty output after send | Command still running / buffering | Add a short sleep then `terminal_read`; or run simpler command |
| Missing events | Pagination cursor set too high | Remove `after` or lower it; check `oldestSeq/newestSeq` in response |
| Permission error launching | CWD or python not accessible | Use a directory you own; ensure executable bit |
If issues persist, run the server manually (`python server.py`) and observe stderr/stdout for import or runtime errors.
---
This section aims to make integration copy/paste friendly. If you use a different MCP host that requires a slightly different schema, adapt the key names but keep the `command`, `args`, and `type: stdio` trio intact.
Notes
cwdis honored when starting the shell where supported; if the platform does not acceptcwdfor the Popen call the server will still store the requestedcwdand return it (shell may start in the default directory).- Reads are non-destructive by default; call
terminal_clearto empty a buffer. - This server intentionally only exposes terminal-related tools so it can be included or deployed separately from a larger MCP server.
Keywords: terminal, pty, mcp, fastmcp, shell, remote-shell, terminal-mcp