Help us shape the future of Go! Take the 2026 Go Developer Survey

dockhand-mcp

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: GPL-3.0 Imports: 4 Imported by: 0

README

dockhand-mcp - a Dockhand MCP server, CLI and Go SDK

GitHub release Go Version License build tests lint coverage

An MCP server, CLI and Go SDK for Dockhand: see every container on every Docker host a Dockhand manages, find the ones that are not right, read their logs and restart them - from Claude Code, Claude Desktop, or any other MCP client.

A Dockhand already knows every host: the one it runs on, the ones it reaches at an address, and the ones behind a Hawser agent. dockhand-mcp asks it, so a model does not need a login on each machine, a Docker socket, or to know which host a container is on. "Is anything unhealthy anywhere?" is one call. "Why does it keep restarting?" is two more.

What is in the box
  • Every host at once. A read asks every environment unless one is named and sets the answers side by side, with a host that could not be asked named beside the ones that answered. A container is found by its name alone when only one host has it.
  • What is wrong, said. A container that is unhealthy, that Docker keeps starting again, or that exited with an error says so in a word, and problems narrows a list to those. container_get gives what the health check printed and whether the host killed the container for memory.
  • Secrets stay hidden. A container's variables, its command line and its log lines are read with passwords, tokens and keys hidden, told by the names beside them, unless a call asks for them.
  • Reads until told otherwise. Every tool only reads unless --enable-write is given. The tools that change something take dry_run, say what they did in words, and read the result back from the host rather than trusting the answer to the change.
  • A Go SDK. sdk/dockhand is a client for every call the Dockhand API has, 395 of them, generated from Dockhand's own description of itself, with no knowledge of MCP.
  • Tested against a real server. Every tool and every read of the client runs against an actual Dockhand in Docker, with a Docker host on its socket and a second behind a real Hawser agent, both with its login off and with it on. The suites fail if a registered tool was never seen to answer.

The tools are about which hosts a Dockhand manages and everything Docker holds on them: adding and removing a host; containers, from making one to its files and keeping it on a current image; compose stacks; images, volumes and networks; and, of Dockhand's own side, what it does by itself, the registries it keeps, who it tells when something happens, what its scans find wrong in the images, who may sign in, its settings, its templates and config sets, the tags, labels and icons it keeps, what its web interface remembers, the secret stores it loads a stack's secrets from, and the git repositories it deploys stacks from. The rest of its own side, its backups, is in the SDK and has no tools yet.

Installation

go install github.com/katbyte/dockhand-mcp@latest

Written against Dockhand 1.0.51.

Configuration

All options can be passed as command-line flags, environment variables, or via a configuration file.

Variable Flag Description
DOCKHAND_SERVER --server, -s the address the Dockhand web console answers on, e.g. http://dockhand:3000
DOCKHAND_TOKEN --token, -t a Dockhand API token (web console, Profile, API tokens); leave it out for a Dockhand with its login switched off, which takes none
DOCKHAND_ENABLE_WRITE --enable-write register the tools that change something; without it every tool only reads
DOCKHAND_ENABLE_DELETE --enable-delete register the tools that delete as well; needs --enable-write
DOCKHAND_READ_ONLY --read-only register only tools that never change anything: the default, said out loud, and refused beside --enable-write
DOCKHAND_TOOLSETS --toolsets groups of tools to register, default core,audit: all, core, audit, manage, containers, files, updates, stacks, resources, setup, schedules, registries, notifications, vulnerabilities, or a resource family like container (core is always included)
DOCKHAND_ALLOW_TOOLS --allow-tools register these tools as well as the toolsets asked for, or only these when no toolset is (names, container_* globs, or essential)
DOCKHAND_DENY_TOOLS --deny-tools never register these tools, whatever asked for them (names or globs such as *_stop)
DOCKHAND_LOG log level (WARN default; DEBUG, TRACE, ...)
DOCKHAND_LISTEN --listen serve MCP over HTTP on this address (e.g. :8080) instead of stdio
DOCKHAND_AUTH_TOKEN --auth-token bearer token required on the HTTP endpoint (required with --listen)
DOCKHAND_ALLOW_NO_AUTH --allow-no-auth serve HTTP with no bearer token at all: anyone who can reach the port can use every tool

An API token acts as the Dockhand user who made it, and may do what that user may. A Dockhand with its login switched off asks nobody who they are: anyone who can reach it can do everything it can, through these tools or without them, and server_info says so.

Configuration File

You can place a .dockhand-mcp file in your home directory ~/.dockhand-mcp (for global settings) and in your current directory ./.dockhand-mcp (for per-project settings). Both are read, the project's over the home one, so a project file only needs the settings it changes. Keys are the environment variable names, with or without the DOCKHAND_ prefix, in env format:

SERVER=http://dockhand:3000
TOKEN=dh_...
ENABLE_WRITE=true

A flag or an environment variable beats either file.

Usage

Quick connectivity check:

dockhand-mcp info
Register with Claude Code

.mcp.json:

{
  "mcpServers": {
    "dockhand": {
      "command": "dockhand-mcp",
      "args": ["serve"],
      "env": {
        "DOCKHAND_SERVER": "http://dockhand:3000",
        "DOCKHAND_TOKEN": "..."
      }
    }
  }
}

Or from the shell:

claude mcp add dockhand -e DOCKHAND_SERVER=http://dockhand:3000 -e DOCKHAND_TOKEN=... -- dockhand-mcp serve
Run as a service (HTTP transport)

serve --listen :8080 serves the MCP Streamable HTTP transport at /mcp (plus GET /healthz) instead of stdio. DOCKHAND_AUTH_TOKEN is required: clients must send Authorization: Bearer <token>, and the server refuses to start without one unless DOCKHAND_ALLOW_NO_AUTH=true says that anyone who can reach the port may use every tool. Register it from any machine:

claude mcp add --transport http dockhand http://dockhand-mcp:8080/mcp \
  --header "Authorization: Bearer $DOCKHAND_AUTH_TOKEN"
Docker

Releases publish a multi-arch (amd64, arm64) image to ghcr.io/katbyte/dockhand-mcp, tagged vX.Y.Z, vX.Y and latest. docker-compose.yml is the default always-on deployment: it runs that image and reads secrets from a gitignored .env (copy .env.example). Adjust DOCKHAND_SERVER and TZ in the compose file, then:

cp .env.example .env      # fill in DOCKHAND_TOKEN and DOCKHAND_AUTH_TOKEN
docker compose up -d

make docker builds the same image from source, tagged dockhand-mcp, with version info from git. The image is alpine-based (so docker exec -it dockhand-mcp sh works), runs as a non-root user and has a healthcheck against /healthz. The binary is the entrypoint, so docker run --rm ghcr.io/katbyte/dockhand-mcp info works as a connectivity check with the DOCKHAND_* variables passed via -e.

MCP Tools

Tools are named for what they act on. A tool about a Docker host takes env, the host's name or number as env_list gives them; left out, every host is asked.

Resource Tools
audits audit_all (every audit run over every host, with only the counts: where to look), audit_health (containers broken right now: unhealthy, restarting, exited with an error, killed for memory, could not start), audit_hosts (hosts Dockhand cannot reach), audit_stacks (stacks not wholly up), audit_healthcheck (running containers nothing checks), audit_restart_policy (running containers that would stay down after a reboot), audit_updates (containers a newer image is out for), audit_vulnerabilities (images with critical or high vulnerabilities, and hosts with images never scanned), audit_security (containers that are privileged, on the host's network or processes, hold the Docker socket or the host's disk, or were given capabilities that reach outside), audit_image_tags (containers on latest, no tag, or an image with no name), audit_limits (running containers with no memory limit), audit_stopped (containers left stopped for days), audit_unused (images, volumes and networks nothing uses)
server server_info (connectivity, version, whether the login is on, the health of Dockhand's own database), server_update_check (whether a newer version of Dockhand itself is out, told apart from a Dockhand that could not look), server_about (its licence, its privacy policy, and the software it is built from by licence), server_files (a directory on Dockhand's own filesystem, where the compose files of its stacks lie), server_read (one text file there), server_mkdir (make a folder there)
environments env_list (every Docker host: how it is reached, whether it is online, and what it holds by state), env_get (one host in full: how it is reached, the machine and its Docker, and what Dockhand does for it by itself), env_test (have Dockhand try a host, saved or not yet, and say what it found there and which Hawser agent answered), env_create (add a host by its socket, its address, an agent Dockhand calls or an agent that calls Dockhand, tried first), env_edit (change its name, labels and how it is reached, tried afterwards), env_schedules (its timezone, and when Dockhand checks its containers for newer images, prunes its images and warns of a filling disk), env_delete (forget a host, leaving what runs on it alone), env_disk (what a host's Docker disk holds, images, containers, volumes and build cache, how much of it nothing uses, and the biggest of each)
agents agent_list (the Hawser agents that call in to Dockhand: which are connected, which hosts are waiting for one, and the tokens they call in with), agent_token_create (make or replace the token a host's agent calls in with, shown once), agent_token_delete (revoke one)
containers container_list (every container on every host, narrowed by host, stack, name or state, or to the ones something is wrong with), container_get (one in full: state, exit code, health and what the latest check printed, limits, variables, mounts, ports), container_logs (the end of its log, by time or by a word), container_start, container_stop, container_restart, container_stats (what each running one is using right now, the heaviest first), container_sizes (what each takes on disk, and has written inside itself), container_top (the processes in one), container_compose (one written out as a compose file), container_events (what containers did and when: each start, death with its exit code, kill, restart and removal, and for containers that are gone too), container_create (make one from an image and start it), container_edit (change its variables, labels, ports, mounts or command, by making it again), container_limits (set its memory, processors and restart policy as it runs), container_pause, container_unpause, container_rename, container_exec (run one command in it), container_delete, container_prune (remove every one on a host that is not running), container_events_clear (clear the record of what containers did)
files in a container container_files (a directory, entry by entry), container_read (one text file), container_file_download (a file or a whole directory, written to this machine), container_file_write (write a text file), container_file_upload (send files in from this machine), container_file_mkdir, container_file_move, container_file_perms (permissions and owner), container_file_delete
updates container_updates (which containers have a newer image to be had, as last checked or checked now, with what the newer versions changed), container_update (pull the newer image and make the container again from it), container_updates_clear (forget what was noted), container_autoupdate (which containers Dockhand updates by itself, and when), container_autoupdate_set (set, change or stop that schedule)
stacks stack_list (every compose stack with its containers by service), stack_logs (the logs of a stack's containers as one, in the order written), stack_start, stack_stop, stack_restart, stack_get (one in full: how Dockhand comes to have it, its compose file, its variables, where its secrets are loaded from, its last deploy), stack_validate (check a compose file, a stack's own or text not yet saved, and the variables it needs), stack_deploys (each time Dockhand brought it up, and what compose printed), stack_scan (look through a folder for compose files to adopt), stack_create (make one from a compose file's text and bring it up), stack_edit (change its compose file, its .env file, its secrets, and the secret provider and path they are loaded from), stack_deploy (bring it up from its file, which is how a change takes effect), stack_down (remove its containers and keep its file), stack_adopt (give Dockhand the file of a stack someone else brought up), stack_move (move its files to another folder), stack_delete, stack_deploys_delete (remove the record of one deploy)
images image_list (every image on every host: its names, size, and how many containers were made from it, or only the ones nothing uses), image_get (one in full: its digests, labels, the steps it was built from, and the containers made from it), image_pull (pull an image to a host), image_tag (give one another name as well), image_delete (remove one, with every name it goes by), image_prune (remove the ones a host no longer uses), image_export (write one to an archive on this machine, as docker save does), image_load (load one onto a host from such an archive)
volumes volume_list (every volume on every host with the stack that made it and the containers that mount it, or only the ones nothing mounts), volume_get (one in full, with what its driver was told: a network share's address, say), volume_files (what is in one, a directory at a time), volume_read (one text file in it), volume_create, volume_clone (copy one, files and all, into a new one), volume_delete, volume_prune (remove every one nothing mounts), volume_export (write what one holds, or a part of it, to a tar archive on this machine)
networks network_list (every network on every host with its address ranges and the containers on it, running or not), network_get (one in full, with each container's address on it), network_create, network_connect and network_disconnect (put a container on a network, or take it off), network_delete, network_prune (remove the ones nothing running is on)
registries registry_list (the image registries Dockhand keeps, and which it signs in to), registry_test (whether one can be reached and signed in to, saved or not yet), registry_images (what one holds, or a search of it or of Docker Hub), registry_tags (the tags of an image there, with their sizes), registry_create, registry_edit (its name, address, login and whether it is the default), image_push (push an image on a host to one), registry_delete, registry_image_delete (delete a tag from one)
schedules schedule_list (everything Dockhand does by itself: containers it keeps on the newest image, checks for newer images, image prunes, git syncs, backups and its own tidying, each with when it next runs and how its latest run went), schedule_history (the runs, by type, host, outcome and time, or one run with what it logged), schedule_run (run one now), schedule_set (switch one off or on), schedule_delete, schedule_history_delete (remove the record of a run)
notifications notification_list (the ways Dockhand tells people what happened, mail or an address of another service, with where each sends, the events it is told of and the hosts it is tied to), notification_test (send a test through a channel, saved or not yet, or set an event off for a host and count the channels that took it), notification_create, notification_edit (its name, where it sends, its events and whether it is on), notification_link (tie a channel to a host for the events named, or change the tie), notification_link_delete, notification_delete
vulnerabilities vulnerability_list (the known vulnerabilities in the images of every host, the gravest first, by how grave, image, container or stack, and only what an update would mend), image_scan_get (the latest scan kept of one image), vulnerability_export (the findings written out as CSV, or one image's as SARIF), scanner_get (what each host's images are scanned with, and whether the host has the scanner yet), scanner_set (grype, trivy, both or nothing, and what they are run from and with), image_scan (scan one image now), image_scan_all (scan every image on a host), scanner_delete (take the scanners off a host), scanner_cache_clear (remove the scanners' databases everywhere)
users user_list (the users, whether each is switched on and marked an administrator, what it signs in through and when it last did), user_create (make one, which Dockhand makes an administrator), user_edit (its name, password, email and display name, whether it is switched on and whether it is marked an administrator), user_delete (remove one, and its API tokens with it)
login login_get (whether the login is on at all, how long a session lasts, what whoever signs in is offered, and whether there is a licence), login_set (switch the login on or off, after saying what that means, and set the session's length and the default provider), token_list (the API tokens of the user this server signs in as)
single sign-on sso_list (the OpenID Connect providers Dockhand keeps, switched on or not, without their secrets), sso_create (save one, switched off unless said), sso_edit (change one, switch it on or off, or have its claim for an administrator forgotten), sso_delete (remove one)
settings settings_get (every setting of Dockhand itself, by name, or the ones with a word in their name), settings_set (change the ones named, checked first and read back, with what Dockhand did not take put back)
preferences preferences_get (what the web interface remembers: the order of hosts and of tags, the sidebar, the columns of each grid, the dashboard and its tiles, and the pages it opens on), preferences_set (change any of them, each whole, checked first and read back), favorite_list (the containers and groups kept at hand for each host's log view), favorite_set (set them for one host, whole and in order)
secret providers secret_provider_list (the secret stores Dockhand loads stacks' secrets from, each with where it is and the stacks that use it, never with its token), secret_provider_test (whether Dockhand can reach one and sign in, saved or not yet), secret_provider_keys (the names of the secrets one holds at a path, never their values), secret_provider_create (save one, tried first), secret_provider_edit (its name, its settings, its token), secret_provider_delete (remove one, after naming the stacks it would leave without secrets)
git git_credential_list (what Dockhand signs in to a git server with, each with the repositories fetched with it, never with its password or key), git_credential_create (save one), git_credential_edit (its name, how it signs in and what with, trying every repository that uses it afterwards), git_credential_delete (remove one, after naming the repositories it would leave with none), git_repository_list (the repositories Dockhand keeps, each with its address, branch, credential and the git stacks made from it), git_repository_test (whether Dockhand can fetch one, saved or not yet, with its branches), git_repository_variables (what the .env files in one would give a stack), git_repository_create (save one, tried first), git_repository_edit (its name, address, branch and credential, tried first), git_repository_delete (remove one, after naming the git stacks Dockhand would forget with it), git_stack_list (the stacks deployed from git, each with its commit and, when asked, whether its branch has a newer one), git_stack_get (one in full: the commit fetched, the one it runs from and the newest, its schedule, its webhook and what Dockhand keeps for it), git_stack_create (make one from a repository and bring it up), git_stack_edit (its branch, paths, schedule, webhook, variables, secrets and name), git_stack_sync (fetch the newest commit and say what a deploy would change), git_stack_deploy (fetch and bring it up, or only when there is something new), git_stack_delete (have Dockhand forget it is from git, leaving its containers)
templates template_list (the ready-made ways to run a well-known application that Dockhand has, by words, category, kind or source), template_get (one in full, with the compose file for it), template_source_list (where the templates come from, and how many each gives), template_source_create (add a source by the address of its list), template_source_edit (switch one off or on, rename or move it), template_source_delete (remove one)
tags tag_list (the tags Dockhand keeps, each with the containers and stacks it is on, host by host), tag_create (make one, in one of Dockhand's colours), tag_edit (its name, colour and icon), tag_assign (set the tags of one container or stack, whole or a tag at a time), tag_delete (remove one, from everything that has it)
labels of hosts label_list (the labels Dockhand groups its hosts by, each with its colour and the hosts that have it), label_edit (rename one on every host that has it, colour it, or make two one), label_assign (give one to hosts and take it from others), label_delete (remove one from every host)
icons icon_list (what was given an icon on each host: the host itself, its containers and its stacks), icon_search (find one among selfh.st's icons by words), icon_set (give a container, a stack or a host an icon by name, or send a picture for it from this machine), icon_download (write the picture of an icon to this machine), icon_delete (remove one)
config sets config_set_list (the bundles of container settings Dockhand keeps, each in full with its secrets hidden), config_set_create (save one, checked first), config_set_edit (change it a variable or a label at a time, and its ports and mounts whole), config_set_delete (remove one)
jobs job_get (how slow work left running is going, and how it ended), job_cancel (ask it to stop)

The tools that change something are only registered when --enable-write / DOCKHAND_ENABLE_WRITE is set: the starts, stops and restarts, env_create, env_edit, env_schedules, agent_token_create, server_mkdir, schedule_run, schedule_set, registry_create, registry_edit, image_push, notification_test, notification_create, notification_edit, notification_link, scanner_set, image_scan, image_scan_all, user_create, user_edit, login_set, sso_create, sso_edit, settings_set, preferences_set, favorite_set, secret_provider_create, secret_provider_edit, git_credential_create, git_credential_edit, git_repository_create, git_repository_edit, git_stack_create, git_stack_edit, git_stack_sync, git_stack_deploy, template_source_create, template_source_edit, config_set_create, config_set_edit, tag_create, tag_edit, tag_assign, label_edit, label_assign, icon_set, container_create, container_edit, container_limits, container_update, container_pause, container_unpause, container_rename, container_exec, container_autoupdate_set, container_updates_clear, the container_file_ tools that write, send, make, move and set permissions, image_pull, image_tag, image_load, volume_create, volume_clone, volume_export, network_create, network_connect, network_disconnect, stack_create, stack_edit, stack_deploy, stack_down, stack_adopt, stack_move and job_cancel. Each that makes a change takes dry_run, but for login_set, which asks for confirm before it switches the login on or off. The tools that remove something, env_delete, container_events_clear, scanner_cache_clear and every _delete and _prune, are only registered when --enable-delete / DOCKHAND_ENABLE_DELETE is set as well; without confirm each says what would go and removes nothing.

Two tools that only look need --enable-write all the same. Docker has no way to see inside a volume, so for volume_files and volume_read Dockhand starts a small helper container on the host with the volume mounted, and the first time it has the host pull the busybox image for it. Nothing in the volume is changed and the helper is taken away after each look, but a session that was told it may only read should not be making a host pull and run things, so these two wait for the switch.

A prune says what it would take before it takes it, and one of them is refused outright. Docker prunes a network that no running container is on, so a stack that is merely stopped loses its networks and then will not start, failing with network not found. network_prune lists the stopped containers a prune would leave stranded and refuses to go on unless stranding is given; network_delete refuses a network any container is on, stopped or not. volume_prune removes named volumes too, where docker volume prune leaves them, so its list is worth reading: it is the data of whatever is no longer there to mount it.

Changing what a container was made with makes it again. Docker cannot change a container's variables, ports, mounts or image once it exists, so container_edit and container_update have Dockhand stop it, remove it and make it again under the same name with the change, keeping everything not named: its networks, restart policy, limits and the rest. It is started again when it was running, and what it wrote inside itself, outside its volumes, is gone. container_limits is the exception: memory, processors and the restart policy are set on the container as it runs. A container that is Dockhand itself, or one of its agents, is refused by both: making it again through Dockhand stops the thing doing the work part way.

An update is a pull first, and nothing more when the pull brings nothing. container_updates says which containers have something newer to be had: another image under the very tag they run, and, when Dockhand is set to look for them, a newer version tag. container_update pulls the image and only makes the container again when what came is another image than it runs, so asking twice does no harm. container_autoupdate_set has Dockhand do the same by itself on a schedule, with nobody asked, which is right for something that should follow its tag and wrong for anything that must not restart unwatched.

A file written into a container is checked before it is written. Dockhand writes a file wherever it is told, and told to write one where a directory is, it puts the file in the directory's place and everything in the directory is gone. container_file_write, container_file_move and container_file_upload look at the path first: a directory is always refused, and anything else that is there is replaced only with overwrite. container_file_delete removes as rm -rf does, so it says what is there before it removes it, and refuses the container's root, a directory directly under it and the place a volume is mounted. A file written or sent in is root's afterwards, whatever it replaced, and container_file_perms gives it back. Writing, sending and fetching work on a stopped container too, which is how one that will not start for a bad file is mended; the rest run a command in the container and need it running.

What Dockhand does for a host unasked is set in one place, and said plainly. env_get shows a host's schedules beside the machine itself, and env_schedules sets them: when the registries are asked whether its containers are behind, when its images are pruned, when a filling disk is warned of. Two of them act on a whole host with nobody watching: auto_update pulls newer images and makes containers again on the schedule, and a scheduled prune of all removes every image nothing is using at that moment, a stopped stack's among them. Dockhand saves a schedule whatever its time says, and one it cannot read never runs, so a time that is not five cron fields is refused here. env_edit changes how a host is reached, and has Dockhand try the new way at once: a host that is no longer reached is one whose containers cannot be seen until it is put right.

A host Dockhand cannot reach is added as one whose agent calls in. env_create with connection: hawser-edge saves the host with nothing to try and answers the token its Hawser agent is to call in with, once; agent_list then says which agents are connected and which hosts are still waited for. A host has one token that works: agent_token_create replaces it and agent_token_delete revokes it, and either cuts off the agent that was connected. One thing Dockhand does here costs five minutes when it is not known: an agent that calls with a token Dockhand does not take is turned away, and so is every call from that agent's address for the five minutes after, the right token's among them. So stop an agent before its token is replaced; the tools say so before they do it.

A registry's login is kept by Dockhand, and used for the hosts. A host pulling an image whose name leads with a kept registry's address is signed in with what Dockhand keeps for it, and so is a push: image_push sends an image that is on one host to a registry, from where image_pull brings it to the others. registry_create and registry_edit try the registry and say whether Dockhand can use it. Two things a registry does are said before they bite: deleting one tag deletes every other tag of that image holding the very same contents, which registry_image_delete names once they are gone; and a registry that turns Dockhand's login away is reported as that, where Dockhand's own answer reads as if it had refused the caller.

What Dockhand does with nobody asking is one list, and each run of it is kept. schedule_list gives every schedule Dockhand keeps, whatever set it: a container kept on the newest image, a host's check for newer images, its image prune, a git stack's sync, a backup, and Dockhand's own tidying. schedule_history gives the runs, so status: failed is everything that went wrong overnight, and one run by its number comes with what it logged. A schedule is named by its type and its number together; the types are written with hyphens here (container-update, image-prune), since written Dockhand's way three of them are the names of tools. schedule_run does now what the schedule would do in its time, which for an update or a prune changes a host.

Who hears of what is decided in two places, and the tools say which. A channel is a way of telling people: mail, or an address of another service such as ntfy, Gotify, Discord, Slack, Telegram or a webhook. What happens on a host, a container dying or being updated, reaches a channel only through its tie to that host, which notification_link makes and which holds a list of events of its own; the channel's own list decides only the events of Dockhand itself. So a channel that is saved and tied to nothing is silent about every host, and notification_create says so. notification_test sends for real: through one channel, or by setting an event off for a host, which answers how many channels took it. An address is listed by its service and host alone, since the rest of it is the token that lets anyone send to it and Dockhand hands it back whole to whoever asks. And of Dockhand's own events only the one about its license can be set off for a test: the others it would send to nobody, so they are refused with the reason.

A host that scans nothing is not a host with nothing wrong. Dockhand scans images with grype or trivy, run as a container on the host that has the image, and a fresh Dockhand scans nothing anywhere. Asked to scan on a host set to scan with nothing, it answers that the scan is complete and found no vulnerabilities; image_scan and image_scan_all refuse instead, and vulnerability_list says for each host how many of its images were ever scanned, since an image never scanned has no findings. The list is the gravest first, counts every finding that matched, and narrows to what a newer version puts right, which is the list of what an update would mend. A scanner pulls its own image and downloads its database the first time, so a first scan takes a minute or more and comes back as a job when it outlasts the wait. Dockhand runs the scanners from Docker Hub unless told: scanner_set names another registry, for every host at once.

Dockhand will lock itself if asked, and these tools will not ask. Dockhand makes every user an administrator, and guards the last one only when it is the only user there is: with a second user of any kind it will switch off, unmark or remove the last administrator, and it will switch its login on over a server whose only user is switched off. Either can leave a Dockhand nobody can sign in to, with no call that undoes it. So user_edit and user_delete leave the last user that is both switched on and an administrator as it is, and login_set does not switch the login on unless such a user exists. Switching the login either way needs confirm, and without it the answer says what it would mean: off, anyone who can reach Dockhand's address has all of it and every host it manages; on, this server is let in only with an API token, which Dockhand lets nobody make except signed in to its web interface, so a server started with no token is turned away from that moment, and the answer says so (turned_away). token_list lists the tokens of the user this server signs in as, and no tool makes or revokes one. Taking the administrator's mark from a user holds it to nothing on a Dockhand with no licence, which user_edit says when it is asked to.

Settings are checked before they are sent, because Dockhand checks nothing. settings_get lists every setting of Dockhand itself by the name its API gives it, the ones that say which newer versions of an image it notes among them (semver.*), and settings_set changes the ones named. Dockhand saves a schedule it cannot read, after which that clean-up job never runs, and saves a time zone it does not know while answering that the save failed; it raises a number it finds too small and passes over a word it does not know, each without a word. So a schedule and a time zone are checked first, every setting is read back, and one Dockhand did not take as given is put back to what it was and named in not_taken: a setting ends as it was asked for or as it was. A Retention setting is what the clean-up jobs say they go by, so a smaller one has the next run remove more.

A template is a starting point, and the tools say where it falls short. Dockhand has about a thousand app templates as it comes, fetched from lists that others keep: template_list searches them and template_get gives one in full with the compose file Dockhand writes for it, which is text to read, change and make a stack from. Nothing is started by either. Dockhand writes undefined where a template names no place on the host for a volume, which is no volume, and template_get says so when it has. A stack template's compose file is fetched from its makers' repository, and the tool says which when that cannot be reached. Dockhand takes any address as a source of templates and says nothing when it answers nothing readable, so template_source_create and template_source_list count what came of each; it removes one of the sources it comes with as readily as any other, and nothing brings it back as its own, which template_source_delete says before it does. What Dockhand fetched from an address it keeps for an hour, with the name the source had then.

A tag belongs to a name, not to the thing that has it. Dockhand keeps the tags of a container and of a stack under its name on one host: a container that is renamed leaves its tags behind under the name it had, a container or a stack that is removed leaves them too, and whatever next has that name has them. tag_list gives each tag with the names it is on, and tag_assign refuses to tag a name nothing has, which Dockhand would do without a word, while letting what a name still holds be taken from it. It refuses a tag there is none of as well: Dockhand answers that with an error only after it has taken away every tag the name had, and should Dockhand refuse a change all the same, tag_assign puts back what was there. A tag is one of sixteen colours, and tag_create and tag_edit refuse any other, which Dockhand would make slate or quietly keep as it was.

The labels of hosts are plain words, and Dockhand is careless with them. prod and Prod are two labels. Asked to rename a label to the name it already has, Dockhand takes it from every host, so label_edit never sends that. Given a colour that is not # and six hexadecimal digits, Dockhand answers that it was set and takes away the colour the label had, so one is checked first. Renaming a label to the name of another makes the two one for good, and waits for merge. Given a label for a host it does not manage, Dockhand passes over it without a word, so label_assign refuses one.

What the web interface remembers is partly one user's and partly everyone's. preferences_get reads it and preferences_set changes it: the order hosts and tags are listed in, the sidebar, the columns of a grid and whether the schedules page hides Dockhand's own jobs are each user's own, here the user this server signs in as, while the dashboard with its tiles and the pages the interface opens on are everyone's. Dockhand saves an order or a tile that names a host or a tag there is none of, and any text as a layout, so those are looked up and checked first; a tag that is removed stays in the order it was saved in, and is shown by its number. The pages a user chose for themselves, and their theme and fonts, are kept with their profile, which only a browser that signed in may read or set.

The favourites of a log view are one list, and Dockhand's own reordering loses half of it. For each host Dockhand keeps the containers whose logs are at hand and the groups whose logs are read together in a single list, and asked to reorder the containers it drops every group, and asked to reorder the groups, every container. favorite_set therefore takes the whole list, in order, and writes it as one, and favorite_list reads it. A favourite is kept by a container's name and outlasts the container; a name no container has is refused unless it is kept already, since Dockhand keeps any text.

An image and a volume can be carried as files. image_export writes an image to a tar archive on the machine this server runs on, under whichever of its names is asked for, and image_load puts one on a host from such an archive: how an image reaches a host that cannot pull it. Dockhand loads only onto a host it reaches itself, and refuses one behind an agent. volume_export writes what a volume holds, or one folder of it, the same way. None of them ever writes over a file that is there. Two things Dockhand does are kept from biting: asked to export a volume there is none of, it makes the volume and answers an empty archive, so a volume is looked up first; and it says the helper container it read a volume through is gone before it is, which keeps the volume from being removed, so the volume tools wait until it really is.

A stack's secrets can be loaded from a secret store rather than kept by Dockhand. A secret provider is a store outside Dockhand - a Vault, or anything that speaks its API, among the kinds it knows - and a stack given one, with a path in it, gets every key at that path as a variable each time it is deployed, so the values are in no file. secret_provider_keys lists the names at a path, never the values, and stack_create and stack_edit take secret_provider and secret_path, asking the provider first whether anything is there. Dockhand is careless at three points, each of which the tools check: it saves a provider it cannot reach, and a stack bound to one is then not deployed, so a provider is tried first and stack_deploy says when a failed deploy has one behind it; it keeps the token when a provider's address is changed and sends it to the new address, so secret_provider_edit moves one only with its token given again; and it removes a provider that stacks use without a word, after which their next deploy succeeds with every such variable empty, so secret_provider_delete names those stacks and waits for even_used.

A stack can be deployed from a git repository, and then its files are the repository's. A git stack is a compose stack Dockhand fetches from a repository it keeps and copies into place at each deploy: git_stack_create makes one from a repository and a branch, with the variables and secrets Dockhand is to keep for it besides, and git_stack_deploy fetches and brings it up. It can be fetched by itself on a schedule, or when a git server calls its webhook, which takes the webhook's secret and no login. Since each deploy replaces the stack's files, stack_edit refuses a git stack and git_stack_edit changes what Dockhand keeps for it. Dockhand is careless at six points, each of which the tools check. It gives the webhook's secret back to whoever reads the stack, so git_stack_get hides it unless asked; and the call that deploys and waits answers the values of the stack's secrets with how it went, so the tools use the other one. It saves a repository it cannot fetch and a branch that is not there, and says so only at the next deploy, so both are tried first. A fetch alone marks a commit as seen, after which the schedule and the webhook find nothing new and never deploy it: git_stack_sync says so, and git_stack_get tells the commit fetched from the one that runs. It renames a stack that is running and leaves its containers behind under the old name, and a rename it refuses because the name is taken is half done all the same, after which the next rename takes the other stack's variables and secret provider: git_stack_edit renames only a stack that is down, to a name nothing has. And it removes a credential repositories are fetched with, and a repository git stacks are made from, without a word, the second taking those git stacks with it and leaving their containers running: git_credential_delete and git_repository_delete name what uses each and wait for even_used. A repository's own fetch, deploy and webhook are leftovers with no tool: they act on a copy no git stack uses, and that deploy runs compose on the Docker that Dockhand itself is on.

A config set is only kept. It is a bundle of settings for a container - variables, labels, published ports, mounts, a network mode and a restart policy - and nothing in Dockhand's API applies one: config_set_list gives a set in the words container_create takes, to be handed to it. Dockhand checks nothing of a set but its name, so config_set_create and config_set_edit check its ports, mounts, names and restart policy first. A value whose name says it is a secret is hidden unless reveal_secrets is given, and config_set_edit changes a variable at a time, so that one that is hidden need not be known to be kept.

An icon is a small thing that Dockhand keeps carelessly. icon_set gives a container, a stack or a host an icon: one of Dockhand's own by name, one of selfh.st's as icon_search finds them, or a picture sent from this machine, which icon_download fetches back. Dockhand saves any text as a name and, for a host, any file as a picture, so both are checked first. A container's and a stack's icon is kept under its name, as tags are, and Dockhand keeps one for a name nothing has; for a stack it then lists a stack of that name with nothing in it, so a name that is no container's or stack's is refused. And giving an icon to a stack Dockhand only found makes it keep a record of that stack for good, after which Dockhand's own adoption makes a second stack of the file: icon_set wants even_found for that, and stack_adopt gives such a stack its file another way.

Slow work does not hold a session up. Dockhand runs a pull, a prune, a deploy or a down in the background, and a tool that starts one waits for it only so long: 50 seconds unless wait says otherwise, 300 at most. Done by then, the answer is what was done, read back from the host. Still going, the answer says so and gives a job id, and job_get says how it is going and, in the end, how it went.

A stack is either one Dockhand has the compose file of, or one it only found. A stack someone brought up on a host with docker compose shows up in Dockhand all the same, and Dockhand can start, stop and restart its containers, but it has no file for it: stack_get says kept: found and where the host says the stack came from, and stack_deploy, stack_edit and stack_move refuse it. stack_adopt gives Dockhand the file where it lies, which has to be a path Dockhand's own container can see; nothing running is touched, and from then on the stack is deployed from that file. It is not undone lightly: Dockhand cannot be made to forget a stack again without removing its containers. A stack Dockhand only found that was then given an icon is one it keeps a record of, with still no file: stack_get says kept: external, and it is adopted and removed as a found one is.

Three things Dockhand does without a word are refused here unless asked for. Making a stack under a name that is taken replaces the file Dockhand has for that name, or takes over a stack it only found, so stack_create refuses a name any stack on the host has. Moving a stack's files into a folder that is already there writes over files of the same name in it, so stack_move refuses one unless overwrite is given. And a compose file that does not parse is saved as it is, and is then a stack that cannot be deployed or, unforced, removed, so stack_create and stack_edit run Dockhand's own check first and refuse a file compose would not take.

Removing a stack removes its containers, running or not. For a stack Dockhand keeps in its own data directory the files go too, unless keep_files is given; files outside it, of a stack that was adopted or moved, are never deleted. Named volumes stay unless volumes is given. For a stack Dockhand only found, the containers are all that goes. stack_down is the gentler one: the containers and the networks go, the file and the variables stay, and stack_deploy brings it back; its remove_volumes deletes data and so needs --enable-delete as well.

Choosing which tools load

The default is core and audit: 21 read-only tools, about 6,000 tokens, enough to see what runs where and to look for what is wrong. Every tool's definition is spent from a client's context before anyone asks a question, so --toolsets / DOCKHAND_TOOLSETS loads the groups a session needs, and core comes along with whatever else is asked for, because nothing else can say which hosts there are.

toolset tools with core ~tokens
core (default) 8 8 2,600
audit (default) 13 21 6,000
manage 7 15 4,100
containers 15 23 8,300
files 9 17 5,900
updates 5 13 4,500
stacks 15 23 8,700
resources 25 33 10,900
setup 9 17 6,800
schedules 6 14 4,700
registries 9 17 5,500
notifications 7 15 5,800
vulnerabilities 9 17 6,000
users 11 19 6,200
settings 8 16 5,800
secrets 6 14 4,700
git 17 25 9,600
templates 10 18 6,000
tags 14 22 7,300
all 203 203 71,900

Tokens are what the model sees: each tool's name, description and input schema, measured over a real tools/list at four bytes a token. Every tool also carries an output schema, another 64,600 tokens across all, but clients keep that to themselves to validate results rather than sending it to the model.

--toolsets also takes a resource family - server, env, agent, schedule, registry, notification, scanner, vulnerability, user, login, token, sso, settings, preferences, favorite, secret, template, config, tag, label, icon, container, stack, image, volume, network, job - which is every tool with that prefix. containers is one container looked into, made and changed, files is what is inside one, and updates is keeping them on current images; container is every container tool from those three, manage and core, 40 tools and about 14,100 tokens. stacks is a stack as the compose file it is, where manage starts and stops what is already there; stack is every stack tool from both, about 8,500 tokens with core. resources is the three kinds of thing a host holds besides its containers, and each is 5,000 to 7,000 tokens with core when asked for alone:

DOCKHAND_TOOLSETS=all DOCKHAND_ENABLE_WRITE=true      # every tool
DOCKHAND_TOOLSETS=manage DOCKHAND_ENABLE_WRITE=true   # core, and starting, stopping and restarting
DOCKHAND_TOOLSETS=containers,files DOCKHAND_ENABLE_WRITE=true  # core, and making and changing containers and their files
DOCKHAND_TOOLSETS=updates DOCKHAND_ENABLE_WRITE=true  # core, and seeing what is behind its image and updating it
DOCKHAND_TOOLSETS=resources DOCKHAND_ENABLE_WRITE=true  # core, and images, volumes and networks
DOCKHAND_TOOLSETS=image DOCKHAND_ENABLE_WRITE=true    # core, and images alone
DOCKHAND_TOOLSETS=setup DOCKHAND_ENABLE_WRITE=true    # core, and a host in full, adding one and setting its schedules
DOCKHAND_TOOLSETS=schedules                           # core, and what Dockhand does by itself and how each run went
DOCKHAND_TOOLSETS=registries DOCKHAND_ENABLE_WRITE=true  # core, and the registries Dockhand keeps, what is in them, and pushing to one
DOCKHAND_TOOLSETS=notifications DOCKHAND_ENABLE_WRITE=true  # core, and who Dockhand tells of what, with a test of each
DOCKHAND_TOOLSETS=vulnerabilities                     # core, and what the scans found wrong in the images of every host
DOCKHAND_TOOLSETS=users DOCKHAND_ENABLE_WRITE=true    # core, and who may sign in: users, the login and single sign-on
DOCKHAND_TOOLSETS=settings DOCKHAND_ENABLE_WRITE=true # core, and the settings of Dockhand itself, with what its web interface remembers
DOCKHAND_TOOLSETS=secrets DOCKHAND_ENABLE_WRITE=true  # core, and the secret stores a stack's secrets are loaded from
DOCKHAND_TOOLSETS=git DOCKHAND_ENABLE_WRITE=true      # core, and the stacks deployed from git, with their repositories and credentials
DOCKHAND_TOOLSETS=templates                           # core, and the app templates Dockhand has, with the compose file for each, and its config sets
DOCKHAND_TOOLSETS=tags DOCKHAND_ENABLE_WRITE=true     # core, and the tags of containers and stacks, the labels of hosts and the icons of all three
DOCKHAND_TOOLSETS=container                           # core plus one whole family, reads only

dockhand-mcp tools prints what the current flags would register, grouped by toolset, and needs no server:

dockhand-mcp tools                               # the default set
dockhand-mcp tools --toolsets all --enable-write # every tool
dockhand-mcp tools -q                            # names only
One tool more, or a few less

--allow-tools asks for tools by name, as --toolsets asks for them by set, and a session gets what either names: "these sets, and this tool as well". On its own, with no toolset beside it, it is only the tools it names. --deny-tools takes tools out of whatever was asked for, which is how a toolset is narrowed. Both take comma-separated tool names and globs with a leading or trailing *, and --allow-tools the essential preset. Neither lets a tool that changes something past --enable-write.

DOCKHAND_ALLOW_TOOLS=essential                                             # the four essential tools and nothing else
DOCKHAND_ENABLE_WRITE=true DOCKHAND_TOOLSETS=core DOCKHAND_ALLOW_TOOLS=container_restart   # core, and restarting a container as well
DOCKHAND_ENABLE_WRITE=true DOCKHAND_TOOLSETS=manage DOCKHAND_DENY_TOOLS=*_stop   # start and restart, never stop
Finding what is not right

The audits look for it. They load by default and only read.

audit_all                             every audit over every host, counted: where to look
audit_health                          what is broken now, each with why: the health check's words, the exit code
audit_healthcheck                     what could never be found unhealthy, because nothing checks it
audit_security env=<host>             what was given more of its host than a container needs

And one container looked into, with core:

container_list problems=true          every container that is unhealthy, restarting or exited with an error, on every host
container_get container=<name>        why: what its health check printed, its exit code, whether the host killed it for memory
container_logs container=<name>       what it wrote before it stopped
stack_logs stack=<name>               what its neighbours wrote around the same time
container_restart container=<name>    once the cause is dealt with

Using the client on its own

sdk/dockhand is a plain Go client for the Dockhand API with no knowledge of MCP. It is generated by pandorest from the OpenAPI document every Dockhand serves about itself, and sends its requests through pandorest's base client, which is built on go-kt's HTTP package. If you only want to talk to Dockhand from Go, take it and ignore the rest:

import "github.com/katbyte/dockhand-mcp/sdk/dockhand"

client, err := dockhand.New("http://dockhand:3000", os.Getenv("DOCKHAND_TOKEN"))
environments, err := client.GetEnvironments(ctx)
containers, err := client.GetContainers(ctx, dockhand.GetContainersOptions{Env: &environments.Answer[0].Id})

dockhand.NewWithoutLogin makes a client for a Dockhand with its login switched off. A read is asked for again when a gateway could not reach the server (502, 503, 504) or the connection dropped, three times in all; a write is sent once; a redirect is refused rather than followed. It logs nothing unless handed a logger with client.WithLog, and then traces every request and answer with tokens, passwords and keys blanked: dockhand-mcp hands it its own, so DOCKHAND_LOG=trace shows the traffic. A status a call does not document is a *client.StatusError carrying what Dockhand said; client.IsNotFound, IsUnauthorized and IsForbidden read the common ones.

It has a method for every one of the 395 operations in Dockhand's document, named for its method and path: GET /api/stacks/{name}/compose is GetStacksByNameCompose. make apicheck fails if one is missing, so the claim is checked rather than asserted.

That document comes from Dockhand itself: every Dockhand server serves a description of its own API at /openapi.json, and api-defs/get.sh starts a throwaway server of the published image, asks it for that, and removes it again. Nothing is taken from Dockhand's source repository. The document is Dockhand's own, so it is not kept in this repository: what is committed is a pin (the version, the image and a checksum of the document's content), the definitions imported from the document, and the client generated from those. A document whose checksum is not the pinned one is refused, and make fetches it before anything reads it.

Three things the document never says are declared here and checked against a real server: which fields are dates (read in each of the forms Dockhand writes one in), which take a value from a fixed set (a container's state, a job's status, how an environment is reached: 24 sets, each a type with its values), and which ids are of which record (12 kinds, so an environment's number handed to a call that wants a registry's does not compile). A true or false in an answer is a plain bool; one in a body that is sent stays unset until it is set, so that a change to one setting never sends the others.

Dockhand writes that document from its own code, and what a call answers is the part it gets least right: a quarter of the calls say nothing of their answer and others declare a fraction of it. Each such fault that has been found is fixed with a named workaround (sdk/pandorest/workarounds), proved against a running server, and written up in api-defs/README.md beside what the server does that no document says. An answer no workaround has given a shape yet is handed back as raw JSON.

Development

make            # fmt + build
make check-all  # build + unit tests + both live suites (needs docker) + every linter
make generate   # import Dockhand's document and regenerate sdk/dockhand
Tests

make test is fast, and hermetic once the API document has been fetched, which it does first when the document is not on disk and which needs Docker that once. It covers the generated client, each method against a canned server generated with it; that importing and generating afresh reproduces what is checked in, byte for byte (sdk/pandorest/main_test.go); what is Dockhand's own about being talked to, including that a trace holds no token, password or key (sdk/dockhand/service_test.go); and the tools end to end over an in-memory MCP session against a canned Dockhand (sdk/dockhandtest), which holds every request the tools make against the API's definitions.

Everything else runs against a real Dockhand in Docker, because a stub can only confirm what you already believed. .scripts/testenv.sh starts a fresh Dockhand, two Docker hosts of its own for it to manage, each a Docker inside a container that nothing else ever sees, a real Hawser agent in front of the second, a registry with a login for the hosts to push to and pull from, which is also where the test notifications are sent, a secret store that speaks Vault's API (OpenBao, started empty and in memory) for Dockhand to load a stack's secrets from, and a small git server (.scripts/gitserver.js, run from the Dockhand image itself, which already carries git) that serves the repositories the suites commit to, to anyone and behind a login. A list of templates is served to Dockhand from a small container on the first host. The scanner the scans are run with, and an old image for it to find fault with, come from a mirror. Nothing is pointed at the Docker of the machine the tests run on.

Covers Command
acceptance/dockhand/ the sdk/dockhand client: the setup of a fresh server, environments reached each way, an agent that calls in with its token made, replaced and revoked, what a host's disk holds, Dockhand's own filesystem, schedules listed, run, switched and removed with their runs, a registry saved, pushed to, browsed, pulled from and removed, a notification channel tried, saved, tied to a host, set off and removed, a host set to scan its images, one scanned both ways a scan is answered, its findings read back, narrowed and written out, every image scanned at once and the scanner removed, a user made, changed, switched off and removed with what each does to its session and its token, the login set and switched off and on, a single sign-on provider saved, offered and removed, the settings saved, mangled and put back, a source of templates added, its templates read and written out as compose files, and the source switched off and removed, tags made, changed, given to a container and a stack by name and removed, with what a renamed and a removed container leave behind and what a refused change takes, the labels of hosts given, coloured, renamed, made one and removed, config sets saved, changed and removed, a container, a stack and a host given an icon by name and as a picture, fetched and removed, with the record an icon leaves of a stack Dockhand only found and what that does to adopting it, the icons of selfh.st listed and fetched, the orders, sidebar, grid columns, dashboard and pages of the web interface saved and forgotten, with which of them are one user's own, the favourites of a log view kept as one list of names and groups and what Dockhand's own reordering drops from it, a secret provider tried, saved, asked what it holds, moved and removed, with the token it keeps through a change of address, and a stack given its secrets by one, refused a deploy while it is out of reach and deployed with them empty once it is gone, an image written out by its id under a name asked for, gzipped and from behind the agent, and loaded again, a volume written out whole, in part and as one file, with the helper each export leaves and the volume Dockhand makes when asked for one there is none of, a git credential saved, changed and removed while a repository uses it, a repository tried, saved, asked for its branches and variables, fetched, changed and removed, with what its own deploy and webhook are, a git stack made, fetched, deployed both ways, changed and forgotten, on the socket's host and behind the agent, with its webhook called with no login, the commit a fetch alone hides from it, the rename a refusal leaves half done, and the containers left when the stack or its repository is forgotten, every read the API has, images, volumes and networks made, changed, removed and pruned, and stacks made, read, changed, deployed, taken down, adopted, moved and removed, waited for and as jobs make testacc-sdk
acceptance/tools/ the tools: every one of them, the journeys that change things and read them back, and the built binary make testacc-tools
make testacc        # both, each on its own throwaway server and hosts, torn down after
make cover          # all three suites, merged into one coverage number

Each suite runs twice on the same server, because a Dockhand is run two ways: open, as it comes, with no login; and with its login on, which the suite switches on itself, through the client, making the first user and minting the API token it then asks with.

The read sweep (acceptance/dockhand/sweep_test.go, pandorest's) calls every GET the API has against the server and holds each answer to decoding into its model with something in it. A read that cannot simply answer has a case that says why (acceptance/dockhand/sweep_cases_test.go): a stream of events that never ends, a feature of Dockhand's paid edition, something a server as it comes has none of. A case whose call starts answering fails the sweep, so the list cannot go stale.

The journeys (acceptance/tools/journey_test.go) chain the tools the way a session does. The first is the suite's own setup: a Dockhand that manages nothing takes on its two hosts through env_test and env_create, one by its socket and one through its agent. Then an agent tried with a wrong token and turned away, added, read through, and forgotten again with what runs behind it untouched; a host added as one whose agent calls in, reached once an agent the suite starts with its token does, cut off when the token is replaced and when it is revoked, and forgotten with its tokens; a container's update schedule found among what Dockhand does by itself, run before its time, read back with what it logged, switched off and on and removed; a registry tried and saved, an image pushed to it from one host, found there with its tags and pulled by the other, the registry renamed, given a wrong password and its login forgotten, a tag deleted with its twin, and the registry forgotten; a way of telling people tried and saved, tied to a host for two events that are set off and counted, pointed at a service that turns messages away, and removed with its tie; a host that scans nothing given a scanner, an old image on it scanned, what was found read back from the scan and from the host's findings and written out, every image scanned at once, and the scanner taken off again; a user made, renamed, switched off and on and removed, with the last administrator refused all three; the login switched off and on again by the tools with their token still taken, and on an open server switched on, which turns the tools away, and off again by the user they made; a single sign-on provider saved, offered at sign-in, marked the default and removed; the settings changed, a number that is 0 and a list among them, and what Dockhand would not take put back; a source of templates added from a list served on the test network, its templates found and one written out as a compose file, and the source switched off and removed; two tags made and given to a container and a stack, one renamed with everything that has it keeping it, the container and a stack removed and their names seen to hold their tags still, and the tags removed; a label given to both hosts, coloured, renamed, made one with another, taken from each host in turn and removed; a config set saved with a token among its variables, read with it hidden, changed a variable at a time with the token kept, and removed; an icon found among selfh.st's and given to a container, a picture sent for it, fetched back and removed, a host given a name and a picture, and a stack Dockhand only found given an icon when that is asked for, then adopted and removed all the same; the web interface arranged, its orders, sidebar, a grid's columns, the dashboard with its tiles and the two pages, changed a part at a time and put back as it came; the favourites of a host's log view set as a container and a group, turned round, kept after a container is gone and cleared; a secret store tried and saved as a provider, a stack made that loads a path of it and runs with what is there, the provider moved out of reach and the deploy that fails for it saying why, and the provider removed only once the stack it leaves empty-handed has been named, with no answer on the way holding the token or a secret; an image given a name of its own, written to a file, and given the name back by loading the file once it had lost it, with the load refused for a host behind an agent, and a volume written out whole and in part and then removed; a stack deployed from git, from a credential and a repository tried and saved to a git stack made and seen to run with what the repository, Dockhand's own variables and a secret give it, a new commit seen, fetched and deployed, the stack changed, renamed once it is down and forgotten with its container left running, and the repository and credential removed only once nothing uses them, with no answer on the way holding a password or a secret; a container and a stack found, stopped, read while stopped, started and restarted, twice over and on both hosts, each change read back from the host itself; a container's secrets hidden until they are asked for; an unhealthy container and one that exited asked why; and an image pulled to the host behind the agent as a job that is followed to its end, pulled again to no change, given a second name, removed with both, and pulled once more and pruned away; a volume made, filled, looked into and read through Dockhand's helper with a key in it hidden, copied, kept from removal while a container mounts it, and removed and pruned once nothing does; and a network made behind the agent with a range of its own, joined and left by a container, and shown to be neither removed nor pruned while a stopped container is on it. Every step is held against what the host's own Docker says it has. The audits have their own (acceptance/tools/audit_test.go), each one find, fix, find no more: from audit_all's counts to the audit a count names, to the finding, to the container it is about; a stack found partly up and then down, started by the name the finding gave, and found whole; a volume and a network nothing uses found, removed and gone from the audit; and a container found with no memory limit and no restart policy, given both, and the counts one fewer. The update and scan journeys end in the audits too: what a check noted is what audit_updates finds, and what a scan found is what audit_vulnerabilities counts.

acceptance/tools/binary_test.go builds the real binary and speaks to it as a client does, which the rest never touches: serve over stdio saying nothing on stdout but the protocol, with no token in what it logs; the flags, the environment and the configuration file in the working directory registering what they say they do; serve --listen over HTTP behind its bearer token, shutting down when asked with a client still connected; a start that cannot be made refused with the reason; a token that is missing or wrong said by the first tool, in words to act on; and the version, tools and info commands.

Documentation

Overview

Package main implements dockhand-mcp, an MCP server and CLI for the containers and stacks a Dockhand manages.

Directories

Path Synopsis
Package cli implements the dockhand-mcp command line interface: the cobra commands, flag and config handling, and the MCP server exposing Dockhand's tools to AI clients.
Package cli implements the dockhand-mcp command line interface: the cobra commands, flag and config handling, and the MCP server exposing Dockhand's tools to AI clients.
lib
testenv
Package testenv is what the two live suites share: the Dockhand and the Docker hosts .scripts/testenv.sh started for them, and which of the two ways a Dockhand is run they stand for - open, as it comes, with no login, or with its login switched on and asked with an API token.
Package testenv is what the two live suites share: the Dockhand and the Docker hosts .scripts/testenv.sh started for them, and which of the two ways a Dockhand is run they stand for - open, as it comes, with no login, or with its login switched on and asked with an API token.
sdk
dockhand
Package dockhand is a typed client for Dockhand API 1.0.51, generated by pandorest from the definitions in api-defs/dockhand-1.0.51, which were imported from api-defs/dockhand-openapi-1.0.51.json.
Package dockhand is a typed client for Dockhand API 1.0.51, generated by pandorest from the definitions in api-defs/dockhand-1.0.51, which were imported from api-defs/dockhand-openapi-1.0.51.json.
dockhandtest
Package dockhandtest is a canned Dockhand for tests: it answers the API calls a test tells it to and records what it was asked.
Package dockhandtest is a canned Dockhand for tests: it answers the API calls a test tells it to and records what it was asked.
pandorest command
Command pandorest generates sdk/dockhand from the Dockhand API document, with github.com/katbyte/pandorest: the document is imported into definitions under api-defs, fixing what it gets wrong on the way (see workarounds), and the client is written from those.
Command pandorest generates sdk/dockhand from the Dockhand API document, with github.com/katbyte/pandorest: the document is imported into definitions under api-defs, fixing what it gets wrong on the way (see workarounds), and the client is written from those.
pandorest/workarounds
Package workarounds is what is dockhand-mcp's own about generating its client: the one service pandorest generates here, and a named fix for each thing Dockhand's API document gets wrong.
Package workarounds is what is dockhand-mcp's own about generating its client: the one service pandorest generates here, and a named fix for each thing Dockhand's API document gets wrong.
Package tools defines the MCP tools exposed by dockhand-mcp, split by the resource they act on (server.go, env.go, ...).
Package tools defines the MCP tools exposed by dockhand-mcp, split by the resource they act on (server.go, env.go, ...).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL