@corbits/mcp 0.1.0 → 0.2.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 +156 -53
- package/dist/client.d.ts +23 -5
- package/dist/client.js +195 -60
- package/dist/hub/discover.d.ts +32 -7
- package/dist/hub/discover.js +91 -28
- package/dist/hub/index.d.ts +1 -1
- package/dist/hub/index.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/naming.d.ts +14 -0
- package/dist/naming.js +33 -0
- package/dist/oauth-discovery.d.ts +71 -0
- package/dist/oauth-discovery.js +322 -0
- package/dist/sidecar-bundle.d.ts +2 -0
- package/dist/sidecar-bundle.js +24 -20
- package/dist/test-server.d.ts +9 -0
- package/dist/test-server.js +92 -0
- package/dist/tool.d.ts +2 -0
- package/dist/tool.js +40 -8
- package/package.json +72 -44
package/dist/hub/discover.d.ts
CHANGED
|
@@ -1,37 +1,62 @@
|
|
|
1
1
|
import type { DB } from "@intx/db";
|
|
2
|
+
import type { FetchLike } from "@intx/harness";
|
|
2
3
|
import type { TenantEnv } from "@intx/hub-api";
|
|
3
4
|
import { type CredentialCipher } from "@intx/types";
|
|
4
5
|
import type { Hono, MiddlewareHandler } from "hono";
|
|
5
|
-
import { type
|
|
6
|
+
import { type McpTool, type McpServerInfo } from "../client.js";
|
|
6
7
|
export type MountMcpDiscoveryOpts = {
|
|
7
8
|
readonly db: DB["db"];
|
|
8
9
|
readonly cipher: CredentialCipher;
|
|
9
10
|
/** The host's stock grant middleware, so authority is checked exactly once, its way. */
|
|
10
11
|
readonly requireGrant: MiddlewareHandler<TenantEnv>;
|
|
12
|
+
/**
|
|
13
|
+
* Further origins a credential may be sent to, keyed by the credential's
|
|
14
|
+
* pinned origin, for a server whose MCP endpoint is not on that origin.
|
|
15
|
+
* Host-wide: an entry applies to every tenant's credential pinned to that
|
|
16
|
+
* origin. Passed through to `@corbits/credential-http`. Empty by default.
|
|
17
|
+
*/
|
|
18
|
+
readonly extraOrigins?: Readonly<Record<string, readonly string[]>>;
|
|
19
|
+
/**
|
|
20
|
+
* Allow plain-http loopback targets, for local development only. Off by
|
|
21
|
+
* default so the route cannot be pointed at the hub's own ports.
|
|
22
|
+
*/
|
|
23
|
+
readonly allowLoopback?: boolean;
|
|
11
24
|
/** Reported when a discovery attempt fails; the caller only sees a message. */
|
|
12
25
|
readonly onError?: (error: unknown, context: {
|
|
13
26
|
url: string;
|
|
14
27
|
}) => void;
|
|
15
28
|
};
|
|
29
|
+
export type McpCredential = {
|
|
30
|
+
readonly secret: string;
|
|
31
|
+
/** The provider's API origin the secret is pinned to, when it has one. */
|
|
32
|
+
readonly origin?: string;
|
|
33
|
+
};
|
|
16
34
|
/**
|
|
17
|
-
* Read a tenant credential's decrypted secret
|
|
18
|
-
* credential id from another tenant reads as absent,
|
|
35
|
+
* Read a tenant credential's decrypted secret and the origin it is pinned to.
|
|
36
|
+
* Scoped by tenant so a credential id from another tenant reads as absent,
|
|
37
|
+
* not as a secret; a revoked, errored or expired credential reads as absent
|
|
38
|
+
* too.
|
|
19
39
|
*/
|
|
20
|
-
export declare function
|
|
40
|
+
export declare function readCredential(opts: {
|
|
21
41
|
readonly db: DB["db"];
|
|
22
42
|
readonly cipher: CredentialCipher;
|
|
23
43
|
readonly tenantId: string;
|
|
24
44
|
readonly credentialId: string;
|
|
25
|
-
}): Promise<
|
|
45
|
+
}): Promise<McpCredential | undefined>;
|
|
26
46
|
export type McpDiscovery = {
|
|
27
47
|
readonly serverInfo: McpServerInfo;
|
|
28
48
|
readonly tools: readonly McpTool[];
|
|
29
49
|
};
|
|
30
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Handshake and read a server's catalog over a fetch pinned to the
|
|
52
|
+
* credential's origin, or to the URL's origin when no secret is sent.
|
|
53
|
+
*/
|
|
31
54
|
export declare function discoverMcpServer(args: {
|
|
32
55
|
readonly url: string;
|
|
33
|
-
readonly
|
|
56
|
+
readonly credential?: McpCredential;
|
|
57
|
+
readonly extraOrigins?: Readonly<Record<string, readonly string[]>>;
|
|
34
58
|
readonly fetch?: FetchLike;
|
|
59
|
+
readonly timeoutMs?: number;
|
|
35
60
|
}): Promise<McpDiscovery>;
|
|
36
61
|
/**
|
|
37
62
|
* Mount `POST /mcp/discover` on a tenant router. It answers with the server's
|
package/dist/hub/discover.js
CHANGED
|
@@ -1,57 +1,107 @@
|
|
|
1
1
|
// Server-side MCP catalog discovery. The browser never holds an MCP server's
|
|
2
2
|
// token, so the only place an OAuth-protected server's `tools/list` can be
|
|
3
3
|
// read is here, behind the hub's own tenant-member gate and with the secret
|
|
4
|
-
// decrypted in this process and sent only to the
|
|
5
|
-
import { credential } from "@intx/db/schema";
|
|
4
|
+
// decrypted in this process and sent only to the credential's own origin.
|
|
5
|
+
import { credential, provider } from "@intx/db/schema";
|
|
6
6
|
import { credentialAad } from "@intx/types";
|
|
7
|
-
import {
|
|
7
|
+
import { createOriginPinnedFetch, MCP_NO_TOKEN_SENTINEL, } from "@corbits/credential-http";
|
|
8
8
|
import { type } from "arktype";
|
|
9
9
|
import { and, eq } from "drizzle-orm";
|
|
10
|
-
import { mcpInitialize, mcpListTools, } from "../client.js";
|
|
10
|
+
import { McpError, mcpInitialize, mcpListTools, } from "../client.js";
|
|
11
11
|
import { parseMcpEndpoint } from "../url.js";
|
|
12
12
|
const DiscoverBody = type({
|
|
13
13
|
url: "string > 0",
|
|
14
14
|
"credentialId?": "string > 0",
|
|
15
15
|
});
|
|
16
16
|
/**
|
|
17
|
-
* Read a tenant credential's decrypted secret
|
|
18
|
-
* credential id from another tenant reads as absent,
|
|
17
|
+
* Read a tenant credential's decrypted secret and the origin it is pinned to.
|
|
18
|
+
* Scoped by tenant so a credential id from another tenant reads as absent,
|
|
19
|
+
* not as a secret; a revoked, errored or expired credential reads as absent
|
|
20
|
+
* too.
|
|
19
21
|
*/
|
|
20
|
-
export async function
|
|
22
|
+
export async function readCredential(opts) {
|
|
21
23
|
const [row] = await opts.db
|
|
22
|
-
.select(
|
|
24
|
+
.select({
|
|
25
|
+
id: credential.id,
|
|
26
|
+
secret: credential.secret,
|
|
27
|
+
status: credential.status,
|
|
28
|
+
expiresAt: credential.expiresAt,
|
|
29
|
+
apiBaseUrl: provider.apiBaseUrl,
|
|
30
|
+
})
|
|
23
31
|
.from(credential)
|
|
32
|
+
.innerJoin(provider, eq(provider.id, credential.providerId))
|
|
24
33
|
.where(and(eq(credential.id, opts.credentialId), eq(credential.tenantId, opts.tenantId)))
|
|
25
34
|
.limit(1);
|
|
26
|
-
if (row === undefined ||
|
|
35
|
+
if (row === undefined ||
|
|
36
|
+
row.status !== "active" ||
|
|
37
|
+
(row.expiresAt !== null && row.expiresAt.getTime() <= Date.now())) {
|
|
27
38
|
return undefined;
|
|
28
|
-
|
|
39
|
+
}
|
|
40
|
+
const secret = await opts.cipher.decrypt(row.secret, credentialAad(row.id, "secret"));
|
|
41
|
+
return row.apiBaseUrl === null
|
|
42
|
+
? { secret }
|
|
43
|
+
: { secret, origin: new URL(row.apiBaseUrl).origin };
|
|
29
44
|
}
|
|
45
|
+
const HEADER_VALUE = /^[\t\x20-\x7e]*$/;
|
|
30
46
|
/** A 3xx would send the bearer onward, so the pinned fetch's manual redirect
|
|
31
47
|
* is turned into a refusal rather than a response the client tries to read. */
|
|
32
48
|
function refusingRedirects(inner) {
|
|
33
49
|
return async (input, init) => {
|
|
34
50
|
const response = await inner(input, init);
|
|
35
51
|
if (response.status >= 300 && response.status < 400) {
|
|
36
|
-
throw new
|
|
52
|
+
throw new McpError(`the MCP server answered a ${String(response.status)} redirect; refusing to follow it`);
|
|
37
53
|
}
|
|
38
54
|
return response;
|
|
39
55
|
};
|
|
40
56
|
}
|
|
41
|
-
/**
|
|
57
|
+
/** How long one discovery request, including its body, may take. */
|
|
58
|
+
const DISCOVERY_TIMEOUT_MS = 30_000;
|
|
59
|
+
/**
|
|
60
|
+
* Handshake and read a server's catalog over a fetch pinned to the
|
|
61
|
+
* credential's origin, or to the URL's origin when no secret is sent.
|
|
62
|
+
*/
|
|
42
63
|
export async function discoverMcpServer(args) {
|
|
43
64
|
const target = parseMcpEndpoint(args.url);
|
|
44
|
-
const
|
|
65
|
+
const secret = args.credential?.secret;
|
|
66
|
+
const token = secret === undefined || secret === MCP_NO_TOKEN_SENTINEL
|
|
45
67
|
? undefined
|
|
46
|
-
:
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
: secret;
|
|
69
|
+
const origin = token === undefined ? target.origin : args.credential?.origin;
|
|
70
|
+
if (origin === undefined) {
|
|
71
|
+
throw new McpError("the credential's provider has no API origin to pin to");
|
|
72
|
+
}
|
|
73
|
+
// A secret that is not a valid header value makes fetch throw a message
|
|
74
|
+
// quoting it, so it is refused here with a fixed one.
|
|
75
|
+
if (token !== undefined && !HEADER_VALUE.test(token)) {
|
|
76
|
+
throw new McpError("the credential's secret is not a valid header value");
|
|
77
|
+
}
|
|
78
|
+
const extraOrigins = Object.entries(args.extraOrigins ?? {}).flatMap(([pinned, extra]) => (new URL(pinned).origin === origin ? extra : []));
|
|
79
|
+
const allowed = [origin, ...extraOrigins.map((o) => new URL(o).origin)];
|
|
80
|
+
if (!allowed.includes(target.origin)) {
|
|
81
|
+
throw new McpError(`credential is pinned to ${origin}; refusing cross-origin request to ${target.origin}`);
|
|
82
|
+
}
|
|
83
|
+
const pinned = refusingRedirects(createOriginPinnedFetch({
|
|
84
|
+
origin,
|
|
85
|
+
header: "authorization",
|
|
86
|
+
readValue: () => (token === undefined ? undefined : `Bearer ${token}`),
|
|
87
|
+
extraOrigins,
|
|
88
|
+
fetch: args.fetch ?? globalThis.fetch,
|
|
51
89
|
}));
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
const client = {
|
|
91
|
+
fetch: pinned,
|
|
92
|
+
timeoutMs: args.timeoutMs ?? DISCOVERY_TIMEOUT_MS,
|
|
93
|
+
};
|
|
94
|
+
const session = await mcpInitialize(args.url, client);
|
|
95
|
+
const tools = await mcpListTools(args.url, { ...client, session });
|
|
96
|
+
return {
|
|
97
|
+
serverInfo: {
|
|
98
|
+
protocolVersion: session.protocolVersion,
|
|
99
|
+
...(session.serverInfo !== undefined
|
|
100
|
+
? { serverInfo: session.serverInfo }
|
|
101
|
+
: {}),
|
|
102
|
+
},
|
|
103
|
+
tools,
|
|
104
|
+
};
|
|
55
105
|
}
|
|
56
106
|
/**
|
|
57
107
|
* Mount `POST /mcp/discover` on a tenant router. It answers with the server's
|
|
@@ -71,29 +121,42 @@ export function mountMcpDiscovery(app, opts) {
|
|
|
71
121
|
catch (cause) {
|
|
72
122
|
return c.json({ error: cause instanceof Error ? cause.message : String(cause) }, 400);
|
|
73
123
|
}
|
|
124
|
+
if (target.protocol === "http:" && opts.allowLoopback !== true) {
|
|
125
|
+
return c.json({ error: "the MCP server URL must be https" }, 400);
|
|
126
|
+
}
|
|
74
127
|
try {
|
|
75
|
-
const
|
|
128
|
+
const found = body.credentialId === undefined
|
|
76
129
|
? undefined
|
|
77
|
-
: await
|
|
130
|
+
: await readCredential({
|
|
78
131
|
db: opts.db,
|
|
79
132
|
cipher: opts.cipher,
|
|
80
133
|
tenantId: c.get("tenant").id,
|
|
81
134
|
credentialId: body.credentialId,
|
|
82
135
|
});
|
|
83
|
-
if (body.credentialId !== undefined &&
|
|
136
|
+
if (body.credentialId !== undefined && found === undefined) {
|
|
84
137
|
return c.json({ error: "credential not found" }, 404);
|
|
85
138
|
}
|
|
86
139
|
const data = await discoverMcpServer({
|
|
87
140
|
url: body.url,
|
|
88
|
-
...(
|
|
141
|
+
...(found !== undefined ? { credential: found } : {}),
|
|
142
|
+
...(opts.extraOrigins !== undefined
|
|
143
|
+
? { extraOrigins: opts.extraOrigins }
|
|
144
|
+
: {}),
|
|
89
145
|
});
|
|
90
146
|
return c.json({ data });
|
|
91
147
|
}
|
|
92
148
|
catch (cause) {
|
|
93
|
-
|
|
94
|
-
//
|
|
149
|
+
// Only this package's own messages are passed on: anything else, such
|
|
150
|
+
// as a fetch error, may quote the material the request carried. An
|
|
151
|
+
// upstream status is not echoed, so the route is no port-probing oracle.
|
|
152
|
+
const error = cause instanceof McpError && cause.status === undefined
|
|
153
|
+
? cause
|
|
154
|
+
: new McpError(cause instanceof McpError
|
|
155
|
+
? "the server refused the request"
|
|
156
|
+
: "the handshake failed");
|
|
157
|
+
opts.onError?.(error, { url: body.url });
|
|
95
158
|
return c.json({
|
|
96
|
-
error: `the MCP server at ${target.origin} could not be discovered: ${
|
|
159
|
+
error: `the MCP server at ${target.origin} could not be discovered: ${error.message}`,
|
|
97
160
|
}, 422);
|
|
98
161
|
}
|
|
99
162
|
});
|
package/dist/hub/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { mountMcpDiscovery, discoverMcpServer,
|
|
1
|
+
export { mountMcpDiscovery, discoverMcpServer, readCredential, type McpCredential, type McpDiscovery, type MountMcpDiscoveryOpts, } from "./discover.js";
|
package/dist/hub/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Server-only entry point: the hub-hosted MCP discovery mount. The root and
|
|
2
2
|
// `./sidecar-bundle` entries stay free of `hono` and `@intx/db` so neither a
|
|
3
3
|
// browser bundle nor a deployed agent ever reaches them.
|
|
4
|
-
export { mountMcpDiscovery, discoverMcpServer,
|
|
4
|
+
export { mountMcpDiscovery, discoverMcpServer, readCredential, } from "./discover.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { mcpTools, type McpServerConfig, type McpToolsEnv, type McpToolsOptions, } from "./tool.js";
|
|
2
2
|
export { qualifiedName, isAskExempt, toolDescription } from "./naming.js";
|
|
3
|
-
export { mcpInitialize, mcpListTools, mcpCallTool, McpError, McpToolSchema, type McpTool, type McpToolResult, type McpClientOptions, type
|
|
3
|
+
export { mcpInitialize, mcpListTools, mcpCallTool, McpError, McpToolSchema, type McpTool, type McpToolResult, type McpClientOptions, type McpSession, } from "./client.js";
|
|
4
|
+
export { discoverMcpLoginEntry, registerMcpClient, mcpClientConfig, selectMcpScopes, type DiscoverMcpLoginEntryOptions, type McpAuthorizationServer, type McpClientConfigOptions, type McpClientRegistration, type McpLoginEntry, type RegisterMcpClientOptions, type SelectMcpScopesOptions, } from "./oauth-discovery.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { mcpTools, } from "./tool.js";
|
|
2
2
|
export { qualifiedName, isAskExempt, toolDescription } from "./naming.js";
|
|
3
3
|
export { mcpInitialize, mcpListTools, mcpCallTool, McpError, McpToolSchema, } from "./client.js";
|
|
4
|
+
export { discoverMcpLoginEntry, registerMcpClient, mcpClientConfig, selectMcpScopes, } from "./oauth-discovery.js";
|
package/dist/naming.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { McpTool } from "./client.js";
|
|
2
2
|
/** `<server>.<tool>`, the grantable unit: `tool:<server>.*` covers a server. */
|
|
3
3
|
export declare function qualifiedName(server: string, tool: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Refuse a catalog whose names could reach another server's grants: a server
|
|
6
|
+
* name with a `.` could make `<server>.<tool>` fall under another server's
|
|
7
|
+
* `tool:<other>.*`, and a repeated tool name would make two tools one name.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertCatalogNames(server: string, tools: readonly McpTool[]): void;
|
|
10
|
+
/**
|
|
11
|
+
* Refuse a server list before any request: every name non-empty and unique,
|
|
12
|
+
* so no two servers share a grant prefix, and every URL a permitted endpoint.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertServers(servers: readonly {
|
|
15
|
+
name: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}[], label: string): void;
|
|
4
18
|
export declare function toolDescription(tool: McpTool, server: string): string;
|
|
5
19
|
/**
|
|
6
20
|
* Whether `qualified` may drop below the `ask` floor: it must be on the
|
package/dist/naming.js
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
// Naming and the ask floor, shared by the author-time `mcpTools` factory and
|
|
2
2
|
// the deployed `mcpServers` sidecar bundle so both project a server's catalog
|
|
3
3
|
// onto identical tool names and identical approval marks.
|
|
4
|
+
import { parseMcpEndpoint } from "./url.js";
|
|
4
5
|
/** `<server>.<tool>`, the grantable unit: `tool:<server>.*` covers a server. */
|
|
5
6
|
export function qualifiedName(server, tool) {
|
|
6
7
|
return `${server}.${tool}`;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Refuse a catalog whose names could reach another server's grants: a server
|
|
11
|
+
* name with a `.` could make `<server>.<tool>` fall under another server's
|
|
12
|
+
* `tool:<other>.*`, and a repeated tool name would make two tools one name.
|
|
13
|
+
*/
|
|
14
|
+
export function assertCatalogNames(server, tools) {
|
|
15
|
+
if (server.includes(".")) {
|
|
16
|
+
throw new Error(`MCP server name "${server}" must not contain "."; it would overlap another server's grants`);
|
|
17
|
+
}
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
for (const tool of tools) {
|
|
20
|
+
if (seen.has(tool.name)) {
|
|
21
|
+
throw new Error(`MCP server "${server}" lists the tool "${tool.name}" more than once`);
|
|
22
|
+
}
|
|
23
|
+
seen.add(tool.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Refuse a server list before any request: every name non-empty and unique,
|
|
28
|
+
* so no two servers share a grant prefix, and every URL a permitted endpoint.
|
|
29
|
+
*/
|
|
30
|
+
export function assertServers(servers, label) {
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
for (const { name, url } of servers) {
|
|
33
|
+
if (name.length === 0)
|
|
34
|
+
throw new Error(`empty ${label}`);
|
|
35
|
+
if (seen.has(name))
|
|
36
|
+
throw new Error(`duplicate ${label} "${name}"`);
|
|
37
|
+
seen.add(name);
|
|
38
|
+
parseMcpEndpoint(url);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
8
41
|
export function toolDescription(tool, server) {
|
|
9
42
|
const hints = Object.entries(tool.annotations ?? {})
|
|
10
43
|
.filter(([, v]) => v === true)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { FetchLike } from "@intx/harness";
|
|
2
|
+
import { type OAuthClientConfig } from "@corbits/oauth-core";
|
|
3
|
+
export type McpAuthorizationServer = {
|
|
4
|
+
issuer: string;
|
|
5
|
+
authorizationEndpoint: string;
|
|
6
|
+
tokenEndpoint: string;
|
|
7
|
+
registrationEndpoint?: string;
|
|
8
|
+
grantTypesSupported?: readonly string[];
|
|
9
|
+
scopesSupported?: readonly string[];
|
|
10
|
+
tokenEndpointAuthMethodsSupported?: readonly string[];
|
|
11
|
+
codeChallengeMethodsSupported?: readonly string[];
|
|
12
|
+
};
|
|
13
|
+
export type McpLoginEntry = {
|
|
14
|
+
resourceUrl: string;
|
|
15
|
+
authorizationServer: McpAuthorizationServer;
|
|
16
|
+
resourceScopesSupported?: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
export type DiscoverMcpLoginEntryOptions = {
|
|
19
|
+
resourceUrl: string;
|
|
20
|
+
fetchImpl?: FetchLike;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
};
|
|
23
|
+
export type RegisterMcpClientOptions = {
|
|
24
|
+
registrationEndpoint: string;
|
|
25
|
+
redirectUris: readonly string[];
|
|
26
|
+
clientName: string;
|
|
27
|
+
grantTypesSupported?: readonly string[];
|
|
28
|
+
scope?: string;
|
|
29
|
+
fetchImpl?: FetchLike;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
};
|
|
32
|
+
export type McpClientRegistration = {
|
|
33
|
+
clientId: string;
|
|
34
|
+
grantTypes: string[];
|
|
35
|
+
scope?: string;
|
|
36
|
+
};
|
|
37
|
+
export type McpClientConfigOptions = {
|
|
38
|
+
clientId: string;
|
|
39
|
+
redirectUri: string;
|
|
40
|
+
scopes?: readonly string[];
|
|
41
|
+
challengeScope?: string;
|
|
42
|
+
wantRefresh?: boolean;
|
|
43
|
+
extraAuthorizeParams?: Record<string, string>;
|
|
44
|
+
tokenTimeoutMs?: number;
|
|
45
|
+
};
|
|
46
|
+
export type SelectMcpScopesOptions = {
|
|
47
|
+
entry: McpLoginEntry;
|
|
48
|
+
challengeScope?: string;
|
|
49
|
+
wantRefresh?: boolean;
|
|
50
|
+
};
|
|
51
|
+
export declare function discoverMcpLoginEntry(opts: DiscoverMcpLoginEntryOptions): Promise<McpLoginEntry>;
|
|
52
|
+
/**
|
|
53
|
+
* Register a loopback public client with the authorization server (RFC 7591).
|
|
54
|
+
* Loopback clients cannot keep a secret, so registration requests
|
|
55
|
+
* `token_endpoint_auth_method: "none"`. `grant_types` is negotiated from
|
|
56
|
+
* `grantTypesSupported` (see grantTypesToRequest); `scope`, when supplied, is
|
|
57
|
+
* carried on the registration request per RFC 7591 §2. Throws
|
|
58
|
+
* OAuthDiscoveryError when the server has no registration endpoint or
|
|
59
|
+
* rejects the request.
|
|
60
|
+
*/
|
|
61
|
+
export declare function registerMcpClient(opts: RegisterMcpClientOptions): Promise<McpClientRegistration>;
|
|
62
|
+
export declare function selectMcpScopes(opts: SelectMcpScopesOptions): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Build the OAuth client config for a discovered entry plus a registered
|
|
65
|
+
* client id. The RFC 8707 `resource` indicator binds the token to the MCP
|
|
66
|
+
* server; callers pass further provider-required params through
|
|
67
|
+
* `extraAuthorizeParams`. Scopes come from `selectMcpScopes` unless the
|
|
68
|
+
* caller passes an explicit `scopes` override; `wantRefresh` defaults to
|
|
69
|
+
* true, matching this package's default refresh_token registration request.
|
|
70
|
+
*/
|
|
71
|
+
export declare function mcpClientConfig(entry: McpLoginEntry, opts: McpClientConfigOptions): OAuthClientConfig;
|