Metadata-Version: 2.4
Name: lmstudio-terminal-mcp
Version: 0.1.0
Summary: MCP server giving LM Studio (and other MCP hosts) bash/PowerShell access with structured outputs, background processes, and opt-in unattended hardening.
Project-URL: Homepage, https://github.com/knowlesindustry/lmstudio-terminal-mcp
Project-URL: Issues, https://github.com/knowlesindustry/lmstudio-terminal-mcp/issues
Author: David Knowles
License: MIT
License-File: LICENSE
Keywords: bash,lm-studio,mcp,powershell,shell,terminal
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: mcp>=1.0.0
Requires-Dist: psutil>=5.9
Requires-Dist: pydantic-settings>=2.2
Requires-Dist: pydantic>=2.6
Requires-Dist: pywin32>=306; platform_system == 'Windows'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# lmstudio-terminal-mcp

MCP server that gives LM Studio (and any other MCP host) bash / PowerShell access on the local machine, with structured outputs, background-process support, and opt-in unattended hardening.

> **Note on name:** PyPI's `terminal-mcp` is owned by an unrelated project. This package is published as **`lmstudio-terminal-mcp`**.

## Install for LM Studio (chat UI)

### Option A — one-click deeplink

Generate a deeplink button and open it in any browser:

```bash
python scripts/gen_deeplink.py --name terminal
```

Click the resulting `lmstudio://add_mcp?...` link. LM Studio will prompt for confirmation.

### Option B — paste into `mcp.json`

1. LM Studio → Program tab → Install → Edit mcp.json
2. Add:

```json
{
  "mcpServers": {
    "terminal": {
      "command": "uvx",
      "args": ["lmstudio-terminal-mcp"],
      "env": {
        "TERMINAL_MCP_DEFAULT_CWD": "C:/Users/YOU/Documents",
        "TERMINAL_MCP_MAX_TIMEOUT_SEC": "600"
      }
    }
  }
}
```

3. Save. LM Studio hot-reloads.

### Prerequisites

- `uv` on PATH (`winget install astral-sh.uv` on Windows)
- Python 3.11+

## Tools

| Tool | Blast radius | Recommended |
|---|---|---|
| `run_command` | high — arbitrary code | manual approval |
| `start_background_process` | high | manual approval |
| `stop_process` | medium | manual approval |
| `read_process_output` | low | **always allow** |
| `list_processes` | low | **always allow** |

## Unattended / API use

**Read before enabling.** The cwd allowlist restricts the *initial* working directory only — it is not a filesystem sandbox. A child shell can still read or write any absolute path the server's OS user can access, open outbound network connections, and exfiltrate data. For real isolation layer a container, Windows Sandbox, firejail, or a separate VM around this server.

```json
{
  "mcpServers": {
    "terminal": {
      "command": "uvx",
      "args": ["lmstudio-terminal-mcp"],
      "env": {
        "TERMINAL_MCP_UNATTENDED": "1",
        "TERMINAL_MCP_CWD_ALLOWLIST": "C:/Users/YOU/projects",
        "TERMINAL_MCP_COMMAND_ALLOWLIST": "git\\s+(status|log|diff|branch)(\\s+[\\w\\-./=: ]*)?,npm\\s+(install|run\\s+\\w+|test)(\\s+[\\w\\-./=: ]*)?"
      }
    }
  }
}
```

Unattended mode will **refuse to start** without `TERMINAL_MCP_CWD_ALLOWLIST`, and without either `TERMINAL_MCP_COMMAND_ALLOWLIST` or `TERMINAL_MCP_UNATTENDED_ACK_NO_ALLOWLIST=1`.

## Configuration reference

| Env var | Default | Purpose |
|---|---|---|
| `TERMINAL_MCP_UNATTENDED` | `0` | `1` enables strict unattended gate |
| `TERMINAL_MCP_DEFAULT_CWD` | home dir | Default working directory |
| `TERMINAL_MCP_DEFAULT_SHELL` | `auto` | `auto`, `bash`, `powershell`, `cmd` |
| `TERMINAL_MCP_POWERSHELL_PATH` | auto | Explicit pwsh path |
| `TERMINAL_MCP_BASH_PATH` | auto | Explicit bash path (NOT `$SHELL`) |
| `TERMINAL_MCP_MAX_TIMEOUT_SEC` | 1800 (att) / 600 (unatt) | Hard cap |
| `TERMINAL_MCP_MAX_OUTPUT_BYTES` | 1 MB | Per-stream truncation |
| `TERMINAL_MCP_CWD_ALLOWLIST` | unset / required in unatt | Comma-sep abs paths, realpath-resolved ancestor check |
| `TERMINAL_MCP_COMMAND_ALLOWLIST` | unset / required in unatt | Comma-sep regex, `re.fullmatch` |
| `TERMINAL_MCP_COMMAND_DENYLIST` | unset / built-in in unatt | Comma-sep regex, `re.search` |
| `TERMINAL_MCP_UNATTENDED_ACK_NO_ALLOWLIST` | unset | Required to run unattended without command allowlist |
| `TERMINAL_MCP_UNATTENDED_DENY_SHELL_OPERATORS` | 0 (att) / 1 (unatt) | Block `&&`, `\|\|`, `;`, pipes, etc. |
| `TERMINAL_MCP_ALLOW_ELEVATION` | `0` | `1` permits sudo/runas in attended mode |
| `TERMINAL_MCP_MAX_BACKGROUND` | 4 (att) / 2 (unatt) | Concurrent bg process cap |
| `TERMINAL_MCP_PROCESS_BUFFER_BYTES` | 4 MB (att) / 1 MB (unatt) | Per-stream ring buffer |
| `TERMINAL_MCP_BACKGROUND_ENABLED` | `1` | Set to `0` to disable background tools |
| `TERMINAL_MCP_ENV_SCRUB` | unset / credential defaults in unatt | Comma-sep env vars to strip from children |
| `TERMINAL_MCP_LOG_LEVEL` | `INFO` | stderr log level |

## Development

```bash
git clone https://github.com/knowlesindustry/lmstudio-terminal-mcp
cd lmstudio-terminal-mcp
uv pip install -e ".[dev]"
pytest
```

Dev install into LM Studio:

```json
{
  "mcpServers": {
    "terminal": {
      "command": "python",
      "args": ["-m", "lmstudio_terminal_mcp"]
    }
  }
}
```

## License

MIT — see [LICENSE](LICENSE).
