@yawlabs/caddy-mcp puts Caddy's admin API behind a typed tool surface an agent can compose: config at any JSON path, routes and reverse proxies, TLS and the local PKI, Caddyfile conversion, upstream health, Prometheus metrics, and config snapshots for rollback. Built and maintained by Yaw Labs, MIT licensed, published on npm.

npx -y @yawlabs/caddy-mcp@latest

GitHub · npm · Add to Yaw MCP

Who it is for

Anyone running Caddy who would rather say what they want than hand-edit a deeply nested JSON config. Other Caddy MCP servers wrap half the admin API and silently swallow errors; this one covers every documented endpoint and surfaces what actually went wrong. What that looks like in practice:

Install

1. Enable the admin API. Caddy ships with it enabled on localhost:2019 by default, so if caddy-mcp runs on the same host there is nothing to do. If Caddy is in Docker or on a remote host, expose it and point CADDY_ADMIN_URL at it.

VariableDefaultPurpose
CADDY_ADMIN_URLhttp://localhost:2019Admin API URL. Use the container hostname inside Docker, an https URL for remote admin, or a unix socket path - see below.
CADDY_API_TOKENnoneOptional Bearer token, only needed if you have configured Caddy with auth.
CADDY_MCP_SNAPSHOT_DIRnoneWhere rollback snapshots persist. Unset, they live in memory and are lost on restart. Snapshots are full configs and can contain secrets, so the location is opt-in rather than defaulted.
CADDY_MAX_RETRIES2Retries on transient failures. 4xx and 412 never retry, and non-idempotent config POSTs skip retry so a retry cannot duplicate a route. Hard-capped, with a one-time stderr notice when a higher value is clamped.
CADDY_TIMEOUT / CADDY_LOAD_TIMEOUT10000 / 60000Request timeout in ms, and a separate longer one for a full config load. Raise the load timeout for ACME-heavy bring-ups where provisioning many certificates takes a while.

2. Add the server to your client. On macOS, Linux and WSL, a .mcp.json in your project root:

{ "mcpServers": { "caddy": { "command": "npx", "args": ["-y", "@yawlabs/caddy-mcp@latest"] } } }

On Windows, wrap the call in cmd /c: npx is a .cmd file there, and recent Node releases refuse to spawn .cmd files directly.

{ "mcpServers": { "caddy": { "command": "cmd", "args": ["/c", "npx", "-y", "@yawlabs/caddy-mcp@latest"] } } }

The same JSON block works in Claude Desktop, Cursor, Windsurf and VS Code - only the config file path differs. The file is safe to commit: it contains no secrets.

3. Restart and approve. Restart Claude Code (or your MCP client) and approve the Caddy server when prompted. Then ask it something: "What routes are configured on srv0?"

Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.

What it covers

GroupWhat it covers
Config managementRead config at any JSON path or the whole thing; write with overwrite, append or insert semantics; delete a path; get, set or delete by @id tag instead of navigating deep paths; replace the entire config atomically; and manage snapshots for rollback
Route operationsAdd a reverse proxy in one call, add a route with full match and handle control for any Caddy handler, remove a route by @id or index, and get a human-readable route summary that tolerates a malformed config
TLS and conversionCheck or set the ACME email and CA URL, deep-merged into the issuer path so custom certs, on-demand TLS and additional policies survive the write; and convert a config in any registered adapter format to Caddy JSON without applying it
Server operationsConnectivity check plus a config summary, the list of HTTP servers with addresses and route counts, reverse-proxy upstream health, Prometheus metrics with optional filtering, CA info and certificate chains, and a confirm-gated graceful shutdown

Browsable MCP resources expose read-only data a client can fetch without a tool call: the current full configuration (caddy://config), a summary of configured HTTP servers, upstream health, and Prometheus metrics. The metrics resource is capped at a fixed number of lines to keep client context bounded - use the metrics tool with a filter or a larger line budget when you need more.

The TLS tool is worth calling out for what it refuses to do. It PATCHes first, POSTs a minimal config on a fresh install, and on an existing config deep-merges into the issuer path and PUTs the result back so siblings are preserved. If the existing structure is not the shape it expects, it fails with a shape-specific error rather than clobbering what is there.

Safety and control

Troubleshooting

For the hardened setup, Caddy's own recommendation is to move the admin API off a loopback port and onto a unix socket, where access is governed by filesystem permissions. Point CADDY_ADMIN_URL at the same path and requests go over the socket instead of TCP; the process running caddy-mcp needs read and write permission on the socket file, and a Bearer token still applies if you have auth in front of it.

Frequently asked questions

What does the Caddy MCP server do?

It wraps every documented endpoint of Caddy's admin API as MCP tools, so an agent can read and write config at any JSON path, add reverse proxies and routes, check and set TLS settings, convert a Caddyfile to JSON without applying it, inspect upstream health and Prometheus metrics, manage the local CA, and snapshot the config for rollback. Browsable resources expose the config, the server list, upstream health and metrics without a tool call at all.

Do I need to expose the admin API?

Caddy ships with the admin API enabled on localhost:2019, which is all you need when the server runs on the same host. For Docker or a remote host, point CADDY_ADMIN_URL at the right origin and set CADDY_API_TOKEN if you have auth in front of it. Caddy's own recommended hardening is to move the admin API off a loopback port and onto a unix socket, where access is governed by filesystem permissions; CADDY_ADMIN_URL accepts a socket path in either spelling, and the process running caddy-mcp needs read and write permission on the socket file.

Can an agent silently overwrite someone else's change?

Writes go through ETags with If-Match, so a change made between your read and your write fails with HTTP 412 Precondition Failed, surfaced as a clear message rather than a cryptic error - re-read the config and retry. caddy_config_set also defaults to idempotent overwrite (PATCH) rather than append (POST), so calling it twice does not duplicate your route, and caddy_reverse_proxy takes an optional id that replaces the route in place on repeat calls instead of stacking duplicates.

Which operations require confirmation?

Deleting config, removing a route, stopping the server and applying a snapshot all require confirm=true, and deleting a parent path removes every descendant, which is why that one is gated too. Every tool also declares readOnlyHint, destructiveHint and idempotentHint so MCP clients can skip confirmation dialogs on safe reads and prompt on mutations. Replacing the whole config auto-snapshots the prior one first, so a bad atomic deploy is recoverable.

Why did reloading the Caddyfile with SIGUSR1 stop working?

That is Caddy's own behavior and not a bug here. In current Caddy releases SIGUSR1 reloads from the file on disk only if the config has never been changed through the admin API; the first write from caddy-mcp, or any other API client, makes Caddy treat the running config as API-owned and the signal becomes a no-op. Pick one owner per instance: if the Caddyfile is the source of truth, keep caddy-mcp to its read-only tools and reload from the file, and if caddy-mcp owns the config, apply changes with caddy_load instead of the signal.

Related MCP servers

Further reading

Published by Yaw Labs.