Caddy Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Caddy Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Manage Caddy web servers from Claude Code, Cursor, and any MCP client. 18 tools + 4 resources covering every endpoint of Caddy's admin API — config, routes, reverse proxies, TLS, PKI, metrics, snapshots.
Built and maintained by Yaw Labs.
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
Other Caddy MCP servers wrap half the admin API and silently swallow errors. This one doesn't.
/load, /config/*, /id/*, /stop, /adapt, /pki/ca/*, /reverse_proxy/upstreams, /metrics. No placeholder tools that 404.If-Match) so your changes never silently overwrite someone else's. Surfaces HTTP 412 Precondition Failed as a clear message, not a cryptic error.caddy_config_set defaults to idempotent overwrite (PATCH), not append (POST). Calling twice doesn't duplicate your route.caddy_list_routes never crashes on malformed config, even if routes are null, handlers are strings, or matchers are non-arrays. Regression-tested.CADDY_ADMIN_URL contains a token in the path/query, the connect-failed message shows only the origin.readOnlyHint, destructiveHint, and idempotentHint, so MCP clients can skip confirmations for safe ops.node_modules install.@id values, server names, and CA ids are all regex-validated with length caps. Blocks CRLF header injection and ReDoS.1. Enable the Caddy admin API
Caddy ships with the admin API enabled on localhost:2019 by default. If you're running Caddy in Docker or on a remote host, expose it via CADDY_ADMIN_URL.
2. Create `.mcp.json` in your project root
macOS / Linux / WSL:
{
"mcpServers": {
"caddy": {
"command": "npx",
"args": ["-y", "@yawlabs/caddy-mcp@latest"]
}
}
}Windows:
{
"mcpServers": {
"caddy": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/caddy-mcp@latest"]
}
}
}Why the extra step on Windows? Since Node 20,child_process.spawncannot directly execute.cmdfiles (that's whatnpxis on Windows). Wrapping withcmd /cis the standard workaround. This file is safe to commit — it contains no secrets.
3. Restart and approve
Restart Claude Code (or your MCP client) and approve the Caddy MCP server when prompted.
That's it. Now ask your AI assistant:
"Proxy api.local to localhost:3000"
>
"What routes are configured on srv0?"
>
"Show me the Prometheus metrics"
| Environment variable | Default | Description |
|---|---|---|
CADDY_ADMIN_URL | http://localhost:2019 | Caddy admin API URL. Set to http://caddy:2019 inside Docker, or an https URL for remote admin. |
CADDY_API_TOKEN | (none) | Optional Bearer token for authenticated admin endpoints. Only needed if you've configured Caddy with auth. |
CADDY_MAX_RETRIES | 2 | Number of retries on transient failures (5xx, network errors). 4xx and 412 never retry. POSTs to /config/* and /id/* also skip retry (non-idempotent appends/creates -- retrying could duplicate routes or 409 a half-applied create). POSTs to /load, /adapt, /stop still retry. Hard-capped at 5; values above the cap log a one-time stderr notice so the clamp is visible. Set to 0 to disable. |
CADDY_TIMEOUT | 10000 | Timeout in ms for all admin API requests except /load (which uses CADDY_LOAD_TIMEOUT). Non-numeric, <= 0, or fractional values below 1ms fall back to the default. |
CADDY_LOAD_TIMEOUT | 60000 | Timeout in ms for the /load endpoint; raise for ACME-heavy bring-ups where provisioning many certificates can exceed the default. Non-numeric, <= 0, or fractional values below 1ms fall back to the default. |
Alternate MCP clients:
| Client | Config file |
|---|---|
| Claude Code | .mcp.json (project root) or ~/.claude.json (global) |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) |
| Cursor | ~/.cursor/mcp.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| VS Code | .vscode/mcp.json |
Use the same JSON block shown above in any of these.
overwrite (PATCH, default, idempotent), append (POST), insert (PUT, for array positions).confirm=true (deleting a parent path also removes every descendant).@id tag — much easier than navigating deep paths. The delete action requires confirm=true.list, save, apply (confirm-gated). In-memory, last 10.from='api.local' to=['localhost:3000']. Pass an optional id for idempotent writes — repeat calls replace the route in place instead of duplicating.@id (preferred) or by index. Requires confirm=true.on_demand, additional policies). Refuses with a shape-specific error if the existing structure is unexpected — never clobbers.caddyfile (built-in, default) plus any adapter module compiled into your Caddy binary — e.g., nginx (caddy-nginx-adapter), yaml (caddy-yaml). Great for previewing or porting from existing configs.filter (substring match on metric name, keeps # HELP / # TYPE lines for retained metrics) and max_lines (default 500) keep responses compact on busy servers.local).confirm=true to prevent accidents.Browsable read-only data — MCP clients can fetch these directly without a tool call:
caddy://config — Current full Caddy JSON configuration.caddy://servers — Summary of all configured HTTP servers.caddy://upstreams — Reverse proxy upstream health status.caddy://metrics — Prometheus metrics (text exposition format). Capped at the first 500 lines to keep client context bounded; use the caddy_metrics tool with filter / max_lines for filtered or larger output.> "Proxy api.example.com to my app on port 3000"
→ caddy_reverse_proxy({ from: "api.example.com", to: ["localhost:3000"] })> "Make sure api.example.com points at localhost:3000, with a stable id"
→ caddy_reverse_proxy({ from: "api.example.com", to: ["localhost:3000"], id: "api-prod" })
# First call creates the route under @id="api-prod".
# Subsequent calls with the same id REPLACE in place — no duplicate routes.
# Refuses with a clear error if "api-prod" is already in use by a non-route
# config object (TLS issuer, server, etc.) — @ids are config-global in Caddy.> "Just the HTTP request metrics, please"
→ caddy_metrics({ filter: "http_requests" })
# Keeps sample lines whose metric name contains "http_requests",
# plus their `# HELP` / `# TYPE` lines. Drops the rest.> "Convert this Caddyfile to JSON so I can review it:
example.com {
reverse_proxy localhost:8080
}"
→ caddy_adapt({ config: "..." })> "Fetch Prometheus metrics and tell me which route is slowest"
→ caddy_metrics()> "Update the route with @id 'api-v2' to point to the new backend"
→ caddy_config_by_id({ id: "api-v2", action: "set", value: {...} })
# Uses ETags — you'll get HTTP 412 if someone else changed it first> "Replace the whole config with this Caddyfile"
→ caddy_adapt({ config: "..." }) # validate first
→ caddy_load({ config: adaptedJson }) # apply atomically"Cannot connect to Caddy admin API"
caddy run or systemctl status caddy.http://localhost:2019. If Caddy is in Docker, use the container hostname.CADDY_ADMIN_URL in your MCP config env to match."HTTP 412 Precondition Failed"
"HTTP 403" on /load or /config writes
admin.listen or admin.origins restrictions set in your Caddy config, or you're missing an Authorization header.CADDY_API_TOKEN in your MCP config env if Caddy expects a Bearer token.Windows: MCP server doesn't start
cmd /c npx ... pattern from the Quick start section. Node 20+ can't spawn .cmd files directly.localhost:2019)git clone https://github.com/YawLabs/caddy-mcp.git
cd caddy-mcp
npm install
npm run lint # Biome check
npm run lint:fix # Auto-fix
npm run build # tsup bundle
npm test # Vitest (230 unit tests; +8 live-Caddy integration tests gated by CADDY_MCP_INTEGRATION=1)
npm run typecheck # tsc --noEmitSee CONTRIBUTING.md for the full workflow, including release process.
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.