Skip to main content
Glama
cgfm

dockhand-readonly-mcp

by cgfm

Dockhand Read-only MCP

Read-only-MCP-Adapter für die Dockhand-REST-API. Das Projekt unterstützt zwei Transporte:

  • lokales STDIO für Codex auf demselben Rechner

  • authentifiziertes Streamable HTTP für den Docker-Betrieb

Sicherheitsgrenze

Dockhand Free kennt keine Viewer-Rolle. Der zugrunde liegende Dockhand-API-Token besitzt daher weiterhin die Rechte seines Dockhand-Benutzers. Der Adapter reduziert die für MCP erreichbare Oberfläche auf fest implementierte HTTP-GET-Abfragen:

  • Health, Umgebungen, Container, Netze und Stacks lesen

  • aggregierten Laufzeitstatus bilden

  • keine frei wählbaren URLs oder HTTP-Methoden

  • keine Start-, Stop-, Restart-, Deploy-, Create-, Edit- oder Delete-Werkzeuge

  • keine Logs, Terminals, ungefilterten Raw-Inspect-Daten oder Compose-Dateien

  • keine Ausgabe von Werten aus Umgebungsvariablen oder erkannten Zugangsdaten

  • explizite Security-Ansichten für Capabilities, Namespaces, Limits, Devices, Mount-Pfade, Port-Bindings, Netzwerkadressen, IPAM, Labels und Treiberoptionen; geheimnisverdächtige Label- und Optionswerte werden redigiert

Der Container erhält keinen Docker-Socket. Er kommuniziert ausschließlich über die Dockhand-REST-API. Der von Codex verwendete MCP-Bearer-Token ist ein zweites, unabhängiges Secret und darf nicht mit dem Dockhand-Token identisch sein.

Docker-Architektur

Codex
  └─ HTTPS + separater MCP-Bearer-Token
     └─ Reverse Proxy: mcp.example.com
        └─ dockhand-readonly-mcp:3030
           └─ konfiguriertes Docker-Netz + Dockhand-API-Token
              └─ dockhand:3000/api

Der MCP-Adapter lauscht auf Port 3030; Dockhands eigener Standardport 3000 bleibt davon getrennt. Der MCP-Endpunkt ist /mcp. /healthz ist ohne Anmeldung erreichbar und liefert ausschließlich den Adapterstatus. Der Container prüft diesen Endpunkt alle 30 Sekunden. Alle MCP-Anfragen benötigen den separaten Bearer-Token.

Security-Prüfungen per MCP

Für eine Detailprüfung zuerst mit list_containers beziehungsweise list_networks die IDs ermitteln. Anschließend liefern:

  • get_container_security_config: gefilterte Docker-Inspect-Konfiguration mit Benutzer, Privilegien, Capabilities, Namespace-Modi, Security-Optionen, Ressourcenlimits, Devices, Mounts, Ports, DNS, Labels und Netzzuordnungen

  • get_network_security_config: Netzwerk-Details mit Treiber, Flags, IPAM, Optionen, Labels, Peers sowie Endpoint-, MAC- und IP-Adressen

Die Werte von Umgebungsvariablen sowie Entrypoint- und Command-Argumente werden nicht ausgegeben. Sichtbar bleiben die Namen der Umgebungsvariablen und eine Liste der angewendeten Redaktionen.

Automatisches Container-Image auf GitHub

Der Workflow .github/workflows/publish-container.yml testet und baut das Image automatisch für linux/amd64 und linux/arm64. Bei jedem Push auf main wird es in der GitHub Container Registry veröffentlicht als:

ghcr.io/<github-owner>/<repository>:latest

Zusätzlich entstehen ein commitbezogener sha-...-Tag und bei Git-Tags wie v0.2.0 die Tags v0.2.0, 0.2.0 und 0.2. Pull Requests werden getestet und gebaut, aber nicht veröffentlicht.

Vor dem ersten Start:

  1. Das Repository zu GitHub pushen. Der Workflow benötigt keine eigenen Registry-Secrets; er verwendet den automatisch bereitgestellten GITHUB_TOKEN.

  2. Auf der GitHub-Paketseite das erzeugte Container-Paket öffentlich schalten. Soll es privat bleiben, muss der Docker-Host vor dem Abruf mit einem GitHub-Token mit read:packages an ghcr.io angemeldet werden.

Die Compose-Datei verwendet direkt das öffentliche Image ghcr.io/cgfm/dockhand_readonly_mcp:latest.

In Dockhand kann anschließend das GitHub-Repository als Stack-Quelle und compose.yaml als Compose-Datei gewählt werden. Dort müssen außerdem die Werte aus .env.example als Stack-Umgebungsvariablen hinterlegt werden. pull_policy: always sorgt dafür, dass bei einem erneuten Deploy das aktuelle Image aus GHCR abgerufen wird.

Deployment vorbereiten

  1. Benutzer- und Gruppen-ID des Kontos bestimmen, dem die Secret-Dateien gehören:

    id -u
    id -g
  2. Zwei getrennte Secret-Dateien anlegen. Beide müssen diesem Benutzer gehören. Dateimodus 0600 wird empfohlen; bei einem abweichenden Modus protokolliert der Adapter einmalig einen Hinweis, verwendet eine ansonsten gültige und lesbare Datei aber trotzdem:

    • dockhand_api_token: vorhandener Dockhand-Token mit Präfix dh_

    • mcp_bearer_token: neuer, zufälliger Zugriffstoken, beispielsweise aus openssl rand -hex 32

  3. .env.example nach .env kopieren und Docker-Netz, Dockhand-URL, öffentliche MCP-Domain, PUID, PGID sowie die beiden absoluten Secret-Pfade anpassen. Keine Tokenwerte in .env eintragen. Unverschlüsseltes HTTP für DOCKHAND_URL nur innerhalb eines vertrauenswürdigen privaten Docker-Netzes mit DOCKHAND_ALLOW_HTTP=true aktivieren.

  4. Konfiguration prüfen und bereitstellen:

    docker compose config
    docker compose pull
    docker compose up -d
    docker compose ps

Das Compose-Setup veröffentlicht keinen Host-Port. Der Reverse Proxy muss den Container über das in DOCKER_NETWORK konfigurierte externe Docker-Netz erreichen können.

Reverse Proxy

Beispielkonfiguration für Nginx Proxy Manager und mcp.example.com:

Feld

Wert

Scheme

http

Forward Hostname

dockhand-readonly-mcp

Forward Port

3030

Websocket Support

aktiviert

SSL

Zertifikat, Force SSL, HTTP/2

Unter Advanced:

proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

Der Proxy Host sollte nur aus Heimnetz und VPN erreichbar sein. Der Bearer-Token bleibt trotzdem verpflichtend.

Codex mit dem Container verbinden

Den MCP-Bearer-Token im lokalen Prozessumfeld von Codex als DOCKHAND_MCP_BEARER_TOKEN bereitstellen und danach registrieren:

codex mcp add dockhand-readonly \
  --url https://mcp.example.com/mcp \
  --bearer-token-env-var DOCKHAND_MCP_BEARER_TOKEN

Die vorhandene STDIO-Konfiguration erst entfernen, nachdem die HTTP-Variante getestet wurde. Codex unterstützt für Streamable-HTTP-MCPs Bearer-Tokens aus einer benannten Umgebungsvariable; der Tokenwert gehört nicht in config.toml.

Lokaler STDIO-Betrieb

[mcp_servers.dockhand-readonly]
command = "node"
args = ["/absolute/path/to/src/server.mjs"]

[mcp_servers.dockhand-readonly.env]
DOCKHAND_URL = "https://dockhand.example.com"
DOCKHAND_TOKEN_FILE = "/absolute/path/to/dockhand-token"

Tests

npm ci
npm test
npm run live-check
docker build -t local/dockhand-readonly-mcp:0.2.0 .