@yawlabs/caddy-mcp 2.4.1 → 2.4.2
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/bin/caddy-mcp.mjs +76 -6
- package/dist/tools/operational.d.ts +4 -4
- package/package.json +1 -1
package/bin/caddy-mcp.mjs
CHANGED
|
@@ -28,8 +28,37 @@
|
|
|
28
28
|
* For an MCP host config, point straight at oam and skip this file:
|
|
29
29
|
* { "command": "oam", "args": ["run", "<abs>/dist/index.js"] }
|
|
30
30
|
*
|
|
31
|
+
* ALREADY RUNNING ON OAM
|
|
32
|
+
* A host can resolve this package's `bin` and launch `oam run <this file>`
|
|
33
|
+
* instead of `node <this file>` -- Yaw MCP does, and so does oam's sidecar
|
|
34
|
+
* regression matrix. This launcher used to discover oam and spawn it anyway,
|
|
35
|
+
* so one server cost two runtime boots: measured on Windows, oam.exe with a
|
|
36
|
+
* NESTED oam.exe + conhost.exe underneath it. Now, when `process.versions.oam`
|
|
37
|
+
* clears the same MINIMUM OAM VERSION a discovered binary has to, the server is
|
|
38
|
+
* imported into THIS process exactly as the Node fallback is -- no discovery,
|
|
39
|
+
* no `oam --version` probe, no second oam. OAM_BIN is a discovery input, so it
|
|
40
|
+
* is not consulted on that path: the host has already chosen which oam runs.
|
|
41
|
+
*
|
|
42
|
+
* Two cases still take the discovery path, deliberately. CADDY_MCP_SANDBOX=1,
|
|
43
|
+
* because `--permission` is a process-level flag that only a FRESH oam can
|
|
44
|
+
* apply -- serving in-process on the host would drop the sandbox without a
|
|
45
|
+
* word, a security downgrade dressed up as an optimisation. And a host oam
|
|
46
|
+
* below the floor, which takes the discovery path exactly as it always did.
|
|
47
|
+
*
|
|
48
|
+
* The sandbox asks for a spawn; it does not guarantee one. Discovery can still
|
|
49
|
+
* come up empty -- no oam binary, one below the floor or unreadable, a spawn
|
|
50
|
+
* that fails -- and under CADDY_MCP_RUNTIME=auto that falls back exactly as it
|
|
51
|
+
* does on Node: the server runs in THIS process WITHOUT `--permission`. No oam
|
|
52
|
+
* (and no .cmd shim) and a failed spawn fall back silently; the too-old or
|
|
53
|
+
* unreadable binary and .cmd-shim cases print a note that does not mention the
|
|
54
|
+
* sandbox. Only CADDY_MCP_RUNTIME=oam turns that miss into a hard failure, so
|
|
55
|
+
* pair it with CADDY_MCP_SANDBOX=1 when the sandbox has to hold.
|
|
56
|
+
* CADDY_MCP_RUNTIME=node ignores the sandbox entirely.
|
|
57
|
+
*
|
|
31
58
|
* THE `--permission` SANDBOX (oam 0.9.0+, opt-in)
|
|
32
|
-
* `CADDY_MCP_SANDBOX=1` runs the server under oam's permission model
|
|
59
|
+
* `CADDY_MCP_SANDBOX=1` runs the server under oam's permission model when an
|
|
60
|
+
* oam binary is found and launched -- see ALREADY RUNNING ON OAM for the
|
|
61
|
+
* fallback that runs it unsandboxed, and how to refuse that instead.
|
|
33
62
|
*
|
|
34
63
|
* The admin API endpoint is DERIVED from CADDY_ADMIN_URL (default
|
|
35
64
|
* http://localhost:2019 -- byte-identical to DEFAULT_URL in src/api.ts, see
|
|
@@ -68,9 +97,12 @@
|
|
|
68
97
|
*
|
|
69
98
|
* SELECTION
|
|
70
99
|
* CADDY_MCP_RUNTIME=oam require oam; fail loudly if it is missing
|
|
100
|
+
* (already running on oam satisfies it, except
|
|
101
|
+
* under CADDY_MCP_SANDBOX=1)
|
|
71
102
|
* CADDY_MCP_RUNTIME=node never use oam
|
|
72
103
|
* CADDY_MCP_RUNTIME=auto prefer oam, silently fall back (default)
|
|
73
|
-
* CADDY_MCP_SANDBOX=1 run oam under --permission (oam 0.9.0+)
|
|
104
|
+
* CADDY_MCP_SANDBOX=1 run oam under --permission (oam 0.9.0+); under
|
|
105
|
+
* auto a discovery miss still runs unsandboxed
|
|
74
106
|
* OAM_BIN=/path/to/oam explicit binary, checked before any discovery
|
|
75
107
|
*/
|
|
76
108
|
|
|
@@ -143,17 +175,27 @@ function findOam() {
|
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
/**
|
|
146
|
-
*
|
|
178
|
+
* Version text -> [major, minor, patch], or null when it holds no version.
|
|
147
179
|
* A pre-release suffix (0.9.0-rc.1) truncates to its base version.
|
|
180
|
+
*
|
|
181
|
+
* Shared by the two places a version is read -- a discovered binary's
|
|
182
|
+
* `oam --version` output ("oam 0.15.1") and the host's own
|
|
183
|
+
* `process.versions.oam` ("0.15.1") -- so they cannot disagree about what a
|
|
184
|
+
* version string means, or which floor it has to clear.
|
|
148
185
|
*/
|
|
186
|
+
function parseVersion(text) {
|
|
187
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
188
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** `oam --version` -> [major, minor, patch], or null when it cannot be read. */
|
|
149
192
|
function oamVersion(cmd) {
|
|
150
193
|
try {
|
|
151
194
|
const out = execFileSync(cmd, ["--version"], {
|
|
152
195
|
encoding: "utf-8",
|
|
153
196
|
stdio: ["ignore", "pipe", "ignore"],
|
|
154
197
|
});
|
|
155
|
-
|
|
156
|
-
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
198
|
+
return parseVersion(out);
|
|
157
199
|
} catch {
|
|
158
200
|
// Not executable, wrong arch, or deleted since the stat. Caller degrades.
|
|
159
201
|
return null;
|
|
@@ -170,6 +212,29 @@ function atLeast(v, min) {
|
|
|
170
212
|
return true;
|
|
171
213
|
}
|
|
172
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Where the server runs, decided BEFORE any discovery:
|
|
217
|
+
* "in-process" import it into THIS process
|
|
218
|
+
* "discover" find an oam binary, gate its version, spawn it -- or fall
|
|
219
|
+
* back to Node in-process when that fails
|
|
220
|
+
*
|
|
221
|
+
* `hostOam` is `process.versions.oam`: oam's own key, absent on Node, so on
|
|
222
|
+
* Node every mode but `node` is the discovery path it always was. `sandbox`
|
|
223
|
+
* is whether a spawn would carry flags only a fresh oam can apply; see ALREADY
|
|
224
|
+
* RUNNING ON OAM above for why that alone forces discovery -- and why discovery
|
|
225
|
+
* is not a guaranteed spawn, since its miss still falls back in-process. The
|
|
226
|
+
* floor is OAM_MIN itself, not a parameter, so a host oam and a discovered one
|
|
227
|
+
* can never be held to different minimums.
|
|
228
|
+
*
|
|
229
|
+
* Pure on purpose: every input is passed in, so the whole decision is testable
|
|
230
|
+
* without booting a runtime.
|
|
231
|
+
*/
|
|
232
|
+
function runtimePlan({ mode, hostOam, sandbox }) {
|
|
233
|
+
if (mode === "node") return "in-process";
|
|
234
|
+
if (sandbox) return "discover";
|
|
235
|
+
return atLeast(parseVersion(hostOam ?? ""), OAM_MIN) ? "in-process" : "discover";
|
|
236
|
+
}
|
|
237
|
+
|
|
173
238
|
/**
|
|
174
239
|
* The `--permission` grant list, or [] when the sandbox is not requested.
|
|
175
240
|
*
|
|
@@ -361,7 +426,12 @@ async function runInProcess() {
|
|
|
361
426
|
|
|
362
427
|
const mode = (process.env.CADDY_MCP_RUNTIME ?? "auto").toLowerCase();
|
|
363
428
|
|
|
364
|
-
|
|
429
|
+
// The sandbox is read off the grant list rather than CADDY_MCP_SANDBOX, so
|
|
430
|
+
// "would the spawn carry --permission" cannot drift from what the spawn below
|
|
431
|
+
// actually passes.
|
|
432
|
+
const plan = runtimePlan({ mode, hostOam: process.versions.oam, sandbox: sandboxFlags().length > 0 });
|
|
433
|
+
|
|
434
|
+
if (plan === "in-process") {
|
|
365
435
|
await runInProcess();
|
|
366
436
|
} else {
|
|
367
437
|
const oam = findOam();
|
|
@@ -10,10 +10,10 @@ export declare const METRICS_DEFAULT_MAX_LINES = 500;
|
|
|
10
10
|
* metric name (blank lines, free-form `#` comments) are dropped when filtering.
|
|
11
11
|
*
|
|
12
12
|
* Truncation: if the resulting line count exceeds `maxLines`, output is cut at `maxLines` and a
|
|
13
|
-
* trailing `# [truncated, N lines omitted -- use filter
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* stream.
|
|
13
|
+
* trailing `# [truncated, N lines omitted; max_lines=M -- use filter or raise max_lines]` comment
|
|
14
|
+
* is appended, where N is the number of lines cut and M is `maxLines`. If the input contained a
|
|
15
|
+
* `# EOF` end-of-file marker that would have been dropped by the cut, it is re-appended after the
|
|
16
|
+
* truncation comment so strict downstream parsers still see a terminated stream.
|
|
17
17
|
*/
|
|
18
18
|
export declare function applyMetricsControls(raw: string, filter: string | undefined, maxLines: number): string;
|
|
19
19
|
export declare function registerOperationalTools(server: McpServer): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/caddy-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"mcpName": "io.github.YawLabs/caddy-mcp",
|
|
5
5
|
"description": "Caddy MCP server for Claude Code, Cursor, and any MCP client: admin API, config, routes, reverse proxy, TLS, PKI, metrics",
|
|
6
6
|
"license": "MIT",
|