Skip to content

jmagar/arcane-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

148 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arcane MCP

npm ghcr.io

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.

Overview

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

Tools

arcane

Call this tool with action, subaction, and optional envId, id, and params.

{
  "action": "container",
  "subaction": "list",
  "envId": "env-abc123"
}

Parameter rules

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.

Destructive operation gate

Any operation in the DESTRUCTIVE set (listed per family below) is blocked unless one of three conditions is met:

  1. ARCANE_MCP_ALLOW_DESTRUCTIVE=true — all destructive ops auto-confirm, no re-call needed.
  2. ARCANE_MCP_ALLOW_YOLO=true — skips the interactive elicitation prompt; the tool must still be re-called with params: { "confirm": true } to proceed.
  3. 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 }.

Action families

environment

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}}

project

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}}

container

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"}]}}}

image

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}}

network

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}}

volume

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

Volume backup/restore workflow

  1. List available backups: subaction=list-backups, id=<volumeName>
  2. Create a new snapshot: subaction=create-backup, id=<volumeName>
  3. Inspect a backup's contents: use list-backups to get the backup ID, then restore-files with params.paths
  4. Full restore: subaction=restore, id=<volumeName>, params: { backupId: "...", confirm: true }
  5. Partial restore: subaction=restore-files, id=<volumeName>, params: { backupId: "...", paths: ["data/config.json"], confirm: true }
  6. 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"}}

system

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"}}

image-update

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"}

vulnerability

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"}

registry

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}}

gitops

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

GitOps workflow

  1. List existing syncs: subaction=list
  2. Check a sync's state: subaction=status, id=<syncId>
  3. Inspect repository contents: subaction=browse, id=<syncId>
  4. 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}}

Destructive operations reference

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

Installation

Plugin marketplace

/plugin marketplace add jmagar/claude-homelab
/plugin install arcane-mcp @jmagar-claude-homelab

Local development

npm install
npm run build
npm start

For watch mode:

just dev

Docker

just up
just logs

Configuration

Copy .env.example to .env and fill in the required values:

cp .env.example .env
chmod 600 .env

Environment variables

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

Safety flag behavior

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 with params: { confirm: true }.
  • ALLOW_YOLO=true: skips the elicitation dialog. The agent must still re-call with params: { confirm: true }.
  • ALLOW_DESTRUCTIVE=true: bypasses the gate entirely. No re-call needed. Use in CI or fully automated pipelines only.

Authentication

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.


Session management

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.


Development commands

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/

Verification

After starting the server:

just typecheck
just lint
just test
just health

The health endpoint returns:

{"status": "ok", "service": "arcane-mcp"}

Related plugins

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.

License

MIT

Packages

 
 
 

Contributors