toolnexus 0.24.0 → 0.26.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/README.md +151 -1
- package/dist/acp.d.ts +37 -6
- package/dist/acp.js +239 -52
- package/dist/classifier.d.ts +189 -31
- package/dist/classifier.js +589 -30
- package/dist/client.d.ts +38 -8
- package/dist/client.js +102 -23
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/judge.d.ts +6 -3
- package/dist/judge.js +13 -1
- package/dist/mcp.d.ts +32 -7
- package/dist/mcp.js +125 -36
- package/dist/mcp2026/auth.d.ts +137 -0
- package/dist/mcp2026/auth.js +446 -0
- package/dist/mcp2026/client.d.ts +48 -0
- package/dist/mcp2026/client.js +160 -0
- package/dist/mcp2026/protocol.d.ts +54 -0
- package/dist/mcp2026/protocol.js +113 -0
- package/dist/mcp2026/skills.d.ts +5 -0
- package/dist/mcp2026/skills.js +25 -0
- package/dist/mcp2026/source.d.ts +86 -0
- package/dist/mcp2026/source.js +372 -0
- package/dist/mcp2026/stdio.d.ts +24 -0
- package/dist/mcp2026/stdio.js +99 -0
- package/dist/mcp2026/toolsearch.d.ts +5 -0
- package/dist/mcp2026/toolsearch.js +58 -0
- package/dist/mcphandler.d.ts +169 -0
- package/dist/mcphandler.js +1145 -0
- package/dist/mcpheader.d.ts +5 -0
- package/dist/mcpheader.js +18 -0
- package/dist/mcpserve.d.ts +24 -15
- package/dist/mcpserve.js +105 -2
- package/dist/mcpserverauth.d.ts +48 -0
- package/dist/mcpserverauth.js +203 -0
- package/dist/serve.js +7 -0
- package/dist/skill.d.ts +21 -0
- package/dist/skill.js +44 -0
- package/dist/toolkit.d.ts +20 -1
- package/dist/toolkit.js +25 -3
- package/dist/types.d.ts +22 -0
- package/package.json +4 -3
- package/src/acp.ts +258 -55
- package/src/classifier.ts +677 -46
- package/src/client.ts +130 -27
- package/src/index.ts +2 -0
- package/src/judge.ts +19 -4
- package/src/mcp.ts +162 -43
- package/src/mcp2026/auth.ts +473 -0
- package/src/mcp2026/client.ts +161 -0
- package/src/mcp2026/protocol.ts +135 -0
- package/src/mcp2026/skills.ts +22 -0
- package/src/mcp2026/source.ts +407 -0
- package/src/mcp2026/stdio.ts +102 -0
- package/src/mcp2026/toolsearch.ts +56 -0
- package/src/mcphandler.ts +1252 -0
- package/src/mcpheader.ts +19 -0
- package/src/mcpserve.ts +115 -2
- package/src/mcpserverauth.ts +216 -0
- package/src/serve.ts +7 -0
- package/src/skill.ts +50 -0
- package/src/toolkit.ts +31 -4
- package/src/types.ts +23 -0
package/dist/mcp.js
CHANGED
|
@@ -11,6 +11,9 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
|
11
11
|
import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
12
|
import { sanitize } from "./types.js";
|
|
13
13
|
import { summarizePart } from "./content.js";
|
|
14
|
+
import { probe, newOAuth, defaultTokenStore } from "./mcp2026/source.js";
|
|
15
|
+
import { PendingError } from "./mcp2026/auth.js";
|
|
16
|
+
export { Token, MemoryTokenStore } from "./mcp2026/auth.js";
|
|
14
17
|
// ---------------------------------------------------------------------------
|
|
15
18
|
// MCP elicitation bridge (§10). A server can ask US for input mid-`tools/call`
|
|
16
19
|
// (a reverse-request). We map it onto the one `waitFor`: form mode → kind:"input",
|
|
@@ -224,6 +227,22 @@ async function paginateTools(client, timeout) {
|
|
|
224
227
|
}
|
|
225
228
|
throw new Error(`MCP list exceeded ${MAX_LIST_PAGES} pages`);
|
|
226
229
|
}
|
|
230
|
+
/** §2 `tools/call` result → ToolResult (shared by the 2026 path and the frozen fallback). */
|
|
231
|
+
export function mapCallToolResult(result, server) {
|
|
232
|
+
// Parts are collected BEFORE any branch returns: the isError and structuredContent
|
|
233
|
+
// short-circuits both read `content[]` on the way past, so neither reintroduces the
|
|
234
|
+
// silent drop this mapping exists to remove (§2).
|
|
235
|
+
const parts = mcpContentToParts(result.content);
|
|
236
|
+
const withParts = (r) => (parts ? { ...r, parts } : r);
|
|
237
|
+
if (result.isError) {
|
|
238
|
+
return withParts({ output: formatToolErrorContent(result.content), isError: true, metadata: { server } });
|
|
239
|
+
}
|
|
240
|
+
if (result.structuredContent !== undefined && result.structuredContent !== null) {
|
|
241
|
+
return withParts({ output: JSON.stringify(result.structuredContent), isError: false, metadata: { server } });
|
|
242
|
+
}
|
|
243
|
+
const text = joinTextContent(result.content);
|
|
244
|
+
return withParts({ output: text || describeMcpParts(parts), isError: false, metadata: { server } });
|
|
245
|
+
}
|
|
227
246
|
function convertTool(server, def, client, timeout) {
|
|
228
247
|
const inputSchema = {
|
|
229
248
|
...def.inputSchema,
|
|
@@ -244,19 +263,7 @@ function convertTool(server, def, client, timeout) {
|
|
|
244
263
|
// `resetTimeoutOnProgress` never fires (add-mcp-progress-timeout). Notifications for a
|
|
245
264
|
// token that matches no in-flight request are dropped by the SDK, so they never reset.
|
|
246
265
|
{ onprogress: () => { }, resetTimeoutOnProgress: true, signal: ctx?.signal, timeout: ctx?.timeout ?? timeout });
|
|
247
|
-
|
|
248
|
-
// short-circuits both read `content[]` on the way past, so neither reintroduces the
|
|
249
|
-
// silent drop this mapping exists to remove (§2).
|
|
250
|
-
const parts = mcpContentToParts(result.content);
|
|
251
|
-
const withParts = (r) => (parts ? { ...r, parts } : r);
|
|
252
|
-
if (result.isError) {
|
|
253
|
-
return withParts({ output: formatToolErrorContent(result.content), isError: true, metadata: { server } });
|
|
254
|
-
}
|
|
255
|
-
if (result.structuredContent !== undefined && result.structuredContent !== null) {
|
|
256
|
-
return withParts({ output: JSON.stringify(result.structuredContent), isError: false, metadata: { server } });
|
|
257
|
-
}
|
|
258
|
-
const text = joinTextContent(result.content);
|
|
259
|
-
return withParts({ output: text || describeMcpParts(parts), isError: false, metadata: { server } });
|
|
266
|
+
return mapCallToolResult(result, server);
|
|
260
267
|
}
|
|
261
268
|
catch (e) {
|
|
262
269
|
return { output: e instanceof Error ? e.message : String(e), isError: true, metadata: { server } };
|
|
@@ -276,6 +283,15 @@ export function mcpHints(a) {
|
|
|
276
283
|
h.destructive = false;
|
|
277
284
|
return h;
|
|
278
285
|
}
|
|
286
|
+
function sdkConn(client, timeout) {
|
|
287
|
+
return {
|
|
288
|
+
listResources: async (signal) => (await client.listResources({}, { timeout, signal })).resources ?? [],
|
|
289
|
+
readResource: async (uri, signal) => (await client.readResource({ uri }, { timeout, signal })).contents ?? [],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function modernConn(s) {
|
|
293
|
+
return { listResources: (signal) => s.listResources(signal), readResource: (uri, signal) => s.readResource(uri, signal) };
|
|
294
|
+
}
|
|
279
295
|
function resourceTools(servers) {
|
|
280
296
|
const ro = { readOnly: true, destructive: false, openWorld: false };
|
|
281
297
|
const names = () => [...servers.keys()].sort();
|
|
@@ -312,8 +328,7 @@ function resourceTools(servers) {
|
|
|
312
328
|
for (const name of targets) {
|
|
313
329
|
const c = conn(name);
|
|
314
330
|
try {
|
|
315
|
-
const
|
|
316
|
-
for (const r of res.resources ?? []) {
|
|
331
|
+
for (const r of await c.listResources(ctx?.signal)) {
|
|
317
332
|
let line = `${name} ${r.uri}`;
|
|
318
333
|
if (r.name)
|
|
319
334
|
line += ` — ${r.name}`;
|
|
@@ -361,9 +376,8 @@ function resourceTools(servers) {
|
|
|
361
376
|
if (!uri)
|
|
362
377
|
return { output: "read_mcp_resource: uri is required", isError: true };
|
|
363
378
|
try {
|
|
364
|
-
const res = await c.client.readResource({ uri }, { timeout: c.timeout, signal: ctx?.signal });
|
|
365
379
|
const parts = [];
|
|
366
|
-
for (const rc of
|
|
380
|
+
for (const rc of await c.readResource(uri, ctx?.signal)) {
|
|
367
381
|
if (typeof rc.text === "string")
|
|
368
382
|
parts.push(rc.text);
|
|
369
383
|
else if (typeof rc.blob === "string")
|
|
@@ -390,16 +404,17 @@ function newMcpClient(waitFor) {
|
|
|
390
404
|
return client;
|
|
391
405
|
}
|
|
392
406
|
/** Connect an existing client's transport (remote streamable-HTTP with SSE fallback, or local stdio). */
|
|
393
|
-
async function connectTransport(client, cfg) {
|
|
407
|
+
async function connectTransport(client, cfg, auth) {
|
|
394
408
|
if (isRemote(cfg)) {
|
|
395
409
|
const url = new URL(cfg.url);
|
|
396
410
|
const expanded = expandEnvHeaders(cfg.headers);
|
|
397
411
|
const requestInit = expanded ? { headers: expanded } : undefined;
|
|
412
|
+
const fetch = auth ? bearerFetch(cfg.url, auth) : undefined;
|
|
398
413
|
try {
|
|
399
|
-
await client.connect(new StreamableHTTPClientTransport(url, { requestInit }));
|
|
414
|
+
await client.connect(new StreamableHTTPClientTransport(url, { requestInit, ...(fetch ? { fetch } : {}) }));
|
|
400
415
|
}
|
|
401
416
|
catch {
|
|
402
|
-
await client.connect(new SSEClientTransport(url, { requestInit }));
|
|
417
|
+
await client.connect(new SSEClientTransport(url, { requestInit, ...(fetch ? { fetch } : {}) }));
|
|
403
418
|
}
|
|
404
419
|
}
|
|
405
420
|
else {
|
|
@@ -414,6 +429,66 @@ async function connectTransport(client, cfg) {
|
|
|
414
429
|
}));
|
|
415
430
|
}
|
|
416
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* §2A.1a frozen fallback + OAuth: the SDK transport's fetch attaches the current bearer token
|
|
434
|
+
* (refreshing as needed); a 401/403 challenge runs the same OAuth flow once, then the request is
|
|
435
|
+
* re-sent. A pause is answered through waitFor, else the request fails ("authorization pending").
|
|
436
|
+
*/
|
|
437
|
+
function bearerFetch(resource, a) {
|
|
438
|
+
return async (input, init) => {
|
|
439
|
+
for (let attempt = 0;; attempt++) {
|
|
440
|
+
const headers = new Headers(init?.headers);
|
|
441
|
+
const tok = await a.oauth.bearer(resource);
|
|
442
|
+
if (tok)
|
|
443
|
+
headers.set("Authorization", "Bearer " + tok);
|
|
444
|
+
const res = await globalThis.fetch(input, { ...init, headers });
|
|
445
|
+
const www = res.headers.get("www-authenticate") ?? "";
|
|
446
|
+
if (attempt > 0 || (res.status !== 401 && res.status !== 403) || !www)
|
|
447
|
+
return res;
|
|
448
|
+
try {
|
|
449
|
+
await a.oauth.challenge(resource, res.status, www, tok, async () => ({}));
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
if (!(e instanceof PendingError))
|
|
453
|
+
throw e;
|
|
454
|
+
if (!a.waitFor)
|
|
455
|
+
throw new Error("authorization pending");
|
|
456
|
+
const ans = await a.waitFor({ ...e.pending });
|
|
457
|
+
if (!ans?.ok)
|
|
458
|
+
throw new Error(`authorization denied: ${ans?.reason ?? "cancelled"}`);
|
|
459
|
+
const cb = ans.data?.callbackUrl;
|
|
460
|
+
if (typeof cb !== "string")
|
|
461
|
+
throw new Error("authorization answer carries no data.callbackUrl");
|
|
462
|
+
await a.oauth.resume(e.pending.id, cb);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Discover first (§2.2): a 2026 server is used directly; an old one (§2.2a) gets the frozen SDK
|
|
469
|
+
* path, with the OAuth bearer attached for remote servers. Throws ⇒ the server failed.
|
|
470
|
+
*/
|
|
471
|
+
async function connectServer(name, cfg, timeout, opts, store, sdkClient, onSdk) {
|
|
472
|
+
const o = { waitFor: opts.waitFor, tokenStore: store, openUrl: opts.openUrl, signal: opts.signal, disableTools: opts.disableTools };
|
|
473
|
+
const remote = isRemote(cfg);
|
|
474
|
+
const oauth = remote ? newOAuth(cfg.auth, o, store) : undefined;
|
|
475
|
+
const p = await probe({
|
|
476
|
+
name, timeout, tools: cfg.tools, toolSearch: cfg.toolSearch === true, description: cfg.description,
|
|
477
|
+
remote: remote ? { url: cfg.url, headers: expandEnvHeaders(cfg.headers), oauth: oauth } : undefined,
|
|
478
|
+
local: remote ? undefined : {
|
|
479
|
+
argv: cfg.command,
|
|
480
|
+
env: { ...process.env, ...(cfg.environment ?? cfg.env ?? {}) },
|
|
481
|
+
cwd: cfg.cwd,
|
|
482
|
+
},
|
|
483
|
+
}, o);
|
|
484
|
+
if (p.kind === "modern")
|
|
485
|
+
return p;
|
|
486
|
+
const client = sdkClient();
|
|
487
|
+
onSdk(client);
|
|
488
|
+
await raceTimeout(connectTransport(client, cfg, oauth ? { oauth, waitFor: opts.waitFor } : undefined), timeout, opts.signal);
|
|
489
|
+
const defs = await raceTimeout(paginateTools(client, timeout), timeout, opts.signal);
|
|
490
|
+
return { kind: "sdk", client, defs };
|
|
491
|
+
}
|
|
417
492
|
/** Connect to every enabled server, list + convert tools. Failures are isolated.
|
|
418
493
|
* §2 Gap 3: an optional `signal` bounds each connect/list by the server timeout and aborts the
|
|
419
494
|
* whole load promptly on parent cancellation. */
|
|
@@ -423,6 +498,8 @@ export async function loadMcp(input, opts) {
|
|
|
423
498
|
const status = {};
|
|
424
499
|
const clients = [];
|
|
425
500
|
const servers = new Map();
|
|
501
|
+
const skills = [];
|
|
502
|
+
const store = defaultTokenStore({ tokenStore: opts?.tokenStore });
|
|
426
503
|
const waitFor = opts?.waitFor;
|
|
427
504
|
const signal = opts?.signal;
|
|
428
505
|
const onServer = opts?.onServer;
|
|
@@ -447,23 +524,31 @@ export async function loadMcp(input, opts) {
|
|
|
447
524
|
return;
|
|
448
525
|
}
|
|
449
526
|
const timeout = cfg.timeout ?? DEFAULT_TIMEOUT;
|
|
450
|
-
//
|
|
451
|
-
// (its stdio child is killed by close() — never orphaned).
|
|
452
|
-
|
|
527
|
+
// The fallback SDK client is created before its connect so a timed-out/aborted connect
|
|
528
|
+
// still has a closeable handle (its stdio child is killed by close() — never orphaned).
|
|
529
|
+
let client;
|
|
453
530
|
try {
|
|
454
|
-
await
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
531
|
+
const c = await connectServer(name, cfg, timeout, opts ?? {}, store, () => newMcpClient(waitFor), (cl) => (client = cl));
|
|
532
|
+
let converted;
|
|
533
|
+
if (c.kind === "modern") {
|
|
534
|
+
converted = c.server.tools();
|
|
535
|
+
skills.push(...c.server.skills);
|
|
536
|
+
clients.push(c.server);
|
|
537
|
+
servers.set(name, modernConn(c.server));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
const kept = applyToolsFilter(name, c.defs, cfg.tools);
|
|
541
|
+
converted = kept.map((def) => convertTool(name, def, c.client, timeout));
|
|
542
|
+
clients.push(c.client);
|
|
543
|
+
servers.set(name, sdkConn(c.client, timeout));
|
|
544
|
+
}
|
|
458
545
|
tools.push(...converted);
|
|
459
|
-
clients.push(client);
|
|
460
|
-
servers.set(name, { client, timeout });
|
|
461
546
|
status[name] = "connected";
|
|
462
547
|
report({ name, status: "connected", tools: converted });
|
|
463
548
|
}
|
|
464
549
|
catch (e) {
|
|
465
550
|
status[name] = "failed";
|
|
466
|
-
await client
|
|
551
|
+
await client?.close().catch(() => { });
|
|
467
552
|
// Parent cancellation/deadline aborts the whole load (§2 Gap 3, A1); a per-server timeout
|
|
468
553
|
// within budget isolates just this server.
|
|
469
554
|
if (signal?.aborted)
|
|
@@ -493,6 +578,7 @@ export async function loadMcp(input, opts) {
|
|
|
493
578
|
await Promise.all(clients.map((c) => c.close().catch(() => { })));
|
|
494
579
|
},
|
|
495
580
|
resourceTools: () => resourceTools(servers),
|
|
581
|
+
skills,
|
|
496
582
|
};
|
|
497
583
|
}
|
|
498
584
|
/**
|
|
@@ -504,17 +590,20 @@ export async function listMcpTools(input, opts) {
|
|
|
504
590
|
const toolsByServer = {};
|
|
505
591
|
const status = {};
|
|
506
592
|
const signal = opts?.signal;
|
|
593
|
+
const store = defaultTokenStore({ tokenStore: opts?.tokenStore });
|
|
507
594
|
await Promise.all(Object.entries(config).map(async ([name, cfg]) => {
|
|
508
595
|
if (!isEnabled(cfg)) {
|
|
509
596
|
status[name] = "disabled";
|
|
510
597
|
return;
|
|
511
598
|
}
|
|
512
599
|
const timeout = cfg.timeout ?? DEFAULT_TIMEOUT;
|
|
513
|
-
//
|
|
514
|
-
|
|
600
|
+
// The fallback client is created before its connect so an aborted/timed-out connect is still closed.
|
|
601
|
+
let client;
|
|
515
602
|
try {
|
|
516
|
-
await
|
|
517
|
-
const defs =
|
|
603
|
+
const c = await connectServer(name, cfg, timeout, { ...opts, waitFor: opts?.waitFor }, store, () => newMcpClient(), (cl) => (client = cl));
|
|
604
|
+
const defs = c.kind === "modern" ? c.server.rawDefs() : c.defs;
|
|
605
|
+
if (c.kind === "modern")
|
|
606
|
+
await c.server.close().catch(() => { });
|
|
518
607
|
toolsByServer[name] = defs.map((d) => ({
|
|
519
608
|
name: d.name,
|
|
520
609
|
description: d.description ?? "",
|
|
@@ -529,13 +618,13 @@ export async function listMcpTools(input, opts) {
|
|
|
529
618
|
catch (e) {
|
|
530
619
|
status[name] = "failed";
|
|
531
620
|
if (signal?.aborted) {
|
|
532
|
-
await client
|
|
621
|
+
await client?.close().catch(() => { });
|
|
533
622
|
throw e;
|
|
534
623
|
}
|
|
535
624
|
console.warn(`[toolnexus] MCP server "${name}" failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
536
625
|
}
|
|
537
626
|
finally {
|
|
538
|
-
await client
|
|
627
|
+
await client?.close().catch(() => { });
|
|
539
628
|
}
|
|
540
629
|
}));
|
|
541
630
|
return { tools: toolsByServer, status };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type HttpFn, type Obj } from "./protocol.js";
|
|
2
|
+
declare const inspect: unique symbol;
|
|
3
|
+
/** Bound to the issuer that minted it and the resource it is for. Never log it (it prints redacted). */
|
|
4
|
+
export declare class Token {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
refreshToken: string;
|
|
7
|
+
/** ms since epoch; 0 = no expiry. */
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
scopes: string[];
|
|
10
|
+
issuer: string;
|
|
11
|
+
clientId: string;
|
|
12
|
+
/** The protected resource (server URI) the token is bound to. */
|
|
13
|
+
resource: string;
|
|
14
|
+
constructor(init?: Partial<Token>);
|
|
15
|
+
toString(): string;
|
|
16
|
+
toJSON(): string;
|
|
17
|
+
[inspect](): string;
|
|
18
|
+
}
|
|
19
|
+
/** Persists tokens per resource. Implementations must keep them confidential. Sync or async. */
|
|
20
|
+
export interface TokenStore {
|
|
21
|
+
load(resource: string): Token | null | undefined | Promise<Token | null | undefined>;
|
|
22
|
+
save(resource: string, t: Token): void | Promise<void>;
|
|
23
|
+
delete(resource: string): void | Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
26
|
+
private m;
|
|
27
|
+
load(r: string): Token | undefined;
|
|
28
|
+
save(r: string, t: Token): void;
|
|
29
|
+
delete(r: string): void;
|
|
30
|
+
}
|
|
31
|
+
/** Mirrors toolnexus SPEC §10 Request (kind "authorization"). */
|
|
32
|
+
export interface Pending {
|
|
33
|
+
id: string;
|
|
34
|
+
kind: "authorization";
|
|
35
|
+
prompt: string;
|
|
36
|
+
url: string;
|
|
37
|
+
expiresAt: string;
|
|
38
|
+
}
|
|
39
|
+
/** Thrown instead of blocking when the authorizer pauses. Call oauth.resume(id, callbackURL). */
|
|
40
|
+
export declare class PendingError extends Error {
|
|
41
|
+
pending: Pending;
|
|
42
|
+
constructor(p: Pending);
|
|
43
|
+
}
|
|
44
|
+
/** The Pending inside err, or undefined. */
|
|
45
|
+
export declare function pendingOf(err: unknown): Pending | undefined;
|
|
46
|
+
/** What a pausing AuthSession.wait throws. */
|
|
47
|
+
export declare const PAUSE: Error;
|
|
48
|
+
/** Decides how the user reaches the authorization URL and how the callback comes back. */
|
|
49
|
+
export interface Authorizer {
|
|
50
|
+
begin(): Promise<AuthSession>;
|
|
51
|
+
}
|
|
52
|
+
export interface AuthSession {
|
|
53
|
+
redirectUri: string;
|
|
54
|
+
/** Resolves with the full callback URL, or throws PAUSE to hand a Pending to the caller. */
|
|
55
|
+
wait(p: Pending): Promise<string>;
|
|
56
|
+
close(): void | Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/** Never blocks: every authorization becomes a Pending. */
|
|
59
|
+
export declare class PauseAuthorizer implements Authorizer {
|
|
60
|
+
redirect: string;
|
|
61
|
+
constructor(o: {
|
|
62
|
+
redirect: string;
|
|
63
|
+
});
|
|
64
|
+
begin(): Promise<AuthSession>;
|
|
65
|
+
}
|
|
66
|
+
/** Opens a browser and listens on 127.0.0.1:<random>/callback (RFC 8252 §7.3). */
|
|
67
|
+
export declare class LoopbackAuthorizer implements Authorizer {
|
|
68
|
+
open: (url: string) => unknown | Promise<unknown>;
|
|
69
|
+
timeoutMs: number;
|
|
70
|
+
constructor(o?: {
|
|
71
|
+
open?: (url: string) => unknown | Promise<unknown>;
|
|
72
|
+
});
|
|
73
|
+
begin(): Promise<AuthSession>;
|
|
74
|
+
}
|
|
75
|
+
export interface OAuthOptions {
|
|
76
|
+
authorizer?: Authorizer;
|
|
77
|
+
clientId?: string;
|
|
78
|
+
clientSecret?: string;
|
|
79
|
+
tokenAuthMethod?: "" | "none" | "client_secret_basic" | "client_secret_post";
|
|
80
|
+
/** Drops the RFC 8707 `resource` from refresh_token requests (Entra rejects it; python-sdk #2578). */
|
|
81
|
+
omitRefreshResource?: boolean;
|
|
82
|
+
store?: TokenStore;
|
|
83
|
+
clientIds?: Record<string, string>;
|
|
84
|
+
clientMetadataUrl?: string;
|
|
85
|
+
http?: HttpFn;
|
|
86
|
+
allowHttp?: boolean;
|
|
87
|
+
}
|
|
88
|
+
interface AuthFlow {
|
|
89
|
+
resource: string;
|
|
90
|
+
issuer: string;
|
|
91
|
+
clientId: string;
|
|
92
|
+
redirect: string;
|
|
93
|
+
verifier: string;
|
|
94
|
+
state: string;
|
|
95
|
+
tokenEndpoint: string;
|
|
96
|
+
issSupported: boolean;
|
|
97
|
+
scopes: string[];
|
|
98
|
+
expires: number;
|
|
99
|
+
retry: () => Promise<Obj>;
|
|
100
|
+
}
|
|
101
|
+
/** The client-side auth engine; attach it as client.auth. */
|
|
102
|
+
export declare class OAuth {
|
|
103
|
+
authorizer: Authorizer;
|
|
104
|
+
clientId: string;
|
|
105
|
+
clientSecret: string;
|
|
106
|
+
tokenAuthMethod: string;
|
|
107
|
+
omitRefreshResource: boolean;
|
|
108
|
+
store: TokenStore;
|
|
109
|
+
clientIds: Record<string, string>;
|
|
110
|
+
clientMetadataUrl: string;
|
|
111
|
+
http: HttpFn;
|
|
112
|
+
allowHttp: boolean;
|
|
113
|
+
private authMu;
|
|
114
|
+
private issuer;
|
|
115
|
+
/** @internal paused flows by pending id (exposed for tests) */
|
|
116
|
+
flows: Map<string, AuthFlow>;
|
|
117
|
+
constructor(o?: OAuthOptions);
|
|
118
|
+
/** The token to send for resource, refreshing if expired. "" = send none. */
|
|
119
|
+
bearer(resource: string): Promise<string>;
|
|
120
|
+
private refresh;
|
|
121
|
+
/** @internal Handles a 401/403. Resolves = retry the request now; throws PendingError = paused. */
|
|
122
|
+
challenge(resource: string, status: number, www: string, sent: string, retry: () => Promise<Obj>): Promise<void>;
|
|
123
|
+
/** @internal */
|
|
124
|
+
setRetry(id: string, f: () => Promise<Obj>): void;
|
|
125
|
+
/** Finishes a paused authorization and re-runs the original operation, returning its result. */
|
|
126
|
+
resume(id: string, callbackUrl: string): Promise<Obj>;
|
|
127
|
+
private complete;
|
|
128
|
+
private tokenRequest;
|
|
129
|
+
/** PRM: header URL if present, else path-inserted well-known, else root (spec: PRM Discovery Requirements). */
|
|
130
|
+
fetchPRM(resource: string, hinted: string): Promise<Obj>;
|
|
131
|
+
}
|
|
132
|
+
/** RFC 8414 then OIDC endpoints in the spec's order, with an issuer equality check. */
|
|
133
|
+
export declare function discoverAS(httpFn: HttpFn, issuer: string): Promise<Obj>;
|
|
134
|
+
/** Parses `Bearer k="v", k2=v2` (RFC 6750 §3 / RFC 9110 auth-param). */
|
|
135
|
+
export declare function parseChallenge(h: string): Record<string, string>;
|
|
136
|
+
export declare function randStr(n: number): string;
|
|
137
|
+
export {};
|