@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/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bnl-harness — point this machine at Blue Note Memory in one command.
|
|
4
|
+
*
|
|
5
|
+
* npx @bluenotelogic/harness init
|
|
6
|
+
*
|
|
7
|
+
* THE PROBLEM THIS SOLVES: configuring a machine used to mean pasting a bearer token by
|
|
8
|
+
* hand into `.mcp.json`, `~/.claude.json`, `~/.codex/config.toml` and a VS Code roaming
|
|
9
|
+
* profile — and rotating anything meant editing every one of them again.
|
|
10
|
+
*
|
|
11
|
+
* THE IMPORTANT PART — the `.mcp.json` this writes carries NO TOKEN:
|
|
12
|
+
*
|
|
13
|
+
* { "mcpServers": { "mem": { "type": "http", "url": "https://mem.bnlgit.com/mcp" } } }
|
|
14
|
+
*
|
|
15
|
+
* OAuth-capable hosts (Claude Code, VS Code) discover auth from the endpoint's 401 +
|
|
16
|
+
* WWW-Authenticate, run the browser flow against Blue Note ID, and store their own
|
|
17
|
+
* credentials. Nothing secret is written into a repo file, so the file is safe to commit
|
|
18
|
+
* and there is nothing to rotate.
|
|
19
|
+
*
|
|
20
|
+
* Codex and CI cannot do that yet, so `--mint-token` exists for them — and it writes the
|
|
21
|
+
* token to `~/.codex/.env`, never into a repo. Minting is OPT-IN precisely so the default
|
|
22
|
+
* path creates no credential and no database row.
|
|
23
|
+
*/
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { join, resolve } from "node:path";
|
|
26
|
+
import { discover, registerClient, authorizeViaBrowser, authorizeViaDevice, } from "./oauth.js";
|
|
27
|
+
import { exchangeForTenantToken, fetchBundle, SubscriptionRequiredError, } from "./bundle.js";
|
|
28
|
+
import { planMcpJson, planSettingsLocal, planCodexConfig, planEnvVar, planRoutingFile, assertNoSecret, readIfExists, isNoop, apply, } from "./writers.js";
|
|
29
|
+
const DEFAULT_BASE_URL = "https://mem.bnlgit.com";
|
|
30
|
+
const DEFAULT_CLIENT_ID = "blue-note-mem-mcp";
|
|
31
|
+
/**
|
|
32
|
+
* `blue_note_identity`, NOT `email`. The two are easy to confuse and the failure is silent
|
|
33
|
+
* until the very last step:
|
|
34
|
+
*
|
|
35
|
+
* The `blue-note-mem-mcp` provider has exactly four scope mappings attached — openid, profile,
|
|
36
|
+
* offline_access and blue_note_identity. The stock `email` mapping is deliberately NOT among
|
|
37
|
+
* them, and Authentik ignores a scope it has no mapping for rather than refusing it. So asking
|
|
38
|
+
* for `email` succeeded all the way through consent and the code exchange, then produced a
|
|
39
|
+
* userinfo document with no `email_verified`, which `/api/oidc_exchange.php` rejects as
|
|
40
|
+
* "A verified email is required" — surfaced to the user as a 401 that reads like a bad token.
|
|
41
|
+
*
|
|
42
|
+
* `blue_note_identity` ("Blue Note verified identity") is the mapping that supplies email and
|
|
43
|
+
* email_verified. This matches what the browser login has always requested — see
|
|
44
|
+
* BlueNoteOidc.php:201 in caveau-mem, which is the same identity resolution this trades into.
|
|
45
|
+
*/
|
|
46
|
+
const DEFAULT_SCOPE = "openid profile blue_note_identity offline_access";
|
|
47
|
+
const TOKEN_ENV_VAR = "BNL_MEMORY_MCP_TOKEN";
|
|
48
|
+
/**
|
|
49
|
+
* `--timeout`, in seconds. Refused rather than coerced: a typo'd `--timeout abc` silently
|
|
50
|
+
* becoming NaN — and therefore an instant timeout — would be a second, quieter version of the
|
|
51
|
+
* exact failure this flag exists to fix.
|
|
52
|
+
*/
|
|
53
|
+
function parseTimeout(raw) {
|
|
54
|
+
const sec = Number(raw);
|
|
55
|
+
if (!Number.isInteger(sec) || sec < 30 || sec > 3600) {
|
|
56
|
+
fail(`--timeout wants whole seconds between 30 and 3600, got: ${raw}`);
|
|
57
|
+
}
|
|
58
|
+
return sec * 1000;
|
|
59
|
+
}
|
|
60
|
+
function parseArgs(argv) {
|
|
61
|
+
const opts = {
|
|
62
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
63
|
+
dir: process.cwd(),
|
|
64
|
+
dryRun: false,
|
|
65
|
+
device: false,
|
|
66
|
+
token: null,
|
|
67
|
+
mintToken: false,
|
|
68
|
+
harnessHome: null,
|
|
69
|
+
serverName: null,
|
|
70
|
+
clientId: DEFAULT_CLIENT_ID,
|
|
71
|
+
openBrowser: true,
|
|
72
|
+
codex: false,
|
|
73
|
+
timeoutMs: null,
|
|
74
|
+
};
|
|
75
|
+
let command = "init";
|
|
76
|
+
const rest = [...argv];
|
|
77
|
+
if (rest[0] && !rest[0].startsWith("-"))
|
|
78
|
+
command = rest.shift();
|
|
79
|
+
while (rest.length) {
|
|
80
|
+
const arg = rest.shift();
|
|
81
|
+
const next = () => {
|
|
82
|
+
const v = rest.shift();
|
|
83
|
+
if (v === undefined)
|
|
84
|
+
fail(`${arg} needs a value`);
|
|
85
|
+
return v;
|
|
86
|
+
};
|
|
87
|
+
switch (arg) {
|
|
88
|
+
case "--url":
|
|
89
|
+
opts.baseUrl = next().replace(/\/$/, "");
|
|
90
|
+
break;
|
|
91
|
+
case "--dir":
|
|
92
|
+
opts.dir = resolve(next());
|
|
93
|
+
break;
|
|
94
|
+
case "--dry-run":
|
|
95
|
+
opts.dryRun = true;
|
|
96
|
+
break;
|
|
97
|
+
case "--device":
|
|
98
|
+
opts.device = true;
|
|
99
|
+
break;
|
|
100
|
+
case "--token":
|
|
101
|
+
opts.token = next();
|
|
102
|
+
break;
|
|
103
|
+
case "--mint-token":
|
|
104
|
+
opts.mintToken = true;
|
|
105
|
+
break;
|
|
106
|
+
case "--codex":
|
|
107
|
+
opts.codex = true;
|
|
108
|
+
opts.mintToken = true;
|
|
109
|
+
break;
|
|
110
|
+
case "--harness-home":
|
|
111
|
+
opts.harnessHome = resolve(next());
|
|
112
|
+
break;
|
|
113
|
+
case "--server-name":
|
|
114
|
+
opts.serverName = next();
|
|
115
|
+
break;
|
|
116
|
+
case "--client-id":
|
|
117
|
+
opts.clientId = next();
|
|
118
|
+
break;
|
|
119
|
+
case "--no-browser":
|
|
120
|
+
opts.openBrowser = false;
|
|
121
|
+
break;
|
|
122
|
+
case "--timeout":
|
|
123
|
+
opts.timeoutMs = parseTimeout(next());
|
|
124
|
+
break;
|
|
125
|
+
case "-h":
|
|
126
|
+
case "--help":
|
|
127
|
+
command = "help";
|
|
128
|
+
break;
|
|
129
|
+
case "-v":
|
|
130
|
+
case "--version":
|
|
131
|
+
command = "version";
|
|
132
|
+
break;
|
|
133
|
+
default: fail(`unknown argument: ${arg}\nRun \`bnl-harness --help\`.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { command, opts };
|
|
137
|
+
}
|
|
138
|
+
const HELP = `
|
|
139
|
+
bnl-harness — configure this machine for Blue Note Memory
|
|
140
|
+
|
|
141
|
+
npx @bluenotelogic/harness init [options]
|
|
142
|
+
|
|
143
|
+
Options
|
|
144
|
+
--url <base> Server base URL (default ${DEFAULT_BASE_URL})
|
|
145
|
+
--dir <path> Repo to configure (default: current directory)
|
|
146
|
+
--dry-run Show every file that would change; write nothing
|
|
147
|
+
--device Device-code login, for headless boxes and SSH sessions
|
|
148
|
+
--token <token> Use an existing cmem_ token instead of logging in (CI)
|
|
149
|
+
--mint-token Also mint a token for hosts that cannot do OAuth
|
|
150
|
+
--codex Configure Codex too (implies --mint-token)
|
|
151
|
+
--harness-home <dir> Also materialize hosted routing into <dir>/state/routing/
|
|
152
|
+
--server-name <name> Override the MCP server key written into .mcp.json
|
|
153
|
+
--client-id <id> OAuth client_id (default ${DEFAULT_CLIENT_ID})
|
|
154
|
+
--no-browser Print the login URL instead of opening a browser
|
|
155
|
+
--timeout <seconds> How long to wait for you to sign in (default 300, max 3600).
|
|
156
|
+
With --device the server's own device-code lifetime still wins.
|
|
157
|
+
-h, --help This text
|
|
158
|
+
-v, --version Version
|
|
159
|
+
|
|
160
|
+
By default nothing secret is written: Claude Code and VS Code authenticate over OAuth and
|
|
161
|
+
keep their own credentials, so the .mcp.json this produces is safe to commit.
|
|
162
|
+
`;
|
|
163
|
+
async function main() {
|
|
164
|
+
const { command, opts } = parseArgs(process.argv.slice(2));
|
|
165
|
+
if (command === "help") {
|
|
166
|
+
console.log(HELP.trim());
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (command === "version") {
|
|
170
|
+
console.log(await version());
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (command !== "init")
|
|
174
|
+
fail(`unknown command: ${command}\nRun \`bnl-harness --help\`.`);
|
|
175
|
+
const bundle = await getBundle(opts);
|
|
176
|
+
const changes = planAll(bundle, opts);
|
|
177
|
+
report(changes, opts, bundle);
|
|
178
|
+
for (const c of changes)
|
|
179
|
+
assertNoSecret2(c, opts);
|
|
180
|
+
apply(changes, opts.dryRun);
|
|
181
|
+
if (!opts.dryRun)
|
|
182
|
+
printNextSteps(bundle, opts, changes);
|
|
183
|
+
}
|
|
184
|
+
/** Authenticate (unless given a token) and fetch the bundle. */
|
|
185
|
+
async function getBundle(opts) {
|
|
186
|
+
if (opts.token) {
|
|
187
|
+
log(`Using the token supplied on the command line (skipping login).`);
|
|
188
|
+
return fetchBundle(opts.baseUrl, opts.token, { mintToken: opts.mintToken });
|
|
189
|
+
}
|
|
190
|
+
log(`Discovering ${opts.baseUrl}…`);
|
|
191
|
+
const meta = await discover(opts.baseUrl);
|
|
192
|
+
const clientId = await registerClient(meta, opts.clientId);
|
|
193
|
+
const tokens = opts.device
|
|
194
|
+
? await authorizeViaDevice({
|
|
195
|
+
meta, clientId, scope: DEFAULT_SCOPE, resource: `${opts.baseUrl}/mcp`,
|
|
196
|
+
timeoutMs: opts.timeoutMs ?? undefined, log,
|
|
197
|
+
})
|
|
198
|
+
: await authorizeViaBrowser({
|
|
199
|
+
meta, clientId, scope: DEFAULT_SCOPE, resource: `${opts.baseUrl}/mcp`,
|
|
200
|
+
timeoutMs: opts.timeoutMs ?? undefined, openBrowser: opts.openBrowser, log,
|
|
201
|
+
});
|
|
202
|
+
log("Signed in. Resolving your memory tenant…");
|
|
203
|
+
let tenantToken;
|
|
204
|
+
try {
|
|
205
|
+
tenantToken = await exchangeForTenantToken(opts.baseUrl, tokens.access_token);
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
if (e instanceof SubscriptionRequiredError) {
|
|
209
|
+
// Authenticated fine — they just do not have the product. Say that, and say where to
|
|
210
|
+
// go, rather than leaving them re-trying a login that already worked.
|
|
211
|
+
fail(`${e.message}\n\n` +
|
|
212
|
+
`Your Blue Note Account signed in successfully — it just has no Blue Note Memory\n` +
|
|
213
|
+
`subscription attached. Start one at ${opts.baseUrl}/pricing.php`);
|
|
214
|
+
}
|
|
215
|
+
throw e;
|
|
216
|
+
}
|
|
217
|
+
return fetchBundle(opts.baseUrl, tenantToken, { mintToken: opts.mintToken });
|
|
218
|
+
}
|
|
219
|
+
/** Everything this run would write, as data. Nothing here touches the filesystem twice. */
|
|
220
|
+
function planAll(bundle, opts) {
|
|
221
|
+
const changes = [];
|
|
222
|
+
// --- The tokenless repo config: the whole point of the tool. --------------------------
|
|
223
|
+
let servers = bundle.mcp_json.mcpServers;
|
|
224
|
+
if (opts.serverName) {
|
|
225
|
+
const [firstKey] = Object.keys(servers);
|
|
226
|
+
if (firstKey)
|
|
227
|
+
servers = { [opts.serverName]: servers[firstKey] };
|
|
228
|
+
}
|
|
229
|
+
const mcpPath = join(opts.dir, ".mcp.json");
|
|
230
|
+
changes.push(planMcpJson(mcpPath, readIfExists(mcpPath), servers));
|
|
231
|
+
// --- Optional: a real credential, for hosts that cannot do OAuth. ---------------------
|
|
232
|
+
const minted = bundle.token?.value;
|
|
233
|
+
if (minted) {
|
|
234
|
+
const codexHome = process.env["CODEX_HOME"] ?? join(homedir(), ".codex");
|
|
235
|
+
const serverName = opts.serverName ?? Object.keys(servers)[0] ?? "mem";
|
|
236
|
+
if (opts.codex) {
|
|
237
|
+
const cfg = join(codexHome, "config.toml");
|
|
238
|
+
changes.push(planCodexConfig(cfg, readIfExists(cfg), serverName, bundle.endpoint.mcp_url, TOKEN_ENV_VAR));
|
|
239
|
+
}
|
|
240
|
+
const envPath = join(codexHome, ".env");
|
|
241
|
+
changes.push(planEnvVar(envPath, readIfExists(envPath), TOKEN_ENV_VAR, minted));
|
|
242
|
+
// The VS Code extension launches `claude` without a shell, so an env block in
|
|
243
|
+
// settings.local.json is what makes a token-based setup work from the editor button.
|
|
244
|
+
const settings = join(opts.dir, ".claude", "settings.local.json");
|
|
245
|
+
changes.push(planSettingsLocal(settings, readIfExists(settings), { [TOKEN_ENV_VAR]: minted }));
|
|
246
|
+
}
|
|
247
|
+
// --- Optional: materialize the hosted routing the console edits. -----------------------
|
|
248
|
+
if (opts.harnessHome && bundle.routing) {
|
|
249
|
+
const dir = join(opts.harnessHome, "state", "routing");
|
|
250
|
+
if (bundle.routing.providers_yaml) {
|
|
251
|
+
const p = join(dir, "providers.server.yaml");
|
|
252
|
+
changes.push(planRoutingFile(p, readIfExists(p), bundle.routing.providers_yaml));
|
|
253
|
+
}
|
|
254
|
+
if (bundle.routing.profiles_yaml) {
|
|
255
|
+
const p = join(dir, "profiles.server.yaml");
|
|
256
|
+
changes.push(planRoutingFile(p, readIfExists(p), bundle.routing.profiles_yaml));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return changes;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The secret guard applies to REPO files only. `~/.codex/.env` and
|
|
263
|
+
* `.claude/settings.local.json` are where a minted token is supposed to live — asserting
|
|
264
|
+
* against them would forbid the very thing `--mint-token` is for.
|
|
265
|
+
*/
|
|
266
|
+
function assertNoSecret2(c, opts) {
|
|
267
|
+
const credentialStores = [
|
|
268
|
+
join(opts.dir, ".claude", "settings.local.json"),
|
|
269
|
+
join(process.env["CODEX_HOME"] ?? join(homedir(), ".codex"), ".env"),
|
|
270
|
+
];
|
|
271
|
+
if (credentialStores.includes(c.path))
|
|
272
|
+
return;
|
|
273
|
+
assertNoSecret(c);
|
|
274
|
+
}
|
|
275
|
+
function report(changes, opts, bundle) {
|
|
276
|
+
console.log("");
|
|
277
|
+
console.log(` tenant ${bundle.tenant.slug || bundle.tenant.id}`);
|
|
278
|
+
console.log(` endpoint ${bundle.endpoint.mcp_url}`);
|
|
279
|
+
if (bundle.routing?.source)
|
|
280
|
+
console.log(` routing ${bundle.routing.source}`);
|
|
281
|
+
console.log("");
|
|
282
|
+
for (const c of changes) {
|
|
283
|
+
if (c.skipped) {
|
|
284
|
+
console.log(` SKIP ${c.path}\n ${c.skipped.replace(/\n/g, "\n ")}`);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (isNoop(c)) {
|
|
288
|
+
console.log(` same ${c.path}`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
console.log(` ${c.before === null ? "create" : "update"} ${c.path}`);
|
|
292
|
+
if (opts.dryRun && c.after) {
|
|
293
|
+
const body = c.after.split("\n").map((l) => ` │ ${l}`).join("\n");
|
|
294
|
+
console.log(body.replace(/\n\s+│\s*$/, ""));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
console.log("");
|
|
298
|
+
if (opts.dryRun)
|
|
299
|
+
console.log(" --dry-run: nothing was written.\n");
|
|
300
|
+
}
|
|
301
|
+
function printNextSteps(bundle, opts, changes) {
|
|
302
|
+
const oauth = !bundle.token;
|
|
303
|
+
console.log("Done.\n");
|
|
304
|
+
if (oauth) {
|
|
305
|
+
console.log(" Your .mcp.json carries no token — Claude Code and VS Code will authenticate");
|
|
306
|
+
console.log(" over OAuth on first use and keep their own credentials. It is safe to commit.\n");
|
|
307
|
+
}
|
|
308
|
+
console.log(` 1. Open ${opts.dir} in Claude Code or VS Code`);
|
|
309
|
+
console.log(` 2. The '${Object.keys(bundle.mcp_json.mcpServers)[0]}' server appears automatically`);
|
|
310
|
+
if (bundle.token) {
|
|
311
|
+
console.log(` 3. A token was minted into ${TOKEN_ENV_VAR} — it is in your Codex env file, not this repo`);
|
|
312
|
+
}
|
|
313
|
+
const notes = changes.filter((c) => c.note && !isNoop(c));
|
|
314
|
+
if (notes.length) {
|
|
315
|
+
console.log("");
|
|
316
|
+
for (const c of notes)
|
|
317
|
+
console.log(` NOTE ${c.path}: ${c.note}`);
|
|
318
|
+
}
|
|
319
|
+
console.log("");
|
|
320
|
+
}
|
|
321
|
+
async function version() {
|
|
322
|
+
try {
|
|
323
|
+
const pkg = await import("node:fs").then((fs) => JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")));
|
|
324
|
+
return String(pkg.version ?? "0.0.0");
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return "0.0.0";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function log(msg) {
|
|
331
|
+
console.log(msg);
|
|
332
|
+
}
|
|
333
|
+
function fail(msg) {
|
|
334
|
+
console.error(`\nbnl-harness: ${msg}\n`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
main().catch((e) => {
|
|
338
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
339
|
+
});
|
|
340
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
/** Endpoints as advertised by the resource + authorization-server metadata documents. */
|
|
18
|
+
export interface AsMetadata {
|
|
19
|
+
issuer: string;
|
|
20
|
+
authorization_endpoint: string;
|
|
21
|
+
token_endpoint: string;
|
|
22
|
+
device_authorization_endpoint?: string;
|
|
23
|
+
registration_endpoint?: string;
|
|
24
|
+
code_challenge_methods_supported?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface PkcePair {
|
|
27
|
+
verifier: string;
|
|
28
|
+
challenge: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* PKCE S256. The verifier is 32 random bytes base64url-encoded (43 chars — comfortably
|
|
32
|
+
* inside RFC 7636's 43..128 range); the challenge is its SHA-256, also base64url.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createPkce(): PkcePair;
|
|
35
|
+
export declare function base64url(buf: Buffer): string;
|
|
36
|
+
/**
|
|
37
|
+
* Constant-time compare for the `state` parameter. Not strictly required — state is an
|
|
38
|
+
* integrity check, not a secret — but a length-independent comparison keeps the callback
|
|
39
|
+
* handler free of an early-exit oracle, and it costs nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function safeEqual(a: string, b: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Every discovered endpoint must be https and live on the same host as the issuer, and
|
|
44
|
+
* must not smuggle credentials in the userinfo part. Same rule the PHP side applies in
|
|
45
|
+
* lib/Auth/BlueNoteOidc.php — a metadata document is attacker-influencable input, so a
|
|
46
|
+
* redirected authorization_endpoint is a credential-phishing primitive if left unchecked.
|
|
47
|
+
*
|
|
48
|
+
* Loopback (http://127.0.0.1) is exempt because that is our OWN redirect target, never a
|
|
49
|
+
* discovered endpoint.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assertSafeEndpoint(url: string, issuer: string, what: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* RFC 9728 protected-resource metadata -> RFC 8414 authorization-server metadata.
|
|
54
|
+
*
|
|
55
|
+
* The resource document is what a spec-compliant MCP client finds from the endpoint's
|
|
56
|
+
* `401 + WWW-Authenticate: Bearer resource_metadata="..."`. We follow the same chain
|
|
57
|
+
* rather than hardcoding paths, so a self-hosted deployment works with only `--url`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function discover(baseUrl: string): Promise<AsMetadata>;
|
|
60
|
+
/** The DCR shim. Any failure falls back to the caller's default client_id. */
|
|
61
|
+
export declare function registerClient(meta: AsMetadata, fallbackClientId: string): Promise<string>;
|
|
62
|
+
export interface TokenResponse {
|
|
63
|
+
access_token: string;
|
|
64
|
+
token_type?: string;
|
|
65
|
+
expires_in?: number;
|
|
66
|
+
refresh_token?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Browser flow. Resolves with an access token, or rejects on timeout / user denial.
|
|
70
|
+
*
|
|
71
|
+
* The listener binds 127.0.0.1 EXPLICITLY (not 0.0.0.0) so nothing off-box can reach the
|
|
72
|
+
* callback, and it shuts down the moment it has an answer — a lingering listener holding a
|
|
73
|
+
* valid `state` is a needless window.
|
|
74
|
+
*/
|
|
75
|
+
export declare function authorizeViaBrowser(opts: {
|
|
76
|
+
meta: AsMetadata;
|
|
77
|
+
clientId: string;
|
|
78
|
+
scope: string;
|
|
79
|
+
resource: string;
|
|
80
|
+
timeoutMs?: number;
|
|
81
|
+
openBrowser?: boolean;
|
|
82
|
+
log?: (msg: string) => void;
|
|
83
|
+
}): Promise<TokenResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Device-code flow — for a headless box, an SSH session, or any machine where opening a
|
|
86
|
+
* browser is not possible. Authentik supports this grant natively and `blue-note-mem-mcp`
|
|
87
|
+
* already has it enabled, so there is no server-side work behind this path.
|
|
88
|
+
*/
|
|
89
|
+
export declare function authorizeViaDevice(opts: {
|
|
90
|
+
meta: AsMetadata;
|
|
91
|
+
clientId: string;
|
|
92
|
+
scope: string;
|
|
93
|
+
resource: string;
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
log?: (msg: string) => void;
|
|
96
|
+
}): Promise<TokenResponse>;
|