-
-
Notifications
You must be signed in to change notification settings - Fork 14
MCP Server
purple ships a built-in MCP server for SSH: AI agents can list your SSH hosts, run commands and manage containers through typed tools with input validation, a read-only mode and a JSON Lines audit log. No API keys needed, and every call your assistant makes is on the record.
MCP (Model Context Protocol) is a standard for connecting AI coding assistants to external tools. purple implements an MCP server so AI agents like Claude Code, Cursor, Windsurf and Claude Desktop can query your SSH hosts, run commands and manage containers programmatically.
purple mcpThe AI client spawns purple mcp automatically as a child process. You do not need to start it manually. Just add the config below and restart your AI client.
With a custom SSH config:
purple --config ~/other/ssh_config mcp| Flag | Effect |
|---|---|
--read-only |
Restricts the server to list_hosts, get_host and list_containers. State-changing tools (run_command, container_action) are denied and removed from tools/list. Recommended when exposing purple to autonomous agents. |
--no-audit |
Disables the audit log. By default every tool call is appended to ~/.purple/mcp-audit.log as a JSON line. |
--audit-log <PATH> |
Custom audit log path. Default: ~/.purple/mcp-audit.log. Ignored when --no-audit is set. |
Example: read-only mode for an autonomous agent, audit log to a custom path:
purple mcp --read-only --audit-log /var/log/purple-mcp.log| Tool | Description | Read-only |
|---|---|---|
list_hosts |
List all SSH hosts. Optional tag filter (substring match). | yes |
get_host |
Detailed info for one host: provider, tags, metadata, tunnels, askpass. | yes |
list_containers |
List Docker/Podman containers on a remote host. Returns runtime, engine version (when reported by the daemon) and the parsed container list. | yes |
run_command |
Run a command on a remote host via SSH. Timeout clamped to 1-300 seconds. | no |
container_action |
Start, stop or restart a container. | no |
The simplest path. Download the latest .mcpb (MCP Bundle) from the GitHub releases page and double-click to install. Claude Desktop handles the rest.
The bundled installation runs purple in --read-only mode by default for safety. If you need run_command or container_action from Claude Desktop, install via Homebrew or cargo and configure claude_desktop_config.json directly (see below).
Add to ~/.claude/settings.json:
{
"mcpServers": {
"purple": {
"command": "purple",
"args": ["mcp"]
}
}
}Restart Claude Code. The purple tools appear automatically.
Add to your MCP configuration (Settings > MCP Servers):
{
"purple": {
"command": "purple",
"args": ["mcp"]
}
}Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"purple": {
"command": "purple",
"args": ["mcp"]
}
}
}For read-only mode, add the flag:
{
"mcpServers": {
"purple": {
"command": "purple",
"args": ["mcp", "--read-only"]
}
}
}If you use a non-default SSH config, pass --config before mcp:
{
"mcpServers": {
"purple": {
"command": "purple",
"args": ["--config", "/path/to/ssh_config", "mcp"]
}
}
}Any MCP-compatible client that supports stdio transport can use purple. Point it to the purple mcp command.
Every tool call is appended to ~/.purple/mcp-audit.log as a single JSON line:
{"ts":"2026-05-19T09:32:11Z","tool":"list_hosts","args":{"tag":"prod"},"outcome":"allowed","reason":null}
{"ts":"2026-05-19T09:32:18Z","tool":"run_command","args":{"alias":"web-1","command":"<redacted>"},"outcome":"allowed","reason":null}
{"ts":"2026-05-19T09:35:02Z","tool":"container_action","args":{"alias":"web-1","container_id":"abc","action":"start"},"outcome":"denied","reason":"read-only mode"}Fields:
-
ts: ISO 8601 UTC timestamp, second precision -
tool: the tool name that was called -
args: the call arguments. Thecommandfield ofrun_commandis replaced by<redacted>so secrets passed as shell arguments do not land in the log -
outcome:allowed,denied(read-only blocked the call) orerror(the tool returned an error result) -
reason: present and non-null only whenoutcomeisdenied
The log file is created with mode 0o600 on Unix (owner read/write only). Append-only: writes never truncate prior entries. The MCP server holds a single file handle protected by a mutex, so concurrent writers cannot interleave lines.
To disable: purple mcp --no-audit. To redirect: purple mcp --audit-log /path/to/log.
If --audit-log points at a path that already exists as a symlink, purple refuses to open it (defense against an attacker pre-creating a symlink to a sensitive file).
The MCP server validates every alias against the SSH config before executing SSH commands. Only hosts in your config can be targeted. Container IDs are validated with an ASCII alphanumeric allowlist to prevent injection. All SSH operations use BatchMode=yes (no interactive prompts) and timeouts (clamped to 1-300 seconds for run_command).
The --read-only flag is the recommended posture when exposing purple to autonomous agents. It enforces the allowlist at dispatch time, before any argument validation, so a probing agent cannot distinguish "tool denied" from "tool would have worked but args were bad".
Purple does not implement its own approval gate. Approval behavior depends on your AI client. Claude Code prompts for approval on tool calls by default. Verify your client's settings before enabling run_command in production.
What the AI client sends and receives:
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude","version":"1.0"}}}
← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"purple","version":"3.22.1"}}}
→ {"jsonrpc":"2.0","method":"notifications/initialized"}
(no response)
→ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_hosts","arguments":{"tag":"prod"}}}
← {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"[{\"alias\":\"web-1\",\"hostname\":\"10.0.1.5\", ...}]"}]}}
→ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run_command","arguments":{"alias":"web-1","command":"uptime"}}}
← {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\"exit_code\":0,\"stdout\":\" 14:32 up 42 days\",\"stderr\":\"\"}"}]}}
In --read-only mode the same run_command call gets:
← {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Tool denied. Server started with --read-only. Restart without --read-only to enable state-changing tools."}],"isError":true}}
MCP clients spawn purple as a child process with a minimal PATH (typically /usr/local/bin, /opt/homebrew/bin, /usr/bin, /bin). If you installed purple via curl the binary lives in ~/.local/bin which is not in that PATH.
Fix: use the full path in your MCP config:
{
"mcpServers": {
"purple": {
"command": "/Users/you/.local/bin/purple",
"args": ["mcp"]
}
}
}Find your path with which purple.
Homebrew installs (/opt/homebrew/bin/purple) are usually found automatically.
The .mcpb bundle for Claude Desktop ships the purple binary inside the bundle, so this issue does not apply there.
Check ~/.purple/mcp-audit.log exists and is writable. If purple cannot open the log it prints a one-line warning to stderr at startup and continues without audit logging (so a broken log path never blocks the server).
If the path is a symlink, purple refuses to open it and the same warning is printed. Remove the symlink or point --audit-log at a regular file.
If you see Invalid action: nuke. Must be start, stop or restart even though you started with --read-only, the server is NOT in read-only mode (the read-only guard fires before argument validation, so a denied call returns the read-only message, never the validation error). Check that your client config actually passes --read-only to purple mcp.
- Stdio transport only. No HTTP or SSE.
- Read and execute only. No host mutations (add, edit, delete) in this version.
- No provider sync, tunnel management or file transfer via MCP.
- One request at a time per server instance. The
Mutex<File>audit log is concurrency-safe but the JSON-RPC loop itself is single-threaded.
Getting started
Features
- Jump
- Cloud Providers
- File Explorer
- Command Snippets
- Password Management
- Vault SSH Certificates
- Container Management
- SSH Tunnels
- Keys
- Tags and Search
- Host Patterns
- Themes
- MCP Server
- Whats New
Reference