@yawlabs/caddy-mcp 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,7 +80,7 @@ That's it. Now ask your AI assistant:
80
80
  |---|---|---|
81
81
  | `CADDY_ADMIN_URL` | `http://localhost:2019` | Caddy admin API URL. Set to `http://caddy:2019` inside Docker, or an https URL for remote admin. |
82
82
  | `CADDY_API_TOKEN` | (none) | Optional Bearer token for authenticated admin endpoints. Only needed if you've configured Caddy with auth. |
83
- | `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. Set to `0` to disable. |
83
+ | `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. |
84
84
  | `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. |
85
85
 
86
86
  **Alternate MCP clients:**
@@ -234,7 +234,7 @@ npm install
234
234
  npm run lint # Biome check
235
235
  npm run lint:fix # Auto-fix
236
236
  npm run build # tsup bundle
237
- npm test # Vitest (182 unit tests; +8 live-Caddy integration tests gated by CADDY_MCP_INTEGRATION=1)
237
+ npm test # Vitest (188 unit tests; +8 live-Caddy integration tests gated by CADDY_MCP_INTEGRATION=1)
238
238
  npm run typecheck # tsc --noEmit
239
239
  ```
240
240
 
package/dist/index.js CHANGED
@@ -21,6 +21,23 @@ function setEtag(path, etag) {
21
21
  }
22
22
  etagCache.set(path, etag);
23
23
  }
24
+ function invalidateRelated(path) {
25
+ etagCache.delete(path);
26
+ for (const key of Array.from(etagCache.keys())) {
27
+ if (key === path) continue;
28
+ if (path.startsWith(`${key}/`) || key.startsWith(`${path}/`)) {
29
+ etagCache.delete(key);
30
+ }
31
+ }
32
+ const isIdWrite = path.startsWith("/id/");
33
+ const isConfigWrite = path.startsWith("/config/");
34
+ if (isIdWrite || isConfigWrite) {
35
+ const otherNamespace = isIdWrite ? "/config/" : "/id/";
36
+ for (const key of Array.from(etagCache.keys())) {
37
+ if (key.startsWith(otherNamespace)) etagCache.delete(key);
38
+ }
39
+ }
40
+ }
24
41
  function getBaseUrl() {
25
42
  return (process.env.CADDY_ADMIN_URL || DEFAULT_URL).replace(/\/+$/, "");
26
43
  }
@@ -107,14 +124,9 @@ async function attemptRequest(method, path, body, contentType, timeout) {
107
124
  setEtag(path, etag);
108
125
  }
109
126
  if (isWrite && res.ok && isConfigPath) {
110
- if (method === "PATCH" || method === "PUT") {
111
- if (etag) {
112
- setEtag(path, etag);
113
- } else {
114
- etagCache.delete(path);
115
- }
116
- } else {
117
- etagCache.delete(path);
127
+ invalidateRelated(path);
128
+ if ((method === "PATCH" || method === "PUT") && etag) {
129
+ setEtag(path, etag);
118
130
  }
119
131
  }
120
132
  if (!res.ok) {
@@ -428,7 +440,7 @@ function registerResources(server) {
428
440
  contents: [
429
441
  {
430
442
  uri: "caddy://config",
431
- mimeType: "application/json",
443
+ mimeType: res.ok ? "application/json" : "text/plain",
432
444
  text: res.ok ? JSON.stringify(res.data, null, 2) : `Error: ${res.error}`
433
445
  }
434
446
  ]
@@ -444,7 +456,7 @@ function registerResources(server) {
444
456
  contents: [
445
457
  {
446
458
  uri: "caddy://upstreams",
447
- mimeType: "application/json",
459
+ mimeType: res.ok ? "application/json" : "text/plain",
448
460
  text: res.ok ? JSON.stringify(res.data, null, 2) : `Error: ${res.error}`
449
461
  }
450
462
  ]
@@ -492,7 +504,7 @@ function registerResources(server) {
492
504
  contents: [
493
505
  {
494
506
  uri: "caddy://servers",
495
- mimeType: "application/json",
507
+ mimeType: res.ok ? "application/json" : "text/plain",
496
508
  text: res.ok ? JSON.stringify(res.data ?? {}, null, 2) : `Error: ${res.error}`
497
509
  }
498
510
  ]
@@ -842,9 +854,6 @@ function registerRouteTools(server) {
842
854
  ]
843
855
  };
844
856
  }
845
- if (isParentMissing(putRes)) {
846
- return serverNotFoundError(srv);
847
- }
848
857
  return formatResult(putRes);
849
858
  }
850
859
  if (!isUnknownId(existing)) {
package/dist/server.js CHANGED
@@ -19,6 +19,23 @@ function setEtag(path, etag) {
19
19
  }
20
20
  etagCache.set(path, etag);
21
21
  }
22
+ function invalidateRelated(path) {
23
+ etagCache.delete(path);
24
+ for (const key of Array.from(etagCache.keys())) {
25
+ if (key === path) continue;
26
+ if (path.startsWith(`${key}/`) || key.startsWith(`${path}/`)) {
27
+ etagCache.delete(key);
28
+ }
29
+ }
30
+ const isIdWrite = path.startsWith("/id/");
31
+ const isConfigWrite = path.startsWith("/config/");
32
+ if (isIdWrite || isConfigWrite) {
33
+ const otherNamespace = isIdWrite ? "/config/" : "/id/";
34
+ for (const key of Array.from(etagCache.keys())) {
35
+ if (key.startsWith(otherNamespace)) etagCache.delete(key);
36
+ }
37
+ }
38
+ }
22
39
  function getBaseUrl() {
23
40
  return (process.env.CADDY_ADMIN_URL || DEFAULT_URL).replace(/\/+$/, "");
24
41
  }
@@ -105,14 +122,9 @@ async function attemptRequest(method, path, body, contentType, timeout) {
105
122
  setEtag(path, etag);
106
123
  }
107
124
  if (isWrite && res.ok && isConfigPath) {
108
- if (method === "PATCH" || method === "PUT") {
109
- if (etag) {
110
- setEtag(path, etag);
111
- } else {
112
- etagCache.delete(path);
113
- }
114
- } else {
115
- etagCache.delete(path);
125
+ invalidateRelated(path);
126
+ if ((method === "PATCH" || method === "PUT") && etag) {
127
+ setEtag(path, etag);
116
128
  }
117
129
  }
118
130
  if (!res.ok) {
@@ -426,7 +438,7 @@ function registerResources(server) {
426
438
  contents: [
427
439
  {
428
440
  uri: "caddy://config",
429
- mimeType: "application/json",
441
+ mimeType: res.ok ? "application/json" : "text/plain",
430
442
  text: res.ok ? JSON.stringify(res.data, null, 2) : `Error: ${res.error}`
431
443
  }
432
444
  ]
@@ -442,7 +454,7 @@ function registerResources(server) {
442
454
  contents: [
443
455
  {
444
456
  uri: "caddy://upstreams",
445
- mimeType: "application/json",
457
+ mimeType: res.ok ? "application/json" : "text/plain",
446
458
  text: res.ok ? JSON.stringify(res.data, null, 2) : `Error: ${res.error}`
447
459
  }
448
460
  ]
@@ -490,7 +502,7 @@ function registerResources(server) {
490
502
  contents: [
491
503
  {
492
504
  uri: "caddy://servers",
493
- mimeType: "application/json",
505
+ mimeType: res.ok ? "application/json" : "text/plain",
494
506
  text: res.ok ? JSON.stringify(res.data ?? {}, null, 2) : `Error: ${res.error}`
495
507
  }
496
508
  ]
@@ -840,9 +852,6 @@ function registerRouteTools(server) {
840
852
  ]
841
853
  };
842
854
  }
843
- if (isParentMissing(putRes)) {
844
- return serverNotFoundError(srv);
845
- }
846
855
  return formatResult(putRes);
847
856
  }
848
857
  if (!isUnknownId(existing)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/caddy-mcp",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "mcpName": "io.github.YawLabs/caddy-mcp",
5
5
  "description": "MCP server for managing Caddy web servers via the admin API",
6
6
  "license": "MIT",