@yawlabs/caddy-mcp 1.0.0 → 1.0.1
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/dist/index.js +33 -1
- package/dist/server.js +33 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,6 +41,16 @@ function getHeaders(contentType) {
|
|
|
41
41
|
function normalizePath(path) {
|
|
42
42
|
return path.replace(/^\/?(config(\/|$))?/, "");
|
|
43
43
|
}
|
|
44
|
+
function rejectTraversal(path) {
|
|
45
|
+
if (/(^|\/)\.\.(\/|$)/.test(path)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
status: 0,
|
|
49
|
+
error: `Invalid path "${path}": '..' segments are not allowed`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
44
54
|
function sleep(ms) {
|
|
45
55
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
56
|
}
|
|
@@ -129,22 +139,32 @@ async function attemptRequest(method, path, body, contentType, timeout) {
|
|
|
129
139
|
}
|
|
130
140
|
function configGet(path = "") {
|
|
131
141
|
const normalized = normalizePath(path);
|
|
142
|
+
const bad = rejectTraversal(normalized);
|
|
143
|
+
if (bad) return Promise.resolve(bad);
|
|
132
144
|
return caddyRequest("GET", `/config/${normalized}`);
|
|
133
145
|
}
|
|
134
146
|
function configPost(path, value) {
|
|
135
147
|
const normalized = normalizePath(path);
|
|
148
|
+
const bad = rejectTraversal(normalized);
|
|
149
|
+
if (bad) return Promise.resolve(bad);
|
|
136
150
|
return caddyRequest("POST", `/config/${normalized}`, value);
|
|
137
151
|
}
|
|
138
152
|
function configPut(path, value) {
|
|
139
153
|
const normalized = normalizePath(path);
|
|
154
|
+
const bad = rejectTraversal(normalized);
|
|
155
|
+
if (bad) return Promise.resolve(bad);
|
|
140
156
|
return caddyRequest("PUT", `/config/${normalized}`, value);
|
|
141
157
|
}
|
|
142
158
|
function configPatch(path, value) {
|
|
143
159
|
const normalized = normalizePath(path);
|
|
160
|
+
const bad = rejectTraversal(normalized);
|
|
161
|
+
if (bad) return Promise.resolve(bad);
|
|
144
162
|
return caddyRequest("PATCH", `/config/${normalized}`, value);
|
|
145
163
|
}
|
|
146
164
|
function configDelete(path) {
|
|
147
165
|
const normalized = normalizePath(path);
|
|
166
|
+
const bad = rejectTraversal(normalized);
|
|
167
|
+
if (bad) return Promise.resolve(bad);
|
|
148
168
|
return caddyRequest("DELETE", `/config/${normalized}`);
|
|
149
169
|
}
|
|
150
170
|
var LOAD_TIMEOUT = 6e4;
|
|
@@ -169,14 +189,20 @@ function getPkiCertificates(ca = "local") {
|
|
|
169
189
|
return caddyRequest("GET", `/pki/ca/${ca}/certificates`);
|
|
170
190
|
}
|
|
171
191
|
function configByIdGet(id, subpath = "") {
|
|
192
|
+
const bad = rejectTraversal(subpath);
|
|
193
|
+
if (bad) return Promise.resolve(bad);
|
|
172
194
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
173
195
|
return caddyRequest("GET", path);
|
|
174
196
|
}
|
|
175
197
|
function configByIdSet(id, value, method = "PATCH", subpath = "") {
|
|
198
|
+
const bad = rejectTraversal(subpath);
|
|
199
|
+
if (bad) return Promise.resolve(bad);
|
|
176
200
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
177
201
|
return caddyRequest(method, path, value);
|
|
178
202
|
}
|
|
179
203
|
function configByIdDelete(id, subpath = "") {
|
|
204
|
+
const bad = rejectTraversal(subpath);
|
|
205
|
+
if (bad) return Promise.resolve(bad);
|
|
180
206
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
181
207
|
return caddyRequest("DELETE", path);
|
|
182
208
|
}
|
|
@@ -399,6 +425,12 @@ ${lines.join("\n")}` }] };
|
|
|
399
425
|
if (action === "save") {
|
|
400
426
|
const current2 = await configGet();
|
|
401
427
|
if (!current2.ok) return formatResult(current2);
|
|
428
|
+
if (current2.data === void 0) {
|
|
429
|
+
return {
|
|
430
|
+
isError: true,
|
|
431
|
+
content: [{ type: "text", text: "Error: no config loaded to snapshot" }]
|
|
432
|
+
};
|
|
433
|
+
}
|
|
402
434
|
saveSnapshot(current2.data, "manual");
|
|
403
435
|
return { content: [{ type: "text", text: "Snapshot saved." }] };
|
|
404
436
|
}
|
|
@@ -809,7 +841,7 @@ function registerRouteTools(server) {
|
|
|
809
841
|
);
|
|
810
842
|
server.tool(
|
|
811
843
|
"caddy_remove_route",
|
|
812
|
-
"Remove a route. Target by @id (preferred \u2014 stable across reorderings) or by array index on a specific server.
|
|
844
|
+
"Remove a route. Target by @id (preferred \u2014 stable across reorderings) or by array index on a specific server. Index-based removal is a two-step read-then-delete and can race against concurrent edits; prefer @id when possible.",
|
|
813
845
|
{
|
|
814
846
|
id: z4.string().regex(/^[\w-]{1,128}$/).optional().describe("The @id of the route to remove (preferred \u2014 stable even if routes get reordered)"),
|
|
815
847
|
index: z4.number().int().nonnegative().optional().describe("Zero-based index of the route in the server's routes array (only used if id is not provided)"),
|
package/dist/server.js
CHANGED
|
@@ -39,6 +39,16 @@ function getHeaders(contentType) {
|
|
|
39
39
|
function normalizePath(path) {
|
|
40
40
|
return path.replace(/^\/?(config(\/|$))?/, "");
|
|
41
41
|
}
|
|
42
|
+
function rejectTraversal(path) {
|
|
43
|
+
if (/(^|\/)\.\.(\/|$)/.test(path)) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 0,
|
|
47
|
+
error: `Invalid path "${path}": '..' segments are not allowed`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
42
52
|
function sleep(ms) {
|
|
43
53
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
54
|
}
|
|
@@ -127,22 +137,32 @@ async function attemptRequest(method, path, body, contentType, timeout) {
|
|
|
127
137
|
}
|
|
128
138
|
function configGet(path = "") {
|
|
129
139
|
const normalized = normalizePath(path);
|
|
140
|
+
const bad = rejectTraversal(normalized);
|
|
141
|
+
if (bad) return Promise.resolve(bad);
|
|
130
142
|
return caddyRequest("GET", `/config/${normalized}`);
|
|
131
143
|
}
|
|
132
144
|
function configPost(path, value) {
|
|
133
145
|
const normalized = normalizePath(path);
|
|
146
|
+
const bad = rejectTraversal(normalized);
|
|
147
|
+
if (bad) return Promise.resolve(bad);
|
|
134
148
|
return caddyRequest("POST", `/config/${normalized}`, value);
|
|
135
149
|
}
|
|
136
150
|
function configPut(path, value) {
|
|
137
151
|
const normalized = normalizePath(path);
|
|
152
|
+
const bad = rejectTraversal(normalized);
|
|
153
|
+
if (bad) return Promise.resolve(bad);
|
|
138
154
|
return caddyRequest("PUT", `/config/${normalized}`, value);
|
|
139
155
|
}
|
|
140
156
|
function configPatch(path, value) {
|
|
141
157
|
const normalized = normalizePath(path);
|
|
158
|
+
const bad = rejectTraversal(normalized);
|
|
159
|
+
if (bad) return Promise.resolve(bad);
|
|
142
160
|
return caddyRequest("PATCH", `/config/${normalized}`, value);
|
|
143
161
|
}
|
|
144
162
|
function configDelete(path) {
|
|
145
163
|
const normalized = normalizePath(path);
|
|
164
|
+
const bad = rejectTraversal(normalized);
|
|
165
|
+
if (bad) return Promise.resolve(bad);
|
|
146
166
|
return caddyRequest("DELETE", `/config/${normalized}`);
|
|
147
167
|
}
|
|
148
168
|
var LOAD_TIMEOUT = 6e4;
|
|
@@ -167,14 +187,20 @@ function getPkiCertificates(ca = "local") {
|
|
|
167
187
|
return caddyRequest("GET", `/pki/ca/${ca}/certificates`);
|
|
168
188
|
}
|
|
169
189
|
function configByIdGet(id, subpath = "") {
|
|
190
|
+
const bad = rejectTraversal(subpath);
|
|
191
|
+
if (bad) return Promise.resolve(bad);
|
|
170
192
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
171
193
|
return caddyRequest("GET", path);
|
|
172
194
|
}
|
|
173
195
|
function configByIdSet(id, value, method = "PATCH", subpath = "") {
|
|
196
|
+
const bad = rejectTraversal(subpath);
|
|
197
|
+
if (bad) return Promise.resolve(bad);
|
|
174
198
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
175
199
|
return caddyRequest(method, path, value);
|
|
176
200
|
}
|
|
177
201
|
function configByIdDelete(id, subpath = "") {
|
|
202
|
+
const bad = rejectTraversal(subpath);
|
|
203
|
+
if (bad) return Promise.resolve(bad);
|
|
178
204
|
const path = subpath ? `/id/${id}/${subpath}` : `/id/${id}`;
|
|
179
205
|
return caddyRequest("DELETE", path);
|
|
180
206
|
}
|
|
@@ -397,6 +423,12 @@ ${lines.join("\n")}` }] };
|
|
|
397
423
|
if (action === "save") {
|
|
398
424
|
const current2 = await configGet();
|
|
399
425
|
if (!current2.ok) return formatResult(current2);
|
|
426
|
+
if (current2.data === void 0) {
|
|
427
|
+
return {
|
|
428
|
+
isError: true,
|
|
429
|
+
content: [{ type: "text", text: "Error: no config loaded to snapshot" }]
|
|
430
|
+
};
|
|
431
|
+
}
|
|
400
432
|
saveSnapshot(current2.data, "manual");
|
|
401
433
|
return { content: [{ type: "text", text: "Snapshot saved." }] };
|
|
402
434
|
}
|
|
@@ -807,7 +839,7 @@ function registerRouteTools(server) {
|
|
|
807
839
|
);
|
|
808
840
|
server.tool(
|
|
809
841
|
"caddy_remove_route",
|
|
810
|
-
"Remove a route. Target by @id (preferred \u2014 stable across reorderings) or by array index on a specific server.
|
|
842
|
+
"Remove a route. Target by @id (preferred \u2014 stable across reorderings) or by array index on a specific server. Index-based removal is a two-step read-then-delete and can race against concurrent edits; prefer @id when possible.",
|
|
811
843
|
{
|
|
812
844
|
id: z4.string().regex(/^[\w-]{1,128}$/).optional().describe("The @id of the route to remove (preferred \u2014 stable even if routes get reordered)"),
|
|
813
845
|
index: z4.number().int().nonnegative().optional().describe("Zero-based index of the route in the server's routes array (only used if id is not provided)"),
|