TypeScript MCP server for the Arcane Docker management API. Exposes a single arcane tool that routes all Docker environment, project, container, image, network, volume, system, registry, image-update, vulnerability, and GitOps operations through the Arcane service.
Arcane MCP wraps the Arcane REST API behind a Model Context Protocol interface. An AI agent calls the arcane tool with an action and subaction. The server authenticates the request, enforces a confirmation gate on destructive operations, routes to the appropriate backend service, and returns JSON.
The server ships two MCP tools:
| Tool | Purpose |
|---|---|
arcane |
Unified action/subaction router for all Arcane API operations |
arcane_help |
Returns the full action/subaction reference as formatted text |
Call this tool with action, subaction, and optional envId, id, and params.
{
"action": "container",
"subaction": "list",
"envId": "env-abc123"
}| Parameter | Type | Description |
|---|---|---|
action |
string (enum) | Resource family: environment, project, container, image, network, volume, system, image-update, vulnerability, registry, gitops |
subaction |
string (enum) | Operation to perform (see per-family tables below) |
envId |
string (optional) | Target environment ID. Required by all families except registry. For environment subactions, envId and id are interchangeable for single-resource ops. |
id |
string (optional) | Resource ID for single-resource operations (get, delete, etc.) |
params |
object (optional) | Operation-specific payload. Pass { "confirm": true } to authorize a destructive operation without elicitation. |
Any operation in the DESTRUCTIVE set (listed per family below) is blocked unless one of three conditions is met:
ARCANE_MCP_ALLOW_DESTRUCTIVE=true— all destructive ops auto-confirm, no re-call needed.ARCANE_MCP_ALLOW_YOLO=true— skips the interactive elicitation prompt; the tool must still be re-called withparams: { "confirm": true }to proceed.- Default — if the MCP client supports elicitation forms, the server prompts the user interactively. If the client does not support elicitation, the server returns a prompt asking for a re-call with
params: { "confirm": true }.
Top-level Docker host connections. envId and id are interchangeable for single-resource operations.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all environments | |
get |
Get an environment by ID | |
create |
Create a new environment; pass params with apiUrl, apiKey, and optional name, enabled, isEdge |
|
update |
Update environment settings; pass params with fields to change |
|
delete |
Permanently delete an environment | yes |
test |
Test the environment's API connection |
Example:
{"action": "environment", "subaction": "list"}
{"action": "environment", "subaction": "get", "id": "env-abc123"}
{"action": "environment", "subaction": "create", "params": {"apiUrl": "https://host.example.com", "apiKey": "..."}}
{"action": "environment", "subaction": "delete", "id": "env-abc123", "params": {"confirm": true}}Docker Compose stacks running inside an environment. Requires envId.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all projects in the environment | |
get |
Get a project by ID | |
create |
Create a project; pass params with name, composeContent, and optional envContent |
|
update |
Update a project; pass params with composeContent, envContent, or name |
|
up |
Start a project (docker compose up) | |
down |
Stop a project (docker compose down) | yes |
restart |
Restart all containers in the project | yes |
pull |
Pull latest images without restarting | |
destroy |
Stop and remove containers, networks, volumes created by the compose file | yes |
redeploy |
Pull images and recreate the project | yes |
build |
Build images for the project; pass optional params with services, provider, push, load |
Example:
{"action": "project", "subaction": "list", "envId": "env-abc123"}
{"action": "project", "subaction": "up", "envId": "env-abc123", "id": "my-stack"}
{"action": "project", "subaction": "down", "envId": "env-abc123", "id": "my-stack", "params": {"confirm": true}}
{"action": "project", "subaction": "build", "envId": "env-abc123", "id": "my-stack", "params": {"services": ["web"], "push": true}}Individual Docker containers inside an environment. Requires envId.
Note: container logs are not available via the Arcane REST API. Use the Arcane web UI or docker logs directly.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all containers | |
get |
Get a container by ID | |
create |
Create a container; pass params with name, image, and optional cmd, env, ports, volumes, restartPolicy, labels, memory, cpus, privileged |
|
start |
Start a stopped container | |
stop |
Stop a running container | yes |
restart |
Restart a container | yes |
update |
Re-pull the container's image and recreate it using its existing config. No params accepted. | |
delete |
Remove a container | yes |
stats |
Get CPU, memory, and network stats for all containers in the environment |
Example:
{"action": "container", "subaction": "list", "envId": "env-abc123"}
{"action": "container", "subaction": "stats", "envId": "env-abc123"}
{"action": "container", "subaction": "stop", "envId": "env-abc123", "id": "container-id", "params": {"confirm": true}}
{"action": "container", "subaction": "create", "envId": "env-abc123", "params": {"name": "nginx", "image": "nginx:latest", "ports": {"80/tcp": [{"HostPort": "8080"}]}}}Docker images available in an environment. Requires envId.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all images | |
get |
Get an image by ID | |
pull |
Pull an image; pass params with imageName and optional tag |
|
delete |
Delete an image by ID | yes |
prune |
Remove all unused images | yes |
scan |
Scan an image for vulnerabilities (triggers Trivy scan) |
Example:
{"action": "image", "subaction": "list", "envId": "env-abc123"}
{"action": "image", "subaction": "pull", "envId": "env-abc123", "params": {"imageName": "nginx", "tag": "latest"}}
{"action": "image", "subaction": "scan", "envId": "env-abc123", "id": "sha256:abc..."}
{"action": "image", "subaction": "prune", "envId": "env-abc123", "params": {"confirm": true}}Docker networks in an environment. Requires envId.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all networks | |
get |
Get a network by ID | |
create |
Create a network; pass params with name and options (driver, internal, enableIPv6, labels) |
|
delete |
Delete a network by ID | yes |
prune |
Remove all unused networks | yes |
Example:
{"action": "network", "subaction": "list", "envId": "env-abc123"}
{"action": "network", "subaction": "create", "envId": "env-abc123", "params": {"name": "my-net", "options": {"driver": "bridge"}}}
{"action": "network", "subaction": "prune", "envId": "env-abc123", "params": {"confirm": true}}Docker volumes in an environment, with full backup and restore support. Requires envId.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all volumes | |
get |
Get a volume by name | |
create |
Create a volume; pass params with name and optional driver, driverOpts, labels |
|
delete |
Delete a volume by name | yes |
prune |
Remove all unused volumes | yes |
browse |
Browse a volume's directory tree; pass optional params.path (relative, no .. allowed) |
|
list-backups |
List all backups for a volume; requires id (volume name) |
|
create-backup |
Create a new backup snapshot; requires id (volume name) |
|
delete-backup |
Delete a backup by ID; pass params.backupId |
yes |
restore |
Restore a volume to a backup state; requires id (volume name) and params.backupId |
yes |
restore-files |
Restore specific files from a backup; requires id, params.backupId, and params.paths (string array) |
yes |
- List available backups:
subaction=list-backups,id=<volumeName> - Create a new snapshot:
subaction=create-backup,id=<volumeName> - Inspect a backup's contents: use
list-backupsto get the backup ID, thenrestore-fileswithparams.paths - Full restore:
subaction=restore,id=<volumeName>,params: { backupId: "...", confirm: true } - Partial restore:
subaction=restore-files,id=<volumeName>,params: { backupId: "...", paths: ["data/config.json"], confirm: true } - Remove old snapshot:
subaction=delete-backup,params: { backupId: "...", confirm: true }
Example:
{"action": "volume", "subaction": "list-backups", "envId": "env-abc123", "id": "my-data-vol"}
{"action": "volume", "subaction": "create-backup", "envId": "env-abc123", "id": "my-data-vol"}
{"action": "volume", "subaction": "restore", "envId": "env-abc123", "id": "my-data-vol", "params": {"backupId": "bkp-xyz", "confirm": true}}
{"action": "volume", "subaction": "restore-files", "envId": "env-abc123", "id": "my-data-vol", "params": {"backupId": "bkp-xyz", "paths": ["etc/app.conf"], "confirm": true}}
{"action": "volume", "subaction": "browse", "envId": "env-abc123", "id": "my-data-vol", "params": {"path": "etc"}}Docker daemon-level operations for an environment. Requires envId.
| Subaction | Description | Destructive |
|---|---|---|
docker-info |
Get Docker daemon info (version, runtime, resources) | |
start-all |
Start all projects in the environment | |
stop-all |
Stop all projects in the environment | yes |
prune |
Prune unused Docker resources; pass params with boolean flags: containers, images, networks, volumes, buildCache, dangling |
yes |
convert |
Convert a docker run command to Docker Compose YAML; pass params.dockerRunCommand |
Example:
{"action": "system", "subaction": "docker-info", "envId": "env-abc123"}
{"action": "system", "subaction": "prune", "envId": "env-abc123", "params": {"containers": true, "images": true, "networks": true, "volumes": false, "buildCache": true, "dangling": true, "confirm": true}}
{"action": "system", "subaction": "convert", "envId": "env-abc123", "params": {"dockerRunCommand": "docker run -d -p 80:80 nginx"}}Check whether images have newer versions available. Requires envId. Long-running checks use a 120-second timeout.
| Subaction | Description |
|---|---|
check-all |
Check all images in the environment for updates; pass optional params.credentials for private registries |
check |
Check a single image; pass either id (Arcane image ID) or params.imageRef (e.g. nginx:latest) |
check-batch |
Check multiple images; pass params.imageRefs (string array) and optional params.credentials |
summary |
Get a summary count: total images, images with updates, digest-only updates, errors |
The check subaction resolves the target in this order: id (Arcane internal imageId) → params.imageRef (image reference string). Passing both is undefined behavior; use one or the other.
Example:
{"action": "image-update", "subaction": "summary", "envId": "env-abc123"}
{"action": "image-update", "subaction": "check", "envId": "env-abc123", "params": {"imageRef": "nginx:latest"}}
{"action": "image-update", "subaction": "check", "envId": "env-abc123", "id": "img-abc123"}
{"action": "image-update", "subaction": "check-batch", "envId": "env-abc123", "params": {"imageRefs": ["nginx:latest", "redis:7"]}}
{"action": "image-update", "subaction": "check-all", "envId": "env-abc123"}Image vulnerability scanning powered by Trivy. Requires envId.
| Subaction | Description |
|---|---|
summary |
Get counts by severity (critical, high, medium, low, unknown) across all scanned images |
list |
List all vulnerability entries with full CVE detail |
scanner-status |
Get the Trivy scanner's current operational status |
ignore |
Add a vulnerability to the ignore list; pass params with imageId, vulnerabilityId, pkgName, and optional installedVersion, reason |
unignore |
Remove an ignore entry; pass id (ignoreId) |
list-ignored |
List all active ignore entries |
Example:
{"action": "vulnerability", "subaction": "summary", "envId": "env-abc123"}
{"action": "vulnerability", "subaction": "list", "envId": "env-abc123"}
{"action": "vulnerability", "subaction": "ignore", "envId": "env-abc123", "params": {"imageId": "img-abc", "vulnerabilityId": "CVE-2024-1234", "pkgName": "openssl", "reason": "mitigated"}}
{"action": "vulnerability", "subaction": "unignore", "envId": "env-abc123", "id": "ignore-entry-id"}Container registry credentials. Global resource — envId is not required and is ignored.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all configured registries | |
get |
Get a registry by ID | |
create |
Add a registry; pass params with url, username, token, and optional description, enabled, insecure |
|
update |
Update a registry; pass id and params with fields to change (url, username, token, description, enabled, insecure) |
|
delete |
Remove a registry | yes |
test |
Test connectivity to the registry |
Example:
{"action": "registry", "subaction": "list"}
{"action": "registry", "subaction": "create", "params": {"url": "registry.example.com", "username": "myuser", "token": "...", "enabled": true}}
{"action": "registry", "subaction": "test", "id": "reg-abc123"}
{"action": "registry", "subaction": "delete", "id": "reg-abc123", "params": {"confirm": true}}Git-backed Compose deployment syncs. Requires envId.
Warning: gitops:sync pulls from a remote repository and applies changes. This is a supply chain risk if the repository is compromised. Always confirm the sync target before proceeding.
| Subaction | Description | Destructive |
|---|---|---|
list |
List all GitOps sync configurations | |
get |
Get a sync configuration by ID | |
create |
Create a sync config; pass params with name, repositoryId, branch, composePath, and optional autoSync, syncInterval, projectName |
|
update |
Update a sync config; pass id and params with fields to change |
|
delete |
Delete a sync configuration | yes |
sync |
Trigger an immediate sync from the remote repository | yes |
status |
Get the sync's last run status, commit, and next scheduled run | |
browse |
Browse the repository's file tree; pass optional params.path |
- List existing syncs:
subaction=list - Check a sync's state:
subaction=status,id=<syncId> - Inspect repository contents:
subaction=browse,id=<syncId> - Trigger a sync:
subaction=sync,id=<syncId>,params: { confirm: true }
Example:
{"action": "gitops", "subaction": "list", "envId": "env-abc123"}
{"action": "gitops", "subaction": "status", "envId": "env-abc123", "id": "sync-abc123"}
{"action": "gitops", "subaction": "browse", "envId": "env-abc123", "id": "sync-abc123"}
{"action": "gitops", "subaction": "sync", "envId": "env-abc123", "id": "sync-abc123", "params": {"confirm": true}}
{"action": "gitops", "subaction": "create", "envId": "env-abc123", "params": {"name": "prod-stack", "repositoryId": "repo-abc", "branch": "main", "composePath": "stacks/web/docker-compose.yml", "autoSync": true, "syncInterval": 300}}The following 22 operations require confirmation before execution:
| Action | Subaction |
|---|---|
environment |
delete |
project |
down, restart, destroy, redeploy |
container |
stop, restart, delete |
image |
delete, prune |
network |
delete, prune |
volume |
delete, prune, delete-backup, restore, restore-files |
system |
prune, stop-all |
registry |
delete |
gitops |
delete, sync |
/plugin marketplace add jmagar/claude-homelab
/plugin install arcane-mcp @jmagar-claude-homelabnpm install
npm run build
npm startFor watch mode:
just devjust up
just logsCopy .env.example to .env and fill in the required values:
cp .env.example .env
chmod 600 .env| Variable | Required | Default | Description |
|---|---|---|---|
ARCANE_API_URL |
yes | — | Base URL of your Arcane instance, e.g. https://arcane.example.com |
ARCANE_API_KEY |
yes | — | API key from Arcane Settings > API |
ARCANE_MCP_TOKEN |
yes | — | Bearer token for MCP server auth. Generate with: openssl rand -hex 32 |
ARCANE_MCP_BIND_PORT |
no | 3000 |
Internal container port the server binds to |
ARCANE_MCP_PORT |
no | 44332 |
Host-side Docker port mapping |
ARCANE_MCP_TRANSPORT |
no | http |
Transport mode: http or stdio |
ARCANE_MCP_AUTH_ENABLED |
no | true |
Set to false to disable Bearer auth (use only behind a trusted proxy) |
ARCANE_MCP_ALLOW_YOLO |
no | false |
true skips elicitation prompts; re-call with params: { confirm: true } is still required |
ARCANE_MCP_ALLOW_DESTRUCTIVE |
no | false |
true auto-confirms all destructive operations. Use only in fully trusted automated environments. |
LOG_LEVEL |
no | info |
Pino log level: trace, debug, info, warn, error |
ARCANE_MCP_ALLOW_YOLO and ARCANE_MCP_ALLOW_DESTRUCTIVE control the confirmation gate independently:
- Both
false(default): destructive ops show an elicitation dialog (if the MCP client supports it) or return a prompt to re-call withparams: { confirm: true }. ALLOW_YOLO=true: skips the elicitation dialog. The agent must still re-call withparams: { confirm: true }.ALLOW_DESTRUCTIVE=true: bypasses the gate entirely. No re-call needed. Use in CI or fully automated pipelines only.
All MCP endpoints require a Bearer token unless ARCANE_MCP_AUTH_ENABLED=false.
The following paths bypass authentication:
GET /health— health check, always unauthenticated/.well-known/*— RFC 9728 OAuth discovery endpoint; reserved for future OAuth resource metadata
All other paths, including /mcp, require Authorization: Bearer <ARCANE_MCP_TOKEN>.
Token comparison uses timingSafeEqual to prevent timing attacks.
The HTTP transport maintains per-session MCP server instances (up to 200 concurrent sessions). Sessions idle for more than 30 minutes are evicted. When the session cap is reached, the least-recently-used idle session is evicted first.
Clients that do not send an initialize request are bootstrapped into a pre-initialized session. These clients cannot use elicitation (the confirmation dialog) — they will always receive the "re-call with params: { confirm: true }" response for destructive operations.
just dev # Start with watch-mode TypeScript compilation
just build # Compile TypeScript to dist/
just typecheck # Type-check without emitting
just lint # Run Biome linter
just fmt # Run Biome formatter
just test # Run unit tests with Vitest
just up # Start Docker Compose service
just down # Stop Docker Compose service
just logs # Tail container logs
just health # Check /health endpoint
just setup # Create .env from .env.example
just gen-token # Generate a random Bearer token
just clean # Remove dist/, .cache/, coverage/After starting the server:
just typecheck
just lint
just test
just healthThe health endpoint returns:
{"status": "ok", "service": "arcane-mcp"}| Plugin | Category | Description |
|---|---|---|
| homelab-core | core | Core agents, commands, skills, and setup/health workflows for homelab management. |
| overseerr-mcp | media | Search movies and TV shows, submit requests, and monitor failed requests via Overseerr. |
| unraid-mcp | infrastructure | Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry. |
| unifi-mcp | infrastructure | Monitor and manage UniFi devices, clients, firewall rules, and network health. |
| gotify-mcp | utilities | Send and manage push notifications via a self-hosted Gotify server. |
| swag-mcp | infrastructure | Create, edit, and manage SWAG nginx reverse proxy configurations. |
| synapse-mcp | infrastructure | Docker management (Flux) and SSH remote operations (Scout) across homelab hosts. |
| syslog-mcp | infrastructure | Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5. |
| plugin-lab | dev-tools | Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates. |
MIT