mikrus-mcp Server

MCP (Model Context Protocol) server for managing VPS servers via the mikr.us API and remote Linux servers over SSH. Built in Python, runs anywhere — locally, in Docker, or as a Claude Desktop integration.

Documentation

Mikrus MCP Server

CI Docker Python 3.14+ License: MIT

MCP (Model Context Protocol) server for managing VPS servers via the mikr.us API and remote Linux servers over SSH. Built in Python, runs anywhere — locally, in Docker, or as a Claude Desktop integration.

All tools follow the MCP Server Standards for response format, testing, and documentation.

Contents

Requirements

  • Python 3.14+ (for local use) or Docker
  • A mikr.us account with an API key or any SSH-accessible Linux server
  • Your server identifier (e.g. your_srv) or SSH host

Quick Start

1. Configure environment

cp .env.example .env

Edit .env with your credentials. Three modes are supported:

A — Single-server (mikr.us API only):

MIKRUS_API_KEY=your_api_key_here
MIKRUS_SERVER_NAME=your_server_name_here

B — Multi-server JSON (mikr.us + SSH):

MCP_SERVERS={"your_srv": {"type": "mikrus", "key": "xxx", "srv": "your_srv"}, "myssh": {"type": "ssh", "host": "srvXX.mikr.us", "port": 22, "user": "root", "password": "secret"}}
MCP_DEFAULT_SERVER=your_srv

C — SSH-only (no mikr.us account needed):

MCP_SERVERS={"prod": {"type": "ssh", "host": "prod.example.com", "user": "admin", "ssh_key": "/home/user/.ssh/id_ed25519"}}

Variable name aliases (MCP_* and MIKRUS_* are interchangeable):

  • MCP_SERVERS ←→ MIKRUS_SERVERS
  • MCP_DEFAULT_SERVER ←→ MIKRUS_DEFAULT_SERVER

IMPORTANT: The .env file contains your API key / passwords. It is gitignored and must never be committed.

2. Run with Docker

Two approaches are available:

Pre-built image (recommended — no local build needed)

Pulls the published image from GitHub Container Registry:

docker run --rm \
  --env-file .env \
  ghcr.io/paulomac1000/mikrus-mcp:latest

Or pass credentials directly:

docker run --rm \
  -e MIKRUS_API_KEY=your_key \
  -e MIKRUS_SERVER_NAME=your_server \
  ghcr.io/paulomac1000/mikrus-mcp:latest

Local build (use when modifying the code or for development)

Build the image from source and run it:

# with docker compose (reads .env automatically)
docker compose up mikrus-mcp

# with docker compose and SSH key mount
docker compose run --rm \
  -v ~/.ssh/id_ed25519:/home/appuser/.ssh/id_ed25519:ro \
  mikrus-mcp

# or with plain docker build
docker build -t mikrus-mcp .
docker run --rm --env-file .env mikrus-mcp

The server communicates over stdio by default. Set MCP_PORT to enable SSE transport (see Security Considerations).

Using SSH keys in Docker: Mount your private key as a read-only volume:

docker run --rm \
  --env-file .env \
  -v ~/.ssh/id_ed25519:/home/appuser/.ssh/id_ed25519:ro \
  ghcr.io/paulomac1000/mikrus-mcp:latest

SSH keys must have permissions 600 or 400 and be readable by the appuser user inside the container (UID 1000). If your host user has a different UID, adjust ownership with chown 1000:1000 ~/.ssh/id_ed25519 or use a less restrictive mode. Certificates can be mounted the same way.

3. Run locally (Python 3.14+)

pip install -e ".[dev]"
mikrus-mcp

Available Tools

Discovery

ToolDescription
list_configured_serversList all configured servers, their types, and connection status. Use this first to discover available servers.
describe_mikrus_capabilitiesReturns the full tool catalog with capability manifests, supported transports, and schema version. Zero I/O, instant.

mikr.us API tools (mikrus servers only)

ToolAPI endpointDescription
get_server_info/infoServer info: ID, RAM, disk, expiration date, PRO plan status (cache=60s)
list_servers/serweryList all servers associated with the account (cache=60s)
get_server_stats/statsUsage stats: RAM, disk, uptime, load avg, processes (cache=60s)
execute_command/execExecute a shell command on the server (60s API limit)
restart_server/restartRestart the VPS server
get_logs/logsLast 10 task log entries from the panel
get_log_by_id/logs/IDDetails of a specific log entry by ID
boost_server/amfetaminaTemporary resource boost (+512MB RAM for 30 min, free)
get_db_info/dbDatabase access credentials (MySQL/PostgreSQL, cache=60s)
get_ports/portyAssigned TCP/UDP ports (cache=60s)
get_cloud/cloudCloud services and statistics assigned to the account
assign_domain/domainAssign a domain to a port (use - for auto-generated subdomain)

System management tools (mikrus + SSH servers)

ToolDescription
read_fileRead a text file from the server (up to 200 lines)
write_fileWrite text content to a file (base64-safe transfer)
manage_serviceManage systemd services: status, start, stop, restart, enable, disable
analyze_diskDisk usage overview (df -h + top-20 largest directories)
check_portCheck if a TCP port is listening and what process uses it
manage_processList top processes by memory or kill a process by PID/name
update_systemRun system updates (apt update && apt upgrade -y)
list_directoryList directory contents (ls -la)
tail_fileRead last N lines from a text file (max 500)
search_in_filesSearch for a pattern in files under a path (grep -r)
get_memory_infoShow memory usage (free -h)
get_network_infoShow network interfaces and listening TCP ports
get_process_treeShow running processes in a tree view (ps auxf)

Docker tools (mikrus + SSH servers, requires Docker access)

ToolDescription
list_docker_containersList all Docker containers with status and image
get_docker_logsFetch recent logs from a Docker container
get_docker_statsShow resource usage stats for Docker containers

Journal tools (mikrus + SSH servers, may require sudo_password)

ToolDescription
get_journal_logsFetch systemd journal logs for a specific service unit
find_system_errorsFind error-level journal entries from the last N hours
search_journal_logsSearch journal logs for a keyword or phrase

Note about journal tools: If the remote user is not in the systemd-journal or adm group, journal commands will fail. To fix this, add sudo_password to the SSH server configuration. If sudo_password is omitted, the tool will still work but returns a helpful hint for the user when privileges are insufficient.

All system tools accept an optional server parameter to target a specific configured server (e.g. server=myssh). If omitted, the default server is used.

Tool Response Format

All 33 tools return a consistent JSON structure. Every response includes a _meta envelope with a unique request_id:

{"success": true, "data": {"server_id": "emil359", "param_ram": "1024"}, "_meta": {"request_id": "a1b2c3d4-...", "duration_ms": 42, "cached": false, "retry_safe": false}}

On failure:

{"success": false, "error": "Server 'unknown' is not a mikrus server", "_meta": {"request_id": "e5f6g7h8-..."}}

The success field is always a boolean. Successful responses contain a data key with the tool-specific result. Error responses contain an error key with a human-readable message. No tool raises unhandled exceptions — errors are always returned as structured JSON.

Tools are annotated with risk-level prefixes in their descriptions:

PrefixMeaningExamples
[DANGEROUS]Executes arbitrary shell commandsexecute_command
[WRITE]Modifies server state or fileswrite_file, update_system
[DESTRUCTIVE]Kills processes, restarts servicesmanage_service (stop/restart), manage_process (kill)
[SENSITIVE]Returns credentials or tokensget_db_info, get_journal_logs
(none)Read-only, no side effectsget_server_info, list_servers, describe_mikrus_capabilities

AI agents use these prefixes to decide whether to request user confirmation before invoking a tool. Tools are additionally gated behind ENABLE_WRITE_OPERATIONS — a server-level authorization flag that must be explicitly enabled for any write, destructive, or command-execution tool to perform I/O.

Multi-server Configuration

You can manage multiple servers simultaneously by using the MCP_SERVERS JSON. You can mix mikrus and SSH servers, or use SSH servers exclusively (no mikr.us account required).

{
  "your_srv": {"type": "mikrus", "key": "xxx", "srv": "your_srv"},
  "myssh":   {"type": "ssh", "host": "192.168.1.10", "port": 22, "user": "root", "password": "secret", "sudo_password": "secret"}
}

SSH server fields

FieldRequiredDescription
typeYesMust be "ssh"
hostYesSSH hostname or IP
portNoSSH port (default: 22)
userNoSSH username (default: root)
passwordNoSSH password (if not using key auth)
ssh_keyNoPath to SSH private key file
ssh_certNoPath to SSH certificate signed by CA
sudo_passwordNoPassword for sudo -S (needed for journal tools if user lacks group privileges)
timeoutNoSSH timeout in seconds (default: 30)
verify_host_keyNoVerify SSH host key (default: false)
known_hosts_fileNoPath to known_hosts file (used when verify_host_key=true)

mikr.us API server fields

FieldRequiredDescription
typeYesMust be "mikrus"
keyYesAPI key from the mikr.us panel
srvYesServer identifier (e.g. srv123)

Usage

All tools accept an optional server parameter to target a specific configured server. If MCP_DEFAULT_SERVER is not set, the first server in the JSON object is used as the default.

Security Considerations

This MCP server grants full system access to configured servers. Treat it as a privileged remote administration tool.

SSE Transport

  • By default, SSE listens on 127.0.0.1 only.
  • Binding to 0.0.0.0 requires MCP_UNSAFE_PUBLIC_ACCESS_CONFIRMED=1.
  • Never expose SSE to the internet without authentication. Use a reverse proxy (nginx, Traefik) with TLS and basic auth if remote access is needed.

SSH Security

  • verify_host_key defaults to false for ease of use. In production, set it to true and provide a known_hosts_file to prevent MITM attacks.
  • sudo_password is fed via asyncssh.create_process + stdin, never via shell string interpolation, so it won't appear in ps aux.
  • SSH private keys mounted into Docker must have permissions 600 or 400.

Input Validation

  • All file paths are validated against traversal (..) and forbidden paths (e.g. /etc/shadow).
  • Dangerous commands (rm -rf /, mkfs, dd if=) are blocked before execution.
  • Shell metacharacters (;, |, $, `) in commands are rejected before any patterns are checked.
  • Service names, container names, ports, and domains are validated with strict regex patterns.
  • File writes outside /home, /var/www, /opt, /tmp, /srv, /var/log trigger a warning.

Write Guard

  • Write, destructive, and command-execution tools are gated behind ENABLE_WRITE_OPERATIONS=1 (default: disabled).
  • When disabled, these tools return a structured error before any I/O.
  • Read-only actions (manage_service status, manage_process list) bypass the write guard.

Credentials

  • API keys and passwords are loaded from environment variables or .env (gitignored).
  • Never commit credentials to version control.

Claude Desktop Configuration

Add the following to your claude_desktop_config.json. Use absolute paths — Claude Desktop may run from a different working directory.

With .env file (Docker):

{
  "mcpServers": {
    "mikrus": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "--env-file",
        "/absolute/path/to/mikrus-mcp/.env",
        "ghcr.io/paulomac1000/mikrus-mcp:latest"
      ]
    }
  }
}

With inline credentials (Docker, no .env file):

{
  "mcpServers": {
    "mikrus": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-e", "MIKRUS_API_KEY=your_key",
        "-e", "MIKRUS_SERVER_NAME=your_server",
        "ghcr.io/paulomac1000/mikrus-mcp:latest"
      ]
    }
  }
}

Native Python (no Docker):

{
  "mcpServers": {
    "mikrus": {
      "command": "mikrus-mcp",
      "env": {
        "MIKRUS_API_KEY": "your_key",
        "MIKRUS_SERVER_NAME": "your_server"
      }
    }
  }
}

Multi-server with SSH key mount:

{
  "mcpServers": {
    "mikrus": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-v", "/home/user/.ssh/id_ed25519:/home/appuser/.ssh/id_ed25519:ro",
        "-e", "MCP_SERVERS={\"prod\":{\"type\":\"ssh\",\"host\":\"10.0.0.5\",\"user\":\"admin\",\"ssh_key\":\"/home/appuser/.ssh/id_ed25519\"}}",
        "-e", "MCP_DEFAULT_SERVER=prod",
        "ghcr.io/paulomac1000/mikrus-mcp:latest"
      ]
    }
  }
}

After restarting Claude Desktop, the 33 mikrus tools will be available for use.

Development

Setup

pip install -e ".[dev]"

Run tests

# Unit tests (fast, no credentials needed) — 310 tests
pytest tests/unit/ -q --cov=mikrus_mcp --cov-report=term

# Smoke tests — API connectivity and response format
pytest tests/smoke/ -q

# Integration tests — real mikr.us API
pytest tests/integration/ -q

# E2E tests — full pipeline workflows
pytest tests/e2e/ -q

All tests use respx (HTTP mocking) or unittest.mock (SSH mocking). No real network calls are made in unit or e2e tests. Smoke and integration tests require a valid MIKRUS_API_KEY and skip gracefully otherwise.

REST Bridge (optional testing utility)

Set MCP_REST_PORT to expose an HTTP bridge that turns tool calls into REST endpoints:

MCP_PORT=8300 MCP_REST_PORT=8301 mikrus-mcp
# GET  http://127.0.0.1:8301/health
# GET  http://127.0.0.1:8301/tools
# POST http://127.0.0.1:8301/tools/get_server_info  {"params": {}}

This is a development utility for smoke testing and debugging. It is separate from the MCP SSE transport and runs on its own port. All endpoints have /api/ prefixed mirrors (GET /api/health, GET /api/tools, POST /api/tools/{name}). Tool manifest endpoints are also available at GET /tools/{name}/manifest and GET /api/tools/{name}/manifest.

Lint & type check

ruff check src/ tests/
ruff format --check src/ tests/
mypy src/
bandit -r src/

In Docker

docker compose run --rm test

Architecture

src/mikrus_mcp/
├── __init__.py          # Package version
├── __main__.py          # python -m mikrus_mcp entry point
├── config.py            # Environment variable loader (dotenv) — single-server, multi-server, SSH-only
├── constants.py         # Backward-compat re-export for tools/constants.py
├── validators.py        # Centralized input validation (path, port, service, container, domain)
├── sanitizer.py         # Log sanitization (redacts API keys, IPs, passwords, MACs)
├── client.py            # Async HTTP client (httpx) + SSH client (asyncssh)
├── server.py            # MCP server with 33 tools, stdio + SSE transport, partial startup
├── rest_bridge.py       # Optional REST bridge for smoke/e2e testing (on MCP_REST_PORT)
└── tools/
    ├── __init__.py
    ├── constants.py     # SSOT defaults, tool manifests, validation limits, write guard
    ├── response.py      # _success_response / _error_response helpers
    ├── capabilities.py  # describe_mikrus_capabilities introspection tool (L3+)
    ├── mikrus_api.py    # 12 mikr.us API tools + internal functions + registration
    ├── system.py        # 14 system management tools (exec, file, service, disk, etc.)
    ├── container_journal.py  # 6 Docker + journalctl tools + registration
    └── discovery.py     # Server listing tool + registration

tests/
├── conftest.py              # Root: environment loading
├── fixtures.py              # Mock data constants
├── _env_loader.py           # Shared .env loader for conftest files
│
├── unit/                    # Unit tests — zero I/O, fully mocked
│   ├── conftest.py
│   ├── test_client.py
│   ├── test_config.py
│   ├── test_server_tools.py
│   ├── test_ssh_client.py
│   ├── test_multi_server.py
│   ├── test_rest_bridge.py
│   ├── test_sanitizer.py
│   ├── test_tool_registration.py
│   ├── test_capabilities.py
│   └── test_validators.py
│
├── smoke/                   # Smoke tests — direct API calls (skipif no creds)
│   ├── conftest.py
│   ├── test_connectivity.py
│   ├── test_critical_tools.py
│   └── test_response_format.py
│
├── integration/             # Integration tests — real API calls
│   ├── conftest.py
│   ├── mcp_wrapper.py
│   └── test_real_tools.py
│
└── e2e/                     # E2E tests — full pipeline workflows
    ├── conftest.py
    └── test_server_api.py

Troubleshooting

IssueSolution
No servers availableCheck that MIKRUS_API_KEY + MIKRUS_SERVER_NAME or MCP_SERVERS JSON is set correctly.
SSH key not foundVerify the path in ssh_key is absolute and accessible from the container (mount it as a volume).
Journal access deniedAdd sudo_password to the SSH server config, or add the user to the systemd-journal / adm group.
Port must be 1-65535The MCP_PORT env var must be a valid TCP port number.
Refusing to start on 0.0.0.0Set MCP_HOST=127.0.0.1 or set MCP_UNSAFE_PUBLIC_ACCESS_CONFIRMED=1 (not recommended).
Partial startup warningOne or more servers failed to connect. Check logs — other servers are still available.

Notes

  • 33 tools total: 2 discovery + 12 mikr.us API + 19 system management tools.
  • All tools return {"success": True/False, ...} JSON format for consistent error handling.
  • All write operations are protected by input validation — no shell injection possible.
  • Errors are logged to stderr, in compliance with the MCP specification.
  • The mikr.us /exec endpoint has a 65-second client timeout (API limit is 60s).
  • /stats, /info, /serwery, /db, and /porty have a 60-second API-side cache.
  • Tool descriptions are optimized for LLM agents — each explains when and why to use the tool.

License

MIT — see LICENSE for details.