vps-mcp
Provides tools for managing Docker containers, including docker ps, docker compose, docker exec, and deploying applications on remote servers.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vps-mcplist docker containers on production"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
vps-mcp
An MCP (Model Context Protocol) server for managing VPS servers via SSH. Connect it to Claude Code, Claude Desktop, or Claude.ai to execute commands, transfer files, manage Docker containers, and maintain per-server documentation — all from within Claude.
Features
SSH execution — run commands and shell scripts on remote servers
File transfer — SFTP plus three remote-friendly channels: inline content tools, streaming HTTP endpoints, and URL fetch-to-server
Docker management —
docker ps,docker compose,docker exec, deploy appsServer docs — scan a server and generate/update Markdown documentation per server
Encrypted vault — server credentials stored with AES-256-GCM + PBKDF2
Related MCP server: mcpHydroSSH
How it works
Claude Code / Claude Desktop / Claude.ai
│
│ MCP protocol
▼
vps-mcp (this server)
│
│ SSH / SFTP
▼
Your VPS serversvps-mcp stores SSH keys and passwords in an encrypted vault (AES-256-GCM). On every tool call it opens an SSH connection to the target server, executes the action, and returns the result.
Two transport modes are supported:
stdio — for local use (Claude Code, Claude Desktop on the same machine)
HTTP (Streamable HTTP) — for remote access (other devices, Claude.ai web)
Installation
Requirements: Node.js 20+
git clone https://github.com/ereskovsky/vps-mcp
cd vps-mcp
npm install
npm run buildCreate .env from the template:
cp .env.example .envEdit .env:
# Master password for the encrypted vault. Pick any strong passphrase.
VAULT_PASSWORD=your-strong-passphrase
# Bearer token for HTTP mode (generate with: openssl rand -hex 32)
API_KEY=your-random-api-key
# HTTP port (default: 3001)
# PORT=3001
# For HTTP mode with OAuth (required for Claude.ai web)
BASE_URL=https://vps-mcp.yourdomain.com # no port — required for OAuth metadata to work
CLIENT_ID=vps-mcp # any string, needed when adding the integration in Claude.ai
CLIENT_SECRET= # defaults to API_KEY if not setLocal usage (stdio)
This mode works without any server deployment — vps-mcp runs on your machine and connects to VPS servers over SSH.
Claude Code
claude mcp add vps-mcp -s user -e VAULT_PASSWORD="your-password" -- node "/path/to/vps-mcp/dist/index.js" --stdioClaude Desktop
Add to claude_desktop_config.json (~/Library/Application Support/Claude/ on Mac, %APPDATA%\Claude\ on Windows):
{
"mcpServers": {
"vps-mcp": {
"command": "node",
"args": ["/absolute/path/to/vps-mcp/dist/index.js", "--stdio"],
"env": {
"VAULT_PASSWORD": "your-password"
}
}
}
}Deploy to a VPS (HTTP mode)
Required for remote access from other devices or Claude.ai web.
1. Prepare
Clone the repo on your VPS. Edit Caddyfile and replace the domain:
vps-mcp.yourdomain.com {
reverse_proxy vps-mcp:3001
}Make sure your domain's DNS A record points to the VPS IP.
2. Start
cp .env.example .env
# Fill in VAULT_PASSWORD and API_KEY
docker compose up -dCaddy will automatically obtain a TLS certificate via Let's Encrypt.
3. Connect clients
Claude Code / Claude Desktop (remote):
claude mcp add vps-mcp -s user --transport http "https://vps-mcp.yourdomain.com/mcp" \
--header "Authorization: Bearer your-api-key"Or manually in config:
{
"mcpServers": {
"vps-mcp": {
"url": "https://vps-mcp.yourdomain.com/mcp",
"headers": {
"Authorization": "Bearer your-api-key"
}
}
}
}Connecting to Claude.ai (web, OAuth)
Claude.ai uses OAuth 2.0 (authorization_code + PKCE) to connect to remote MCP servers. vps-mcp implements the full OAuth flow.
Requirements
Server deployed and reachable over HTTPS
BASE_URLin.envmust be the public domain without a non-standard port (e.g.https://vps-mcp.yourdomain.com, nothttps://...:4443) — otherwise Claude.ai cannot reach thetoken_endpointfrom the OAuth metadata
Steps
Open Claude.ai → Settings → Integrations → Add integration
Enter URL:
https://vps-mcp.yourdomain.com/mcpEnter Client ID (value of
CLIENT_IDfrom.env)Enter Client Secret (value of
CLIENT_SECRETfrom.env, defaults toAPI_KEY)Claude.ai will open an authorization page — confirm access
OAuth endpoints
Endpoint | Description |
| OAuth metadata (discovery) |
| Authorization page (browser redirect) |
| Exchange code for token |
Adding a server to the vault
Via SSH key (recommended)
Encode your private key in base64 first:
# Linux / Mac
base64 -w 0 ~/.ssh/id_ed25519
# Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("$env:USERPROFILE\.ssh\id_ed25519"))Then call the add_server tool:
name: prod-1
host: 1.2.3.4
port: 22
username: root
authType: key
privateKey: <base64 string from above>
passphrase: <key passphrase, if any>
description: Main production serverVia password
name: staging
host: 5.6.7.8
port: 22
username: ubuntu
authType: password
password: your-ssh-password
description: Staging environmentTool discovery model (progressive discovery)
When a client connects, tools/list returns only 4 meta-tools, not the 18 real ones. The model uses them to find and call the actual tools on demand, which keeps the LLM's "cold" context cost down to roughly 1K tokens instead of 5–10K.
Meta-tool | What it does |
| Lists the 5 categories (registry, ssh, files, deploy, docs) with tool counts and descriptions. |
| Searches tools by |
| Returns full descriptions and JSON Schema input definitions for 1–10 tools by name. |
| Executes a real tool: |
Typical LLM workflow:
search_tools({query: "docker"}) → ["docker_ps", "docker_compose", "docker_exec", ...]
get_tool_schemas({names: ["docker_ps"]}) → full schema for docker_ps
invoke_tool({name: "docker_ps", arguments: {server: "prod"}}) → resultThe real tools below are documented for human reference — the model discovers them through search_tools / get_tool_schemas.
Tools
Registry — server management
list_servers
Returns all registered servers. Passwords and keys are never exposed.
add_server
Parameter | Type | Required | Description |
| string | yes | Unique identifier (letters, digits, |
| string | yes | IP address or hostname |
| number | no | SSH port (default: 22) |
| string | yes | SSH user |
|
| yes | Authentication type |
| string | if | Base64-encoded PEM private key |
| string | no | Key passphrase |
| string | if | SSH password |
| string | no | Server description |
remove_server
Parameter | Type | Description |
| string | Server name to remove |
SSH — command execution
execute_command
Runs a single command on the server.
Parameter | Type | Description |
| string | Server name from vault |
| string | Shell command |
→ execute_command(server="prod-1", command="df -h")
← { stdout: "...", stderr: "", exitCode: 0 }execute_script
Runs a multi-line bash script.
Parameter | Type | Description |
| string | Server name |
| string | Bash script (multi-line) |
Files — SFTP transfer
Important — local vs. remote mode
upload_file/download_fileuselocalPathon the MCP server's own filesystem. In stdio mode that is your machine (works as expected). In HTTP/remote mode (Claude.ai web) the MCP server runs on a VPS —localPaththen refers to that VPS, not your machine. For transfers from your machine in remote mode, use the inline content tools below or the HTTP file endpoints.
upload_file
Upload a file from the MCP server's local filesystem to a target VPS via SFTP.
Parameter | Type | Description |
| string | Server name |
| string | Absolute path on the MCP server's filesystem |
| string | Absolute destination path on the target VPS |
download_file
Download a file from a target VPS to the MCP server's local filesystem via SFTP.
Parameter | Type | Description |
| string | Server name |
| string | Path on the target VPS |
| string | Path on the MCP server's filesystem |
upload_file_content
Write inline content to a remote file — no MCP-server-side file needed. Use this in remote mode for configs, scripts, and small code files the model generates in conversation. Soft ceiling ≈ 5 MB; for larger files use the POST /files/upload HTTP endpoint.
Parameter | Type | Description |
| string | Server name |
| string | Absolute destination path on the target VPS |
| string | File content (text or base64) |
|
| Default |
download_file_content
Read a remote file and return its content inline. Rejects files larger than maxBytes to protect the conversation context.
Parameter | Type | Description |
| string | Server name |
| string | Path on the target VPS |
|
| Default |
| number | Max bytes to read. Default 1 MB, hard cap 5 MB |
fetch_url_to_server
Download a file from a public URL directly onto the VPS via curl. Bytes don't pass through the MCP host or Claude — handy for release artifacts, transfer.sh links, raw gists, S3 presigned URLs.
Parameter | Type | Description |
| string | Server name |
| string | http/https source URL |
| string | Absolute destination on the VPS |
| number |
|
list_remote_files
Parameter | Type | Description |
| string | Server name |
| string | Directory path on server |
← [{ name, type, size, modifiedAt, permissions }]HTTP file endpoints (large files, streamed)
The HTTP server exposes two endpoints alongside /mcp for transferring files of any size. They stream directly between the user and the target VPS via SFTP, so they bypass the inline-content size limits of MCP tool calls. Use the same Bearer token as /mcp.
POST /files/upload?server=<name>&path=<absolute-remote-path>
Body: raw bytes (Content-Type: application/octet-stream). Response: { ok: true, server, path, bytes }.
curl -H "Authorization: Bearer $API_KEY" \
--data-binary "@./local-archive.tar.gz" \
"https://vps-mcp.yourdomain.com/files/upload?server=prod-1&path=/tmp/local-archive.tar.gz"PowerShell:
curl -H "Authorization: Bearer $env:API_KEY" `
--data-binary "@./local-archive.tar.gz" `
"https://vps-mcp.yourdomain.com/files/upload?server=prod-1&path=/tmp/local-archive.tar.gz"The destination directory must already exist (the endpoint does not auto-mkdir). After upload, ask Claude to unpack/move/install via execute_command.
GET /files/download?server=<name>&path=<absolute-remote-path>
Streams the remote file back as the response body with Content-Disposition: attachment.
curl -H "Authorization: Bearer $API_KEY" \
"https://vps-mcp.yourdomain.com/files/download?server=prod-1&path=/var/log/app.log" \
-o app.logDocker & Deploy
docker_ps
Parameter | Type | Description |
| string | Server name |
| boolean | Include stopped containers (default: true) |
docker_compose
Parameter | Type | Description |
| string | Server name |
| string | Absolute path to directory with |
| enum |
|
| string | (optional) Specific service name |
| string | (optional) Extra flags |
docker_exec
Parameter | Type | Description |
| string | Server name |
| string | Container name or ID |
| string | Command to execute |
deploy_app
Deploys an app: git pull → build → restart.
Parameter | Type | Description |
| string | Server name |
| string | App directory path |
| string | Git branch (default: |
| string | (optional) Build command |
| string | (optional) Restart command |
Docs — server documentation
scan_server
Scans the server and returns a full snapshot:
OS and kernel version
CPU and RAM
Disk usage
Uptime
Running Docker containers and images
Active systemd services
Open ports (ss / netstat)
Cron jobs
get_server_docs
Reads stored Markdown documentation from data/docs/{server}.md.
update_server_docs
Writes or replaces the Markdown documentation for a server.
Parameter | Type | Description |
| string | Server name |
| string | Full Markdown content |
Common workflows
Onboarding a new server
1. add_server — register the server
2. execute_command — verify connection: "uptime"
3. scan_server — collect a state snapshot
4. update_server_docs — save documentationDeploying an app
1. deploy_app — git pull + build + restart
2. docker_ps — check container status
3. docker_compose — check logs: action="logs"Debugging a server issue
1. execute_command — "journalctl -u nginx --since '10 minutes ago'"
2. docker_compose — action="logs", flags="--tail=200"
3. execute_command — "df -h && free -h"Security
Vault (
data/servers.enc.json) is encrypted with AES-256-GCM; the key is derived via PBKDF2 (100,000 iterations) fromVAULT_PASSWORDSSH keys and passwords are never stored or transmitted in plaintext — only via the encrypted vault
The vault file and
data/directory are in.gitignore— never committed to the repoHTTP mode is protected by a Bearer token (
API_KEY) on every requestWhen connecting via Claude.ai, OAuth 2.0 (
authorization_code+ PKCE) is used — no direct API key exposure to the clientHTTPS is handled by Caddy (automatic TLS via Let's Encrypt)
list_serversand logs never expose keys or passwords
RAM note: If your VPS has limited RAM (< 512 MB), avoid running
npm installon the server — it may trigger the OOM killer. Install dependencies locally and uploadnode_modules/.
Project structure
src/
index.ts # Entry point — picks stdio vs HTTP mode
server.ts # createServer() — registers ONLY the 4 meta-tools
types.ts # Shared types: ServerRecord, VaultData, CommandResult
lib/
credential-store.ts # Encrypted vault (AES-256-GCM + PBKDF2)
ssh-client.ts # SSH wrapper: execCommand, execScript, SFTP upload/download/list
doc-manager.ts # Per-server Markdown docs (data/docs/{name}.md)
tools/
registry-core.ts # Central registry, search, Zod→JSON Schema util, ToolDef type
meta.ts # The 4 meta-tools (list_tool_categories, search_tools, get_tool_schemas, invoke_tool)
registry.ts # getRegistryTools() — list_servers, add_server, remove_server
ssh.ts # getSshTools() — execute_command, execute_script
files.ts # getFileTools() — upload_file, download_file, upload_file_content, download_file_content, fetch_url_to_server, list_remote_files
deploy.ts # getDeployTools() — docker_ps, docker_compose, docker_exec, deploy_app
docs.ts # getDocsTools() — scan_server, get_server_docs, update_server_docsLicense
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ereskovsky/vps-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server