mikrus-mcp

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 AI Skills Python 3.12–3.14 Version 2.1.0 License: MIT

A hardened Model Context Protocol server for managing mikr.us VPS instances and remote Linux hosts over SSH.

mikrus-mcp exposes a bounded set of administration capabilities through one policy-enforced invocation path. It supports local stdio and authenticated loopback-only Streamable HTTP, separates read operations from mutations, binds privileged actions to an exact target identity and resource, and keeps raw shell execution out of the public MCP surface.

Version 2.1 adds typed SSH program execution and bounded process jobs. Version 2.0 remains intentionally stricter than 1.x: Python 3.12+ is required, legacy HTTP+SSE is removed, SSH host verification is enabled by default, mutations require explicit write enablement and short-lived server-side approvals, and broad legacy management tools have been replaced with operation-specific capabilities.

Contents

Highlights

  • mikr.us + SSH — manage mikr.us API targets and ordinary SSH-accessible Linux hosts from the same MCP server.
  • One invocation kernel — validation, authorization, target binding, approvals, deadlines, concurrency, sanitization, provenance, and failure classification are enforced in one application-owned path.
  • Two-phase authorization — selector and data-policy checks happen before target resolution; exact backend identity and resource checks happen after resolution.
  • Safe-by-default mutations — writes are disabled by default, never automatically retried, and require a one-time approval bound to principal, capability, resolved target identity, resource, and normalized arguments.
  • Verified SSH identity — host-key verification is enabled by default; mutation identity includes the verified SHA-256 host-key fingerprint.
  • No public raw shell tool — privileged operations are exposed as bounded, operation-specific tools with validation.
  • Reproducible builds — committed hashed dependency locks are maintained for Linux x64 on CPython 3.12, 3.13, and 3.14.
  • Exact artifact verification — CI builds and exercises the exact wheel and Linux/amd64 container artifact.
  • Runtime provenance — capability discovery and successful results expose version, source/build, artifact, configuration, and instance-generation fields when supplied by the build/deployment profile.
  • Pinned standards authority — repository contracts are aligned with the pinned ai-skills@main stable revision recorded in ai-skills.lock.yaml.

Requirements

For local execution:

  • Python 3.12, 3.13, or 3.14
  • a mikr.us API key and server identifier, or an SSH-accessible Linux host
  • for SSH writes: a verified host key and a target with the required POSIX/Python primitives

Docker can be used instead of installing Python directly.

Quick start

1. Clone and create an environment

git clone https://github.com/paulomac1000/mikrus-mcp.git
cd mikrus-mcp

python3.12 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install .

For repository development, use the committed hashed development lock instead of resolving dependencies ad hoc. See Development.

2. Configure a single mikr.us server

export MIKRUS_API_KEY='replace-me'
export MIKRUS_SERVER_NAME='srv123'

The single-server form is the smallest configuration. For SSH or multiple targets use MCP_SERVERS; examples are below.

3. Start the MCP server

.venv/bin/python -m mikrus_mcp

stdio is the default transport, so this command is suitable for desktop MCP clients which launch the server as a subprocess.

4. Build and run with Docker

docker build -t mikrus-mcp:2.1.0 .

docker run --rm \
  -e MIKRUS_API_KEY='replace-me' \
  -e MIKRUS_SERVER_NAME='srv123' \
  mikrus-mcp:2.1.0

Published release images are promoted by immutable digest. Prefer a release tag or digest over an unpinned moving tag in production.

MCP client configuration

A typical desktop-client configuration can launch the project directly through Python:

{
  "mcpServers": {
    "mikrus": {
      "command": "/absolute/path/to/mikrus-mcp/.venv/bin/python",
      "args": ["-m", "mikrus_mcp"],
      "env": {
        "MIKRUS_API_KEY": "replace-me",
        "MIKRUS_SERVER_NAME": "srv123"
      }
    }
  }
}

Use absolute paths. GUI applications frequently start with a different working directory than your shell.

For Docker-based clients, point the MCP command at docker run and pass credentials through an environment file or secret mechanism rather than embedding long-lived credentials in the client configuration.

Available tools

The supported catalog is configuration-aware. Tools that do not apply to any configured backend, lack the durable-job store, or are mutations disabled by policy are omitted from the public tool list and remain visible in the capability catalog with an inactive reason.

Discovery

ToolTypeDescription
list_configured_serversReadList configured targets and their current connection state.
describe_mikrus_capabilitiesReadReturn the supported capability catalog and policy metadata.

mikr.us API tools

These capabilities require at least one configured mikrus target.

ToolTypeDescription
get_server_infoReadBasic VPS information such as RAM, disk, expiry, and server state.
list_serversReadList servers associated with the mikr.us account.
get_server_statsReadRetrieve resource and runtime statistics.
restart_serverMutationRestart the selected VPS.
get_logsReadFetch recent mikr.us task logs.
get_log_by_idReadFetch one task log by ID.
boost_serverMutationRequest the supported temporary resource boost.
get_db_infoSensitive readRetrieve database connection information; credential fields are protected by the response sanitizer.
get_portsReadShow assigned ports.
get_cloudReadShow cloud services associated with the account.
assign_domainMutationAssign a domain or generated subdomain to a port.

Linux system tools

These capabilities operate through the configured backend abstraction and are available where the target supports them.

ToolTypeDescription
read_fileSensitive readRead a bounded text file from an allowed path.
write_fileMutationAtomically write a file using no-follow directory traversal.
get_service_statusReadInspect one systemd service.
change_service_stateMutationStart, stop, restart, enable, or disable a validated service.
analyze_diskReadInspect filesystem usage.
check_portReadCheck whether a TCP port is listening and identify the owning process where available.
list_processesSensitive readList processes using a bounded output contract.
terminate_processMutationTerminate a validated process target.
update_systemMutationRun the supported system-update workflow.
list_directorySensitive readList a validated directory.
tail_fileSensitive readRead the bounded tail of a text file.
search_in_filesSensitive readSearch within a validated path.
execute_programMutationRun an approved executable with typed argv, optional cwd, and optional stdin through an SSH target.
start_programMutationStart an approved typed program and return a bounded in-process job handle.
get_program_statusReadInspect the status of an owned typed program job.
get_program_resultReadRetrieve the terminal result of an owned typed program job.
cancel_programMutationCancel an owned queued or running typed program job.
remote_job_startMutationStart an SSH-backed durable job using an idempotency key.
remote_job_statusReadInspect a durable remote job by owner-bound job ID.
remote_job_waitReadWait server-side for bounded remote job progress or terminal state.
remote_job_resultReadRetrieve a durable remote job result.
remote_job_outputReadRead bounded stdout or stderr using an explicit cursor.
remote_job_cancelMutationCancel the exact remote process group after PID identity validation and report whether termination was verified.
file_patch_atomicMutationReplace a regular file only when its current SHA-256 digest matches expected_digest (compare-and-swap; binary-safe via base64; serialized by a host-local advisory lock across mikrus-mcp writers).
cron_listReadReport owned cron profiles with their installed crontab state and marker digest.
cron_upsertMutationIdempotently project one owned cron profile into the installed crontab.
cron_removeMutationRemove exactly the marker and generated line pair for one owned cron profile.
docker_runtime_snapshotSensitive readReturn a bounded semantic snapshot of one compose service or container.
docker_recreate_planSensitive readCompute a canonical semantic recreate plan with a bound plan receipt and an expiring server-side record.
docker_recreate_applyMutationApply a verified recreate plan; reports ALREADY_APPLIED when state already matches.
service_waitReadWait server-side for bounded compose service readiness.
get_memory_infoReadShow memory usage.
get_network_infoSensitive readShow network interfaces and listening sockets.
get_process_treeSensitive readShow the process tree.

Docker tools

ToolTypeDescription
list_docker_containersSensitive readList containers and current state.
get_docker_logsSensitive readFetch bounded recent logs for one container.
get_docker_statsReadShow container resource statistics.

Journal tools

ToolTypeDescription
get_journal_logsSensitive readFetch bounded journal output for one unit.
find_system_errorsSensitive readFind recent error-level journal events.
search_journal_logsSensitive readSearch journal output for a bounded term.

Journal access depends on the remote account's permissions. A configured sudo_password can be used when the account is not already allowed to read the journal; the password is sent over process stdin rather than interpolated into the shell command.

Multi-server configuration

Set MCP_SERVERS to a JSON object keyed by the public target alias.

Mixed mikr.us + SSH example

export MCP_SERVERS='{
  "vps": {
    "type": "mikrus",
    "key": "replace-me",
    "srv": "srv123"
  },
  "host": {
    "type": "ssh",
    "host": "server.example.com",
    "port": 22,
    "user": "admin",
    "ssh_key": "/home/me/.ssh/id_ed25519",
    "known_hosts_file": "/home/me/.ssh/known_hosts"
  }
}'
export MCP_DEFAULT_SERVER='vps'

SSH target fields

FieldRequiredDefaultNotes
typeyesMust be "ssh".
hostyesHostname or IP address.
portno22TCP port, 1–65535.
usernorootSSH username.
passwordnoPassword authentication. Prefer key authentication where possible.
ssh_keynoExisting private-key file; must be protected from group/other access.
ssh_certnoOptional SSH certificate.
sudo_passwordnoOptional password for operation-specific sudo -S commands.
known_hosts_filenoAsyncSSH default policyOptional explicit known_hosts file.
timeoutno30Connection/operation timeout input, 1–300 seconds.
verify_host_keynotrueHost-key verification is enabled by default.

Disabling SSH host verification requires MCP_ALLOW_INSECURE_SSH=1 and is limited to read-only development use. Startup fails if writes are enabled while an SSH target has host verification disabled.

Durable remote jobs

Set an absolute owner-controlled store path to activate the remote_job_* capability family. The file is created with private permissions and updated through bounded atomic replacement:

export MCP_REMOTE_JOB_STORE_FILE="$PWD/.mikrus-remote-jobs.json"

The capability remains inactive when this setting is absent or when no SSH target is configured. Do not place the store in a shared or symlinked directory. Cancellation verifies descendant termination after the kill and reports an honest terminated field; a start whose outcome was ambiguous is reconciled once against the remote record instead of surfacing the raw timeout (an unrecoverable lookup surfaces AMBIGUOUS_OUTCOME). Job records expire after a 7-day retention horizon (REMOTE_JOB_RETENTION_SECONDS = 604800); expired records are cleaned on the next registry read.

Cron profiles

Set an absolute owner-controlled store path to activate the cron_list, cron_upsert, and cron_remove capability family:

export MCP_CRON_PROFILE_STORE_FILE="$PWD/.mikrus-cron-profiles.json"
export MCP_DOCKER_PLAN_STORE_FILE="$PWD/.mikrus-docker-plans.json"

Profiles are owner-bound durable records with a typed five-field schedule, an allowlisted executable, typed arguments, and optional bounded environment assignments. Projection into the installed crontab uses one marker comment line (# mikrus-mcp:<profile_id>:sha256=<digest>) followed by exactly one generated cron line whose arguments are strictly single-quote escaped; arbitrary command strings are never serialized. Crontab updates re-read the file immediately before install and fail with CONCURRENT_MODIFICATION when it changed concurrently. All non-profile crontab lines are preserved byte-for-byte, and cron capabilities remain inactive when the setting is absent or no SSH target is configured. Crontab installs and reads are serialized through a host-local advisory lock (~/.mikrus-mcp/locks/crontab.lock) so mikrus-mcp writers never interleave; concurrent non-mikrus writers stay outside that guarantee and are still caught by the immediate pre-install re-read (CONCURRENT_MODIFICATION).

The same advisory-lock family (~/.mikrus-mcp/locks/cas.lock) covers file_patch_atomic: the digest read, comparison, temp-file write, and rename all hold the lock, making the compare-and-swap atomic across every mikrus-mcp writer on the host. Non-mikrus writers are outside the guarantee; the digest precondition still detects them at operation start.

Docker and Compose

The docker_runtime_snapshot, docker_recreate_plan, docker_recreate_apply, and service_wait capabilities manage compose services over an SSH target without a shell. docker_recreate_plan resolves the compose project identity from the live container labels (never a directory basename), reads the desired state through docker compose config, and returns a canonical semantic plan bound by a plan:v1:sha256:... receipt. The plan persists in a durable server-side record store (enabled by MCP_DOCKER_PLAN_STORE_FILE, an absolute operator-controlled path with private permissions; records expire after 300 seconds), so apply derives every desired-state input from the stored record rather than from invocation arguments. Environment values are used for apply comparison but are never returned to the model — only environment keys are visible.

docker_recreate_plan additionally records whether the live container carries runtime-only drift relative to compose (fields such as runtime-applied environment or labels that the compose file does not declare). Planning with allow_runtime_drift=true records explicit acceptance; otherwise docker_recreate_apply refuses the mutation with RECREATE_CONFIG_DRIFT and the operator must re-plan to accept losing that runtime-only configuration.

docker_recreate_apply takes only the service and plan_receipt; a missing or expired record is PLAN_STALE. The stored record classifies drift: compose-desired fields changed since planning → PLAN_STALE; the image digest moved on an implicitly pinned tag → IMAGE_DRIFT; the digest moved under an explicitly pinned image → PLAN_STALE. Behavior is identical fail-closed: re-plan required. When the live semantic state already equals the compose-desired state the apply reports ALREADY_APPLIED without recreating. The apply itself is the bounded argv sequence docker compose -p <project> -f <files>... up -d --no-deps --force-recreate <service>; a single inspect re-check follows, and a readiness wait is mandatory: the default is healthy when the recreated container defines a healthcheck and running otherwise, with a 15-second budget; readiness and timeout_seconds (5–25) are tuning-only. A failed wait surfaces its typed error with applied=true in the message — the recreate executed, so reconcile rather than blind-retry. service_wait polls docker inspect inside one bounded helper invocation and reports READINESS_TIMEOUT or HEALTH_FAILED as read-class errors; with a plan_receipt it verifies the record exists before waiting.

mikr.us target fields

FieldRequiredDefaultNotes
typeyesMust be "mikrus".
keyyesAPI key.
srvyesStable mikr.us server ID.
api_urlnohttps://api.mikr.usMust use HTTPS.

If MCP_DEFAULT_SERVER is omitted, the first configured target is used as the default selector.

Authorization and approvals

Scope model

The built-in single-operator default includes these read-policy axes:

tool:*
target:*
target-id:*
resource:*
data:*

Mutations additionally require their capability scope and write:server.

If you set MCP_ALLOWED_SCOPES yourself, it replaces the default set. A legacy tool:*,target:* override is therefore intentionally insufficient for target-backed operations after the resolved-identity authorization phase.

The important scope families are:

  • tool:<capability> — allows the capability itself.
  • target:<selector> — allows the configured public target alias before target resolution.
  • target-id:<resolved-identity> — allows the exact resolved backend identity.
  • resource:<capability>:sha256:<digest> — allows an exact normalized resource; resource:* is the explicit wildcard profile.
  • data:<classification> — permits the manifest confidentiality class; data:* is the explicit wildcard profile.
  • write:server — enables mutation authorization when process write policy is also enabled.

Enabling mutations

Mutations require both process policy and a matching short-lived approval record:

export MCP_WRITE_ENABLED=true
export MCP_APPROVAL_FILE="$PWD/.mikrus-approvals.json"

Approval records are created from the trusted operator shell, not by the model. The helper resolves the target before persisting the record; for SSH this binds the current verified host-key fingerprint.

General form:

.venv/bin/python scripts/approval.py \
  --file "$MCP_APPROVAL_FILE" \
  --capability '<capability>' \
  --principal '<principal>' \
  --server '<configured-alias>' \
  --resource '<normalized-resource>' \
  --arguments-json '<JSON object without server>' \
  --ttl-seconds 60

Approvals expire quickly (default 60 seconds, maximum 300 seconds), are consumed once, and are matched against principal, capability, exact resolved target identity, resource, and normalized argument digest.

Transports

stdio

stdio is the default and recommended transport for local desktop integrations:

export MCP_TRANSPORT=stdio
.venv/bin/python -m mikrus_mcp

Protocol traffic owns stdout; diagnostics are written to stderr.

Streamable HTTP

Streamable HTTP is deliberately restricted to literal loopback addresses and requires a protected bearer-token file.

umask 077
python -c 'import secrets; print(secrets.token_urlsafe(48))' > .mcp-http-token
chmod 600 .mcp-http-token

export MCP_TRANSPORT=streamable-http
export MCP_HOST=127.0.0.1
export MCP_PORT=8000
export MCP_HTTP_BEARER_TOKEN_FILE="$PWD/.mcp-http-token"

.venv/bin/python -m mikrus_mcp

Clients connect to http://127.0.0.1:8000/mcp with Authorization: Bearer <token>.

Remote/public HTTP exposure is not part of the supported security profile. Legacy two-endpoint HTTP+SSE and the unauthenticated REST bridge were removed in 2.0.

Result format

Application results use a structured success/error envelope and include correlation/provenance metadata.

Representative success:

{
  "success": true,
  "data": {
    "param_ram": "1024"
  },
  "_meta": {
    "request_id": "6a5c...",
    "capability": "get_server_info",
    "capability_version": "2.1.0",
    "source": "mikrus-mcp",
    "artifact": "mikrus-mcp==2.1.0",
    "target": "srv123",
    "target_identity": "mikrus:srv123",
    "backend": "mikrus",
    "duration_ms": 42
  }
}

Representative failure:

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "request rate limit exceeded",
    "retryable": true,
    "retry_after_seconds": 2.0
  },
  "_meta": {
    "request_id": "9b8f...",
    "capability": "get_server_info",
    "source": "mikrus-mcp"
  }
}

Metadata is only included when it is safe and actually known. For example, a pre-resolution authorization failure does not disclose the backend's resolved identity.

Build and deployment profiles provide immutable provenance through the embedded _build_provenance.json stamp, written by scripts/stamp_build_provenance.py from the inputs --source-revision, --build-id, --built-at, and --config-revision; packageContentDigest is computed from the stamped package during stamping. At runtime only two environment variables are read: MIKRUS_MCP_CONFIG_REVISION (overrides the stamped configRevision) and MIKRUS_MCP_DEPLOYMENT_RECEIPT_FILE (deployment receipt path). The stamped sourceRevision, buildId, packageContentDigest, and builtAt are reported in capability discovery and successful result metadata; they cannot be injected through environment variables or inferred from a mounted checkout. Build wheels through scripts/build_wheel.py --provenance stamped|unstamped so the stamp cannot silently go stale.

Result limits are enforced against the serialized application envelope, including metadata, rather than only the nested data value.

Security model

mikrus-mcp is a privileged administration service. Treat its process environment, bearer-token file, SSH keys, approval registry, and target credentials as secrets.

Typed program jobs remain bounded and process-local. When MCP_REMOTE_JOB_STORE_FILE is configured, the remote_job_* tools use a durable owner-bound receipt and an SSH-side JSON-stdin helper; status, wait, result, output cursors, and cancel do not require model-side sleep loops or PID archaeology. The store path must be an absolute operator-controlled file path. Remote-job mutations still require write enablement and one-time approval.

Key defaults:

  • no fallback from a failed target to another configured target;
  • SSH host-key verification enabled by default;
  • loopback-only authenticated Streamable HTTP;
  • mutations disabled unless MCP_WRITE_ENABLED=true;
  • one-time server-side approvals for every public mutation;
  • no automatic mutation retries after timeout, disconnect, rate limit, or ambiguous completion;
  • no public arbitrary-command tool;
  • typed program execution is SSH-only, approval-bound, and sends user-controlled values as helper stdin rather than shell command text;
  • component-safe no-follow remote file writes;
  • bounded request, response, output, concurrency, and deadline behavior;
  • sensitive response fields sanitized before model-visible serialization.

Read SECURITY.md before enabling writes or deploying the server outside a disposable environment.

Development

The repository maintains hashed Linux x64 development locks for every supported Python lane. For the default 3.12 lane:

python3.12 -m venv .venv
.venv/bin/python -m pip install "pip==26.1.2"
.venv/bin/python -m pip install --require-hashes -r requirements-dev-linux-x64-py312.lock
.venv/bin/python -m pip install --no-deps .

Run the fast credential-free gate:

.venv/bin/python scripts/core_gate.py

Run the complete local repository gate:

.venv/bin/python scripts/ci.py

Run a focused test:

.venv/bin/python -m pytest tests/unit/test_kernel.py -q

Build the wheel (stamped builds refuse to run when sources are newer than the embedded build stamp; unstamped builds strip the stamp first):

.venv/bin/python scripts/build_wheel.py --provenance stamped
# or, for an unstamped local wheel:
.venv/bin/python scripts/build_wheel.py --provenance unstamped

requirements-runtime.in and requirements-dev.in are human-edited inputs. requirements-*-linux-x64-py3*.lock files are generated exact hashed graphs and should not be hand-edited.

Hosted CI additionally validates the supported Python matrix, manifests, documentation, static security policy, exact wheel behavior, official MCP transports, dependency locks, and Linux/amd64 container behavior.

Architecture

MCP client
   |
   |  stdio / authenticated loopback Streamable HTTP
   v
MCP registration + transport boundary
   |
   v
InvocationKernel
   |-- argument validation
   |-- principal / selector authorization
   |-- target resolution
   |-- resolved identity / resource authorization
   |-- write policy + one-time approval
   |-- deadline + concurrency policy
   |-- adapter execution
   |-- sanitization + provenance + structured errors
   |
   v
TargetRegistry
   |                    |
   v                    v
mikr.us API adapter   SSH adapter

Backend adapters do not own MCP policy. Transport wrappers do not bypass the kernel. Public tool registration is derived from the active application-owned manifest catalog.

For the full lifecycle and failure model see docs/architecture.md.

Troubleshooting

A tool is missing from list_tools

The supported and active catalogs are intentionally different. A capability may be inactive because:

  • no configured backend supports it;
  • writes are disabled;
  • another process policy makes the capability unavailable.

Inspect describe_mikrus_capabilities / capabilities://catalog for the inactive reason.

AUTHORIZATION_FAILED after upgrading from 1.x

If you explicitly set MCP_ALLOWED_SCOPES, check that it includes the resolved authorization axes. The 2.0 default is:

tool:*,target:*,target-id:*,resource:*,data:*

Mutations additionally need the relevant tool scope plus write:server, process write enablement, and a matching approval.

SSH refuses to start or connect

Host verification is enabled by default. Make sure the remote host is present in the effective known_hosts policy or set a valid known_hosts_file. Do not disable verification for writable deployments.

health://ready reports not ready

The configured default target is a mandatory readiness dependency. Startup itself is lazy, so the target can initially be not_connected; readiness becomes true only after a successful connection to the default target.

Mutation returns AMBIGUOUS_OUTCOME

Do not retry blindly. The request may have reached the backend before the transport/deadline failure. Reconcile the remote state first, then issue a fresh approval only if another mutation is actually required.

Streamable HTTP refuses the token file

The token file must be a regular, non-symlink file owned by the process user, not accessible by group/other users, and contain one token of at least 32 characters.

Standards and migration

The repository pins the exact AI Skills authority in ai-skills.lock.yaml. The MCP architecture contract is based on the stable mcp-server-architect/STANDARD.md entrypoint.

Repository CI emits structural evidence against the pinned authority. Structural evidence is not the same thing as independent production acceptance; deployment-specific real-system evidence and independent review remain separate gates.

For the 1.x → 2.0 breaking changes and rollback procedure see MIGRATION.md.

Additional references:

License

MIT