@bluenotelogic/harness 0.1.0
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/LICENSE +215 -0
- package/README.md +123 -0
- package/dist/bundle.d.ts +69 -0
- package/dist/bundle.js +96 -0
- package/dist/bundle.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +96 -0
- package/dist/oauth.js +345 -0
- package/dist/oauth.js.map +1 -0
- package/dist/writers.d.ts +76 -0
- package/dist/writers.js +190 -0
- package/dist/writers.js.map +1 -0
- package/package.json +46 -0
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 client for Blue Note Memory — authorization-code + PKCE over a loopback
|
|
3
|
+
* redirect, with a device-code fallback for headless machines.
|
|
4
|
+
*
|
|
5
|
+
* WHY LOOPBACK AND NOT A FIXED PORT: this is a public native client, so RFC 8252 §7.3
|
|
6
|
+
* applies — bind an ephemeral port and register a regex redirect. The `blue-note-mem-mcp`
|
|
7
|
+
* client on Blue Note ID already has `http://(127\.0\.0\.1|localhost):[0-9]{1,5}/.*`
|
|
8
|
+
* registered for exactly this. A fixed port would collide with whatever else is running.
|
|
9
|
+
*
|
|
10
|
+
* WHY WE STILL CALL /register: Authentik has no RFC 7591 dynamic client registration, so
|
|
11
|
+
* mem-mcp serves a shim that hands back the pre-registered public client_id. Calling it is
|
|
12
|
+
* the spec-correct discovery path and costs one request; `--client-id` skips it.
|
|
13
|
+
*
|
|
14
|
+
* NO CLIENT SECRET ANYWHERE. A public client cannot keep one, which is the whole point:
|
|
15
|
+
* PKCE is what binds the authorization code to this process.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
18
|
+
import { createServer } from "node:http";
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
|
+
/**
|
|
21
|
+
* PKCE S256. The verifier is 32 random bytes base64url-encoded (43 chars — comfortably
|
|
22
|
+
* inside RFC 7636's 43..128 range); the challenge is its SHA-256, also base64url.
|
|
23
|
+
*/
|
|
24
|
+
export function createPkce() {
|
|
25
|
+
const verifier = base64url(randomBytes(32));
|
|
26
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
27
|
+
return { verifier, challenge };
|
|
28
|
+
}
|
|
29
|
+
export function base64url(buf) {
|
|
30
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Constant-time compare for the `state` parameter. Not strictly required — state is an
|
|
34
|
+
* integrity check, not a secret — but a length-independent comparison keeps the callback
|
|
35
|
+
* handler free of an early-exit oracle, and it costs nothing.
|
|
36
|
+
*/
|
|
37
|
+
export function safeEqual(a, b) {
|
|
38
|
+
const ba = Buffer.from(a);
|
|
39
|
+
const bb = Buffer.from(b);
|
|
40
|
+
if (ba.length !== bb.length)
|
|
41
|
+
return false;
|
|
42
|
+
return timingSafeEqual(ba, bb);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every discovered endpoint must be https and live on the same host as the issuer, and
|
|
46
|
+
* must not smuggle credentials in the userinfo part. Same rule the PHP side applies in
|
|
47
|
+
* lib/Auth/BlueNoteOidc.php — a metadata document is attacker-influencable input, so a
|
|
48
|
+
* redirected authorization_endpoint is a credential-phishing primitive if left unchecked.
|
|
49
|
+
*
|
|
50
|
+
* Loopback (http://127.0.0.1) is exempt because that is our OWN redirect target, never a
|
|
51
|
+
* discovered endpoint.
|
|
52
|
+
*/
|
|
53
|
+
export function assertSafeEndpoint(url, issuer, what) {
|
|
54
|
+
let u;
|
|
55
|
+
let iss;
|
|
56
|
+
try {
|
|
57
|
+
u = new URL(url);
|
|
58
|
+
iss = new URL(issuer);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error(`${what} is not a valid URL: ${url}`);
|
|
62
|
+
}
|
|
63
|
+
if (u.protocol !== "https:")
|
|
64
|
+
throw new Error(`${what} must be https, got: ${url}`);
|
|
65
|
+
if (u.username || u.password)
|
|
66
|
+
throw new Error(`${what} must not embed credentials: ${url}`);
|
|
67
|
+
if (u.host !== iss.host) {
|
|
68
|
+
throw new Error(`${what} host (${u.host}) does not match issuer host (${iss.host})`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function getJson(url, what) {
|
|
72
|
+
const res = await fetch(url, { headers: { accept: "application/json" } });
|
|
73
|
+
if (!res.ok)
|
|
74
|
+
throw new Error(`${what}: ${res.status} ${res.statusText} from ${url}`);
|
|
75
|
+
return (await res.json());
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* RFC 9728 protected-resource metadata -> RFC 8414 authorization-server metadata.
|
|
79
|
+
*
|
|
80
|
+
* The resource document is what a spec-compliant MCP client finds from the endpoint's
|
|
81
|
+
* `401 + WWW-Authenticate: Bearer resource_metadata="..."`. We follow the same chain
|
|
82
|
+
* rather than hardcoding paths, so a self-hosted deployment works with only `--url`.
|
|
83
|
+
*/
|
|
84
|
+
export async function discover(baseUrl) {
|
|
85
|
+
const prm = await getJson(`${baseUrl}/.well-known/oauth-protected-resource`, "protected-resource metadata");
|
|
86
|
+
const servers = prm["authorization_servers"];
|
|
87
|
+
const asBase = Array.isArray(servers) && typeof servers[0] === "string" ? servers[0] : baseUrl;
|
|
88
|
+
const doc = await getJson(`${asBase.replace(/\/$/, "")}/.well-known/oauth-authorization-server`, "authorization-server metadata");
|
|
89
|
+
const issuer = String(doc["issuer"] ?? asBase);
|
|
90
|
+
const meta = {
|
|
91
|
+
issuer,
|
|
92
|
+
authorization_endpoint: String(doc["authorization_endpoint"] ?? ""),
|
|
93
|
+
token_endpoint: String(doc["token_endpoint"] ?? ""),
|
|
94
|
+
device_authorization_endpoint: optString(doc["device_authorization_endpoint"]),
|
|
95
|
+
registration_endpoint: optString(doc["registration_endpoint"]),
|
|
96
|
+
code_challenge_methods_supported: Array.isArray(doc["code_challenge_methods_supported"])
|
|
97
|
+
? doc["code_challenge_methods_supported"]
|
|
98
|
+
: undefined,
|
|
99
|
+
};
|
|
100
|
+
if (!meta.authorization_endpoint || !meta.token_endpoint) {
|
|
101
|
+
throw new Error("authorization-server metadata is missing authorization_endpoint/token_endpoint");
|
|
102
|
+
}
|
|
103
|
+
assertSafeEndpoint(meta.authorization_endpoint, issuer, "authorization_endpoint");
|
|
104
|
+
assertSafeEndpoint(meta.token_endpoint, issuer, "token_endpoint");
|
|
105
|
+
if (meta.device_authorization_endpoint) {
|
|
106
|
+
assertSafeEndpoint(meta.device_authorization_endpoint, issuer, "device_authorization_endpoint");
|
|
107
|
+
}
|
|
108
|
+
if (meta.registration_endpoint) {
|
|
109
|
+
assertSafeEndpoint(meta.registration_endpoint, issuer, "registration_endpoint");
|
|
110
|
+
}
|
|
111
|
+
// Refuse to downgrade. If the server does not advertise S256 we would be left with
|
|
112
|
+
// `plain`, which offers no protection against an intercepted authorization code.
|
|
113
|
+
const methods = meta.code_challenge_methods_supported;
|
|
114
|
+
if (methods && !methods.includes("S256")) {
|
|
115
|
+
throw new Error("authorization server does not advertise PKCE S256 — refusing to continue");
|
|
116
|
+
}
|
|
117
|
+
return meta;
|
|
118
|
+
}
|
|
119
|
+
function optString(v) {
|
|
120
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
121
|
+
}
|
|
122
|
+
/** The DCR shim. Any failure falls back to the caller's default client_id. */
|
|
123
|
+
export async function registerClient(meta, fallbackClientId) {
|
|
124
|
+
if (!meta.registration_endpoint)
|
|
125
|
+
return fallbackClientId;
|
|
126
|
+
try {
|
|
127
|
+
const res = await fetch(meta.registration_endpoint, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: { "content-type": "application/json" },
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
client_name: "Blue Note harness CLI",
|
|
132
|
+
redirect_uris: ["http://127.0.0.1/callback"],
|
|
133
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
134
|
+
response_types: ["code"],
|
|
135
|
+
token_endpoint_auth_method: "none",
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok)
|
|
139
|
+
return fallbackClientId;
|
|
140
|
+
const body = (await res.json());
|
|
141
|
+
return optString(body["client_id"]) ?? fallbackClientId;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return fallbackClientId;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function postForm(url, form) {
|
|
148
|
+
return fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
151
|
+
body: new URLSearchParams(form).toString(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Browser flow. Resolves with an access token, or rejects on timeout / user denial.
|
|
156
|
+
*
|
|
157
|
+
* The listener binds 127.0.0.1 EXPLICITLY (not 0.0.0.0) so nothing off-box can reach the
|
|
158
|
+
* callback, and it shuts down the moment it has an answer — a lingering listener holding a
|
|
159
|
+
* valid `state` is a needless window.
|
|
160
|
+
*/
|
|
161
|
+
export async function authorizeViaBrowser(opts) {
|
|
162
|
+
const { meta, clientId, scope, resource } = opts;
|
|
163
|
+
const log = opts.log ?? (() => { });
|
|
164
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 60_000;
|
|
165
|
+
const pkce = createPkce();
|
|
166
|
+
const state = base64url(randomBytes(24));
|
|
167
|
+
const { code, redirectUri, server } = await new Promise((resolve, reject) => {
|
|
168
|
+
const srv = createServer((req, res) => {
|
|
169
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
170
|
+
if (url.pathname !== "/callback") {
|
|
171
|
+
res.writeHead(404).end("not found");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const err = url.searchParams.get("error");
|
|
175
|
+
const gotState = url.searchParams.get("state") ?? "";
|
|
176
|
+
const gotCode = url.searchParams.get("code") ?? "";
|
|
177
|
+
if (err) {
|
|
178
|
+
respond(res, `Authorization failed: ${err}`);
|
|
179
|
+
reject(new Error(`authorization denied: ${err}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!safeEqual(gotState, state)) {
|
|
183
|
+
// A mismatched state means this callback did not come from the request we made.
|
|
184
|
+
respond(res, "State mismatch — request rejected. You can close this tab.");
|
|
185
|
+
reject(new Error("state mismatch — possible CSRF, aborting"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!gotCode) {
|
|
189
|
+
respond(res, "No authorization code returned.");
|
|
190
|
+
reject(new Error("no authorization code in callback"));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
respond(res, "Signed in. You can close this tab and return to the terminal.");
|
|
194
|
+
const addr = srv.address();
|
|
195
|
+
resolve({ code: gotCode, redirectUri: loopbackUri(addr.port), server: srv });
|
|
196
|
+
});
|
|
197
|
+
srv.on("error", reject);
|
|
198
|
+
// Port 0 = let the OS pick a free ephemeral port.
|
|
199
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
200
|
+
const addr = srv.address();
|
|
201
|
+
const authUrl = new URL(meta.authorization_endpoint);
|
|
202
|
+
authUrl.searchParams.set("response_type", "code");
|
|
203
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
204
|
+
authUrl.searchParams.set("redirect_uri", loopbackUri(addr.port));
|
|
205
|
+
authUrl.searchParams.set("scope", scope);
|
|
206
|
+
authUrl.searchParams.set("state", state);
|
|
207
|
+
authUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
208
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
209
|
+
// RFC 8707: name the resource this token is for, so it cannot be replayed elsewhere.
|
|
210
|
+
authUrl.searchParams.set("resource", resource);
|
|
211
|
+
log(`Opening your browser to sign in to Blue Note ID…`);
|
|
212
|
+
log(` If it does not open, paste this into a browser:\n ${authUrl.toString()}\n`);
|
|
213
|
+
// Say how long the window is. Without this the process just dies at the deadline with no
|
|
214
|
+
// prior warning, which reads as a broken CLI rather than "you had five minutes".
|
|
215
|
+
log(`Waiting for you to sign in… (this window closes in ${Math.round(timeoutMs / 1000)}s — widen it with --timeout)`);
|
|
216
|
+
if (opts.openBrowser !== false)
|
|
217
|
+
openInBrowser(authUrl.toString());
|
|
218
|
+
});
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
srv.close();
|
|
221
|
+
reject(new Error(`timed out after ${Math.round(timeoutMs / 1000)}s waiting for the browser`));
|
|
222
|
+
}, timeoutMs).unref();
|
|
223
|
+
});
|
|
224
|
+
server.close();
|
|
225
|
+
const res = await postForm(meta.token_endpoint, {
|
|
226
|
+
grant_type: "authorization_code",
|
|
227
|
+
code,
|
|
228
|
+
redirect_uri: redirectUri,
|
|
229
|
+
client_id: clientId,
|
|
230
|
+
code_verifier: pkce.verifier,
|
|
231
|
+
resource,
|
|
232
|
+
});
|
|
233
|
+
if (!res.ok) {
|
|
234
|
+
throw new Error(`token exchange failed: ${res.status} ${await safeText(res)}`);
|
|
235
|
+
}
|
|
236
|
+
return (await res.json());
|
|
237
|
+
}
|
|
238
|
+
function loopbackUri(port) {
|
|
239
|
+
return `http://127.0.0.1:${port}/callback`;
|
|
240
|
+
}
|
|
241
|
+
function respond(res, message) {
|
|
242
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
243
|
+
res.end(`<!doctype html><meta charset="utf-8"><title>Blue Note Memory</title>` +
|
|
244
|
+
`<body style="font:16px/1.5 system-ui,sans-serif;padding:3rem;max-width:34rem;margin:auto">` +
|
|
245
|
+
`<h1 style="font-size:1.25rem">Blue Note Memory</h1><p>${escapeHtml(message)}</p></body>`);
|
|
246
|
+
}
|
|
247
|
+
function escapeHtml(s) {
|
|
248
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Device-code flow — for a headless box, an SSH session, or any machine where opening a
|
|
252
|
+
* browser is not possible. Authentik supports this grant natively and `blue-note-mem-mcp`
|
|
253
|
+
* already has it enabled, so there is no server-side work behind this path.
|
|
254
|
+
*/
|
|
255
|
+
export async function authorizeViaDevice(opts) {
|
|
256
|
+
const { meta, clientId, scope, resource } = opts;
|
|
257
|
+
const log = opts.log ?? (() => { });
|
|
258
|
+
if (!meta.device_authorization_endpoint) {
|
|
259
|
+
throw new Error("this server does not advertise a device_authorization_endpoint");
|
|
260
|
+
}
|
|
261
|
+
const startRes = await postForm(meta.device_authorization_endpoint, {
|
|
262
|
+
client_id: clientId,
|
|
263
|
+
scope,
|
|
264
|
+
});
|
|
265
|
+
if (!startRes.ok) {
|
|
266
|
+
throw new Error(`device authorization failed: ${startRes.status} ${await safeText(startRes)}`);
|
|
267
|
+
}
|
|
268
|
+
const start = (await startRes.json());
|
|
269
|
+
const deviceCode = String(start["device_code"] ?? "");
|
|
270
|
+
const userCode = String(start["user_code"] ?? "");
|
|
271
|
+
const verifyUri = String(start["verification_uri_complete"] ?? start["verification_uri"] ?? "");
|
|
272
|
+
// RFC 8628 §3.5: honour the server's interval; default 5s. Poll faster and you get
|
|
273
|
+
// slow_down responses, which only makes the flow take longer.
|
|
274
|
+
let intervalMs = Math.max(1, Number(start["interval"] ?? 5)) * 1000;
|
|
275
|
+
const serverExpiry = Date.now() + Math.max(60, Number(start["expires_in"] ?? 600)) * 1000;
|
|
276
|
+
// A client-side --timeout can only ever SHORTEN this. The device code's lifetime belongs to
|
|
277
|
+
// the IdP (ours issues 60s), so waiting past it would only poll a code the server has already
|
|
278
|
+
// discarded — take whichever deadline lands first.
|
|
279
|
+
const expiresAt = opts.timeoutMs
|
|
280
|
+
? Math.min(serverExpiry, Date.now() + opts.timeoutMs)
|
|
281
|
+
: serverExpiry;
|
|
282
|
+
log(`\n Open: ${verifyUri}`);
|
|
283
|
+
log(` Code: ${userCode}\n`);
|
|
284
|
+
// Surface the window: our IdP issues 60-second device codes, which is tight enough that a
|
|
285
|
+
// user who wanders off to find their password comes back to an expired one. Saying so beats
|
|
286
|
+
// a bare "expired" error after the fact.
|
|
287
|
+
log(`Waiting for you to approve… (this code expires in ${Math.round((expiresAt - Date.now()) / 1000)}s)`);
|
|
288
|
+
for (;;) {
|
|
289
|
+
if (Date.now() > expiresAt)
|
|
290
|
+
throw new Error("device code expired before approval");
|
|
291
|
+
await sleep(intervalMs);
|
|
292
|
+
const res = await postForm(meta.token_endpoint, {
|
|
293
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
294
|
+
device_code: deviceCode,
|
|
295
|
+
client_id: clientId,
|
|
296
|
+
resource,
|
|
297
|
+
});
|
|
298
|
+
if (res.ok)
|
|
299
|
+
return (await res.json());
|
|
300
|
+
const body = (await res.json().catch(() => ({})));
|
|
301
|
+
const error = String(body["error"] ?? "");
|
|
302
|
+
if (error === "authorization_pending")
|
|
303
|
+
continue;
|
|
304
|
+
if (error === "slow_down") {
|
|
305
|
+
intervalMs += 5000;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// Blue Note ID (Authentik) answers a still-pending device grant with `invalid_grant`
|
|
309
|
+
// instead of RFC 8628 §3.5's `authorization_pending`. A spec-literal client therefore
|
|
310
|
+
// aborts on the FIRST poll — roughly five seconds in, long before a human can approve —
|
|
311
|
+
// which made --device unusable against our own IdP. Treat it as pending: the expiresAt
|
|
312
|
+
// deadline above still bounds the loop, so a genuinely bad or revoked device_code ends
|
|
313
|
+
// as "device code expired before approval" rather than hanging forever.
|
|
314
|
+
if (error === "invalid_grant")
|
|
315
|
+
continue;
|
|
316
|
+
throw new Error(`device authorization failed: ${error || res.status}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function sleep(ms) {
|
|
320
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
321
|
+
}
|
|
322
|
+
async function safeText(res) {
|
|
323
|
+
try {
|
|
324
|
+
return (await res.text()).slice(0, 300);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Best-effort browser launch. Failure is non-fatal — the URL is always printed first, so a
|
|
332
|
+
* machine with no default browser still completes the flow by copy/paste.
|
|
333
|
+
*/
|
|
334
|
+
function openInBrowser(url) {
|
|
335
|
+
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
336
|
+
// `cmd /c start ""` needs the empty title argument or a quoted URL is taken as the title.
|
|
337
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
338
|
+
try {
|
|
339
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
/* printed above; nothing to do */
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a bundle into files on disk.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 THE MERGE RULE IS NOT A NICETY — IT IS A SCAR.
|
|
5
|
+
* On 2026-07-17 the predecessor of this code (`bnl-provision-site-mcpjson.sh`) blindly
|
|
6
|
+
* overwrote three pre-existing `.mcp.json` files that defined unrelated servers. Two were
|
|
7
|
+
* recoverable from git; one (`boris-console`) had never been tracked and had to be
|
|
8
|
+
* reconstructed by hand. So: every write reads what is on disk first and replaces ONLY the
|
|
9
|
+
* keys it owns. A file that is not valid JSON is SKIPPED with a message — never rewritten,
|
|
10
|
+
* because "it looked broken" is exactly when someone's hand edit is sitting in it.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is planned as pure data first (`FileChange[]`) and applied second, so
|
|
13
|
+
* `--dry-run` shows precisely what would happen and the merge logic is unit-testable
|
|
14
|
+
* without touching a filesystem.
|
|
15
|
+
*/
|
|
16
|
+
export interface FileChange {
|
|
17
|
+
path: string;
|
|
18
|
+
/** Current contents, or null when the file does not exist. */
|
|
19
|
+
before: string | null;
|
|
20
|
+
/** Contents to write, or null when nothing should be written. */
|
|
21
|
+
after: string | null;
|
|
22
|
+
/** Set when we deliberately declined to write; `after` is null and this explains why. */
|
|
23
|
+
skipped?: string;
|
|
24
|
+
/** Advisory shown to the user after a successful apply. */
|
|
25
|
+
note?: string;
|
|
26
|
+
}
|
|
27
|
+
/** True when the change would actually alter the file. */
|
|
28
|
+
export declare function isNoop(c: FileChange): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Merge our server entries into an existing `.mcp.json`.
|
|
31
|
+
*
|
|
32
|
+
* `servers` comes straight from the bundle's `mcp_json.mcpServers`, which is TOKENLESS —
|
|
33
|
+
* OAuth-capable hosts discover auth from the endpoint's 401 and keep their own credentials.
|
|
34
|
+
* That is what makes the resulting file safe to commit.
|
|
35
|
+
*/
|
|
36
|
+
export declare function planMcpJson(path: string, existing: string | null, servers: Record<string, unknown>): FileChange;
|
|
37
|
+
/**
|
|
38
|
+
* `.claude/settings.local.json` — an `env` block Claude Code applies to every session.
|
|
39
|
+
*
|
|
40
|
+
* WHY THIS EXISTS AT ALL: the VS Code extension launches `claude` without sourcing a shell
|
|
41
|
+
* env file, so a `${VAR}` reference in `.mcp.json` would not expand there. This file is how
|
|
42
|
+
* the harness works from the VS Code button rather than only from `source .env && claude`.
|
|
43
|
+
* Only written when a token was minted — the OAuth path needs no env at all.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planSettingsLocal(path: string, existing: string | null, env: Record<string, string>): FileChange;
|
|
46
|
+
/**
|
|
47
|
+
* Codex `config.toml`.
|
|
48
|
+
*
|
|
49
|
+
* We do NOT parse TOML (that would mean a dependency, and this package has none). Instead:
|
|
50
|
+
* if a `[mcp_servers.<name>]` table already exists we leave the file completely alone and
|
|
51
|
+
* hand the user the snippet; otherwise we APPEND the table. Appending is safe because TOML
|
|
52
|
+
* tables are order-independent, and appending can never corrupt what is already there.
|
|
53
|
+
*
|
|
54
|
+
* The token itself never goes in here — it goes to `~/.codex/.env`, referenced by
|
|
55
|
+
* `bearer_token_env_var`, so the config file stays safe to read over someone's shoulder.
|
|
56
|
+
*/
|
|
57
|
+
export declare function planCodexConfig(path: string, existing: string | null, serverName: string, mcpUrl: string, tokenEnvVar: string): FileChange;
|
|
58
|
+
/** `~/.codex/.env` — one KEY=value line, replaced in place if already present. */
|
|
59
|
+
export declare function planEnvVar(path: string, existing: string | null, key: string, value: string): FileChange;
|
|
60
|
+
/**
|
|
61
|
+
* `state/routing/{providers,profiles}.server.yaml` — materialized from the console's hosted
|
|
62
|
+
* routing. These are plain overwrites, and that is correct: the files live in a CLI-owned
|
|
63
|
+
* directory and their own banner says "Do not edit by hand". A local `providers.yaml` still
|
|
64
|
+
* takes precedence in the resolver, so nobody's hand-tuned routing is affected.
|
|
65
|
+
*/
|
|
66
|
+
export declare function planRoutingFile(path: string, existing: string | null, body: string): FileChange;
|
|
67
|
+
/**
|
|
68
|
+
* Guard: refuse to write anything that looks like a credential into a repo-visible file.
|
|
69
|
+
*
|
|
70
|
+
* The default path must never mint or persist a secret, so this is a hard assertion rather
|
|
71
|
+
* than a warning — if it ever fires, a change has reintroduced exactly the problem this
|
|
72
|
+
* tool exists to remove.
|
|
73
|
+
*/
|
|
74
|
+
export declare function assertNoSecret(c: FileChange): void;
|
|
75
|
+
export declare function readIfExists(path: string): string | null;
|
|
76
|
+
export declare function apply(changes: FileChange[], dryRun: boolean): void;
|
package/dist/writers.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a bundle into files on disk.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 THE MERGE RULE IS NOT A NICETY — IT IS A SCAR.
|
|
5
|
+
* On 2026-07-17 the predecessor of this code (`bnl-provision-site-mcpjson.sh`) blindly
|
|
6
|
+
* overwrote three pre-existing `.mcp.json` files that defined unrelated servers. Two were
|
|
7
|
+
* recoverable from git; one (`boris-console`) had never been tracked and had to be
|
|
8
|
+
* reconstructed by hand. So: every write reads what is on disk first and replaces ONLY the
|
|
9
|
+
* keys it owns. A file that is not valid JSON is SKIPPED with a message — never rewritten,
|
|
10
|
+
* because "it looked broken" is exactly when someone's hand edit is sitting in it.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is planned as pure data first (`FileChange[]`) and applied second, so
|
|
13
|
+
* `--dry-run` shows precisely what would happen and the merge logic is unit-testable
|
|
14
|
+
* without touching a filesystem.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
17
|
+
import { dirname } from "node:path";
|
|
18
|
+
/** True when the change would actually alter the file. */
|
|
19
|
+
export function isNoop(c) {
|
|
20
|
+
return c.after === null || c.after === c.before;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Merge our server entries into an existing `.mcp.json`.
|
|
24
|
+
*
|
|
25
|
+
* `servers` comes straight from the bundle's `mcp_json.mcpServers`, which is TOKENLESS —
|
|
26
|
+
* OAuth-capable hosts discover auth from the endpoint's 401 and keep their own credentials.
|
|
27
|
+
* That is what makes the resulting file safe to commit.
|
|
28
|
+
*/
|
|
29
|
+
export function planMcpJson(path, existing, servers) {
|
|
30
|
+
let doc = {};
|
|
31
|
+
if (existing !== null && existing.trim() !== "") {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(existing);
|
|
34
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
35
|
+
throw new Error("not a JSON object");
|
|
36
|
+
}
|
|
37
|
+
doc = parsed;
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return {
|
|
41
|
+
path,
|
|
42
|
+
before: existing,
|
|
43
|
+
after: null,
|
|
44
|
+
skipped: `existing .mcp.json is not valid JSON (${e.message}) — ` +
|
|
45
|
+
`left untouched. Fix or remove it by hand, then re-run.`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const current = typeof doc["mcpServers"] === "object" && doc["mcpServers"] !== null && !Array.isArray(doc["mcpServers"])
|
|
50
|
+
? doc["mcpServers"]
|
|
51
|
+
: {};
|
|
52
|
+
// Only our own keys are replaced. Any other server entry survives byte-for-byte through
|
|
53
|
+
// the round trip, which is the whole point of parsing rather than templating.
|
|
54
|
+
doc["mcpServers"] = { ...current, ...servers };
|
|
55
|
+
return { path, before: existing, after: JSON.stringify(doc, null, 2) + "\n" };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `.claude/settings.local.json` — an `env` block Claude Code applies to every session.
|
|
59
|
+
*
|
|
60
|
+
* WHY THIS EXISTS AT ALL: the VS Code extension launches `claude` without sourcing a shell
|
|
61
|
+
* env file, so a `${VAR}` reference in `.mcp.json` would not expand there. This file is how
|
|
62
|
+
* the harness works from the VS Code button rather than only from `source .env && claude`.
|
|
63
|
+
* Only written when a token was minted — the OAuth path needs no env at all.
|
|
64
|
+
*/
|
|
65
|
+
export function planSettingsLocal(path, existing, env) {
|
|
66
|
+
let doc = {};
|
|
67
|
+
if (existing !== null && existing.trim() !== "") {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(existing);
|
|
70
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
71
|
+
throw new Error("not a JSON object");
|
|
72
|
+
}
|
|
73
|
+
doc = parsed;
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
return {
|
|
77
|
+
path,
|
|
78
|
+
before: existing,
|
|
79
|
+
after: null,
|
|
80
|
+
skipped: `existing settings.local.json is not valid JSON (${e.message}) — left untouched.`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const currentEnv = typeof doc["env"] === "object" && doc["env"] !== null && !Array.isArray(doc["env"])
|
|
85
|
+
? doc["env"]
|
|
86
|
+
: {};
|
|
87
|
+
doc["env"] = { ...currentEnv, ...env };
|
|
88
|
+
return {
|
|
89
|
+
path,
|
|
90
|
+
before: existing,
|
|
91
|
+
after: JSON.stringify(doc, null, 2) + "\n",
|
|
92
|
+
note: "holds a plaintext token — make sure .claude/settings.local.json is gitignored",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Codex `config.toml`.
|
|
97
|
+
*
|
|
98
|
+
* We do NOT parse TOML (that would mean a dependency, and this package has none). Instead:
|
|
99
|
+
* if a `[mcp_servers.<name>]` table already exists we leave the file completely alone and
|
|
100
|
+
* hand the user the snippet; otherwise we APPEND the table. Appending is safe because TOML
|
|
101
|
+
* tables are order-independent, and appending can never corrupt what is already there.
|
|
102
|
+
*
|
|
103
|
+
* The token itself never goes in here — it goes to `~/.codex/.env`, referenced by
|
|
104
|
+
* `bearer_token_env_var`, so the config file stays safe to read over someone's shoulder.
|
|
105
|
+
*/
|
|
106
|
+
export function planCodexConfig(path, existing, serverName, mcpUrl, tokenEnvVar) {
|
|
107
|
+
const table = `[mcp_servers.${serverName}]`;
|
|
108
|
+
const block = `\n# Added by @bluenotelogic/harness. Codex cannot do OAuth yet, so it reads a token\n` +
|
|
109
|
+
`# from the environment; the value lives in ~/.codex/.env, never in this file.\n` +
|
|
110
|
+
`${table}\n` +
|
|
111
|
+
`url = "${mcpUrl}"\n` +
|
|
112
|
+
`bearer_token_env_var = "${tokenEnvVar}"\n`;
|
|
113
|
+
if (existing !== null && existing.includes(table)) {
|
|
114
|
+
return {
|
|
115
|
+
path,
|
|
116
|
+
before: existing,
|
|
117
|
+
after: null,
|
|
118
|
+
skipped: `${table} already present — left untouched. If the URL changed, update it by hand:\n` +
|
|
119
|
+
block.trim(),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const base = existing ?? "";
|
|
123
|
+
const sep = base === "" || base.endsWith("\n") ? "" : "\n";
|
|
124
|
+
return { path, before: existing, after: base + sep + block };
|
|
125
|
+
}
|
|
126
|
+
/** `~/.codex/.env` — one KEY=value line, replaced in place if already present. */
|
|
127
|
+
export function planEnvVar(path, existing, key, value) {
|
|
128
|
+
const line = `${key}=${value}`;
|
|
129
|
+
const base = existing ?? "";
|
|
130
|
+
const re = new RegExp(`^${escapeRe(key)}=.*$`, "m");
|
|
131
|
+
const after = re.test(base)
|
|
132
|
+
? base.replace(re, line)
|
|
133
|
+
: base + (base === "" || base.endsWith("\n") ? "" : "\n") + line + "\n";
|
|
134
|
+
return {
|
|
135
|
+
path,
|
|
136
|
+
before: existing,
|
|
137
|
+
after,
|
|
138
|
+
note: "holds a plaintext token — never commit it",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function escapeRe(s) {
|
|
142
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* `state/routing/{providers,profiles}.server.yaml` — materialized from the console's hosted
|
|
146
|
+
* routing. These are plain overwrites, and that is correct: the files live in a CLI-owned
|
|
147
|
+
* directory and their own banner says "Do not edit by hand". A local `providers.yaml` still
|
|
148
|
+
* takes precedence in the resolver, so nobody's hand-tuned routing is affected.
|
|
149
|
+
*/
|
|
150
|
+
export function planRoutingFile(path, existing, body) {
|
|
151
|
+
return { path, before: existing, after: body.endsWith("\n") ? body : body + "\n" };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Guard: refuse to write anything that looks like a credential into a repo-visible file.
|
|
155
|
+
*
|
|
156
|
+
* The default path must never mint or persist a secret, so this is a hard assertion rather
|
|
157
|
+
* than a warning — if it ever fires, a change has reintroduced exactly the problem this
|
|
158
|
+
* tool exists to remove.
|
|
159
|
+
*/
|
|
160
|
+
export function assertNoSecret(c) {
|
|
161
|
+
if (c.after === null)
|
|
162
|
+
return;
|
|
163
|
+
const found = c.after.match(/\b(cmem|mcp)_[A-Za-z0-9_-]{16,}/);
|
|
164
|
+
if (found) {
|
|
165
|
+
throw new Error(`refusing to write what looks like a live token into ${c.path} ` +
|
|
166
|
+
`(matched ${found[0].slice(0, 8)}…). Tokens belong in ~/.codex/.env or ` +
|
|
167
|
+
`.claude/settings.local.json, never in a committed config.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function readIfExists(path) {
|
|
171
|
+
try {
|
|
172
|
+
return readFileSync(path, "utf8");
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export function apply(changes, dryRun) {
|
|
179
|
+
for (const c of changes) {
|
|
180
|
+
if (c.after === null || isNoop(c))
|
|
181
|
+
continue;
|
|
182
|
+
if (dryRun)
|
|
183
|
+
continue;
|
|
184
|
+
const dir = dirname(c.path);
|
|
185
|
+
if (!existsSync(dir))
|
|
186
|
+
mkdirSync(dir, { recursive: true });
|
|
187
|
+
writeFileSync(c.path, c.after, "utf8");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=writers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writers.js","sourceRoot":"","sources":["../src/writers.ts"],"names":[],"mappings":"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"}
|