@yanlinglabs/winter-agent-runtime 0.0.29 → 0.0.30
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/dist/embedded-worker.js +6 -4
- package/dist/embedded.js +6 -4
- package/dist/engine.d.ts +16 -0
- package/dist/{index-jz5d90c6.js → index-51673qbw.js} +151 -120
- package/dist/{index-gmxxtqpe.js → index-9df1q7j4.js} +30 -12
- package/dist/{index-8jgwp1px.js → index-9s68t0pd.js} +170 -11
- package/dist/index-bgeqc7ax.js +52 -0
- package/dist/{index-eyddzcgs.js → index-wsfqsrh5.js} +2 -2
- package/dist/index-z6s3r4xm.js +903 -0
- package/dist/index.js +4 -2
- package/dist/mcp/client.d.ts +20 -1
- package/dist/mcp/lifecycle.d.ts +8 -0
- package/dist/mcp/transports/http.d.ts +10 -1
- package/dist/mcp/transports/sse.d.ts +4 -2
- package/dist/mcp-auth/account.d.ts +33 -0
- package/dist/mcp-auth/config.d.ts +6 -0
- package/dist/mcp-auth/constants.d.ts +30 -0
- package/dist/mcp-auth/discovery.d.ts +72 -0
- package/dist/mcp-auth/engine-wiring.d.ts +52 -0
- package/dist/mcp-auth/errors.d.ts +37 -0
- package/dist/mcp-auth/fetch-policy.d.ts +33 -0
- package/dist/mcp-auth/login.d.ts +59 -0
- package/dist/mcp-auth/records.d.ts +73 -0
- package/dist/mcp-auth/refresh.d.ts +34 -0
- package/dist/mcp-auth/revoke.d.ts +15 -0
- package/dist/mcp-auth/session-provider.d.ts +55 -0
- package/dist/mcp-auth/store.d.ts +85 -0
- package/dist/mcp-auth/test-fixture-as.d.ts +72 -0
- package/dist/mcp-auth.d.ts +14 -0
- package/dist/mcp-auth.js +493 -0
- package/dist/mcp-client.d.ts +12 -2
- package/dist/mcp-client.js +15 -2
- package/dist/production-wiring.d.ts +6 -0
- package/dist/provider/host-credentials.d.ts +48 -0
- package/dist/provider/keychain-store.d.ts +29 -1
- package/dist/provider/session-provider.d.ts +1 -1
- package/dist/store/dialect.d.ts +1 -1
- package/dist/testing.js +5 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -53,8 +53,10 @@ import {
|
|
|
53
53
|
DEFAULT_CLASSIFIER_TIMEOUT_MS2,
|
|
54
54
|
createModelClassifier2,
|
|
55
55
|
selectClassifierRoute2
|
|
56
|
-
} from "./index-
|
|
57
|
-
import"./index-
|
|
56
|
+
} from "./index-51673qbw.js";
|
|
57
|
+
import"./index-9s68t0pd.js";
|
|
58
|
+
import"./index-z6s3r4xm.js";
|
|
59
|
+
import"./index-bgeqc7ax.js";
|
|
58
60
|
import"./index-bef62z3r.js";
|
|
59
61
|
import {
|
|
60
62
|
createInMemoryChannel2
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { InsufficientScopeError } from "@modelcontextprotocol/client";
|
|
1
2
|
import type { McpServerConfigForProcessTransport, McpVersionNegotiation } from "@yanlinglabs/winter-agent-sdk";
|
|
2
3
|
import { type InProcessMcpServer } from "./transports/sdk.js";
|
|
3
4
|
import { type ElicitationAsker } from "./elicitation.js";
|
|
5
|
+
import { type McpSessionAuthProvider } from "../mcp-auth/session-provider.js";
|
|
4
6
|
/**
|
|
5
7
|
* WHY a connection attempt failed -- a category, not a message (the message is for humans; this is what
|
|
6
8
|
* the state board's `errorCode`, the web search backend and the `'auto'` legacy retry read).
|
|
@@ -16,7 +18,7 @@ import { type ElicitationAsker } from "./elicitation.js";
|
|
|
16
18
|
* A probe that went unanswered is `timeout` and a probe answered with an HTTP error keeps
|
|
17
19
|
* `handshake_failed` plus its `httpStatus`, as before.
|
|
18
20
|
*/
|
|
19
|
-
export type McpConnectErrorCode = "timeout" | "spawn_failed" | "handshake_failed" | "version_mismatch" | "transport_closed" | "needs_auth" | "unknown";
|
|
21
|
+
export type McpConnectErrorCode = "timeout" | "spawn_failed" | "handshake_failed" | "version_mismatch" | "transport_closed" | "needs_auth" | "auth_refresh_failed" | "unknown";
|
|
20
22
|
export declare class McpConnectError extends Error {
|
|
21
23
|
readonly code: McpConnectErrorCode;
|
|
22
24
|
/**
|
|
@@ -113,8 +115,25 @@ export interface ConnectMcpServerOptions {
|
|
|
113
115
|
* notifications would have nothing to update.
|
|
114
116
|
*/
|
|
115
117
|
onToolListChanged?: () => void;
|
|
118
|
+
/**
|
|
119
|
+
* WS-25 (MCP OAuth): the session's read-only bearer provider for THIS server (`http`/`sse` only;
|
|
120
|
+
* mcp-auth/session-provider.ts). Its `preflight()` runs before the transport exists -- a stored sign-in
|
|
121
|
+
* that is already dead is `needs_auth` with no request sent (spec §1.2) -- and the transport then reads
|
|
122
|
+
* the bearer from it per request, with every redirect refused. Absent: no `Authorization` beyond the
|
|
123
|
+
* config's own headers, exactly as before.
|
|
124
|
+
*/
|
|
125
|
+
auth?: McpSessionAuthProvider;
|
|
116
126
|
}
|
|
117
127
|
export declare function resolveVersionNegotiation(config: McpServerConfigForProcessTransport): McpVersionNegotiation;
|
|
118
128
|
export declare const AUTO_PROBE_TIMEOUT_CAP_MS = 5000;
|
|
119
129
|
export declare const MANUAL_LIST_MAX_PAGES = 64;
|
|
120
130
|
export declare function connectMcpServer(opts: ConnectMcpServerOptions): Promise<ConnectedMcpClient>;
|
|
131
|
+
/** A `403 insufficient_scope` challenge, found directly or as the cause the v2 client wrapped it in. */
|
|
132
|
+
export declare function insufficientScopeOf(err: unknown): InsufficientScopeError | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* What an error from a LIVE connection's request says about its sign-in: `needs_auth` (the sign-in is
|
|
135
|
+
* gone -- a 401 the provider could not recover, or the provider's own verdict), `insufficient_scope` (a
|
|
136
|
+
* 403 step-up this one call needs), or `undefined` (anything else: an ordinary tool failure). Walks a few
|
|
137
|
+
* `cause` links, since the v2 client wraps some transport failures.
|
|
138
|
+
*/
|
|
139
|
+
export declare function classifyMcpAuthFailure(err: unknown): "needs_auth" | "insufficient_scope" | undefined;
|
package/dist/mcp/lifecycle.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { McpServerStateSource } from "./state.js";
|
|
|
3
3
|
import type { McpControlSeam } from "./control-seam.js";
|
|
4
4
|
import type { McpEnvConfig } from "./env.js";
|
|
5
5
|
import { type McpToolInfo, type ConnectedMcpClient } from "./client.js";
|
|
6
|
+
import { type McpSessionOAuth } from "../mcp-auth/session-provider.js";
|
|
6
7
|
import type { ElicitationAsker } from "./elicitation.js";
|
|
7
8
|
import type { InProcessMcpServer } from "./transports/sdk.js";
|
|
8
9
|
export type McpConfigSourceOrigin = "explicit" | "settings" | "project" | "plugin" | "dynamic";
|
|
@@ -110,6 +111,13 @@ export interface McpLifecycleDeps {
|
|
|
110
111
|
reservedServerName?: string;
|
|
111
112
|
/** WS-23: the session cwd every stdio server of this lifecycle is spawned in (see `ConnectMcpServerOptions.cwd`). */
|
|
112
113
|
cwd?: string;
|
|
114
|
+
/**
|
|
115
|
+
* WS-25 (MCP OAuth): the session's sign-ins -- the token store (read-only), the host's refresh door and
|
|
116
|
+
* the sign-in hint. Present, every `http`/`sse` server WITHOUT a static `Authorization` header connects
|
|
117
|
+
* through a read-only bearer provider (`sessionAuthFor`); a server that answers 401 then lands in
|
|
118
|
+
* `needsAuth` with its tools unregistered. Absent: no provider anywhere, byte-identical to before.
|
|
119
|
+
*/
|
|
120
|
+
oauth?: McpSessionOAuth;
|
|
113
121
|
}
|
|
114
122
|
export type RefreshServerToolsResult = {
|
|
115
123
|
ok: true;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { type Transport } from "@modelcontextprotocol/client";
|
|
1
|
+
import { type AuthProvider, type Transport } from "@modelcontextprotocol/client";
|
|
2
2
|
import type { McpHttpServerConfig } from "@yanlinglabs/winter-agent-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* WS-25: the network an AUTHENTICATED transport (one with an `authProvider`) uses -- the platform `fetch`
|
|
5
|
+
* with every redirect REFUSED. A bearer token rides the `Authorization` header, which fetch strips on a
|
|
6
|
+
* cross-origin hop, but the MCP request BODY (tool arguments drawn from the conversation) does not get
|
|
7
|
+
* stripped, and a server that redirects an authenticated MCP connection is not one to follow. Read at call
|
|
8
|
+
* time, so a test's network guard (which wraps the global) still sees every request.
|
|
9
|
+
*/
|
|
10
|
+
export declare const refusingRedirectsFetch: typeof fetch;
|
|
3
11
|
export declare function buildHttpTransport(cfg: McpHttpServerConfig, opts?: {
|
|
4
12
|
refuseRedirects?: boolean;
|
|
13
|
+
authProvider?: AuthProvider;
|
|
5
14
|
}): Transport;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { type Transport } from "@modelcontextprotocol/client";
|
|
1
|
+
import { type AuthProvider, type Transport } from "@modelcontextprotocol/client";
|
|
2
2
|
import type { McpSSEServerConfig } from "@yanlinglabs/winter-agent-sdk";
|
|
3
|
-
export declare function buildSseTransport(cfg: McpSSEServerConfig
|
|
3
|
+
export declare function buildSseTransport(cfg: McpSSEServerConfig, opts?: {
|
|
4
|
+
authProvider?: AuthProvider;
|
|
5
|
+
}): Transport;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The token item's account prefix: `mcp-oauth:<id>`. Sessions read it; the host writes it. */
|
|
2
|
+
export declare const MCP_OAUTH_TOKEN_ACCOUNT_PREFIX = "mcp-oauth:";
|
|
3
|
+
/** The client-registration item's account prefix: `mcp-oauth-client:<id>`. Only the host reads it. */
|
|
4
|
+
export declare const MCP_OAUTH_CLIENT_ACCOUNT_PREFIX = "mcp-oauth-client:";
|
|
5
|
+
/**
|
|
6
|
+
* The canonical form of an MCP server URL: lower-cased scheme and host (the URL parser does both), the
|
|
7
|
+
* scheme's default port dropped (likewise), the path KEPT (two servers under one origin are two servers),
|
|
8
|
+
* no query, no fragment, and trailing slashes removed (`https://x/mcp/` and `https://x/mcp` are the same
|
|
9
|
+
* server; `https://x/` and `https://x` both become `https://x`).
|
|
10
|
+
*
|
|
11
|
+
* Refused, typed (`invalid_server_url`): an unparseable URL, a non-http(s) scheme, and userinfo -- a
|
|
12
|
+
* credential in a URL is never a key, and never silently dropped into one either.
|
|
13
|
+
*/
|
|
14
|
+
export declare function canonicalMcpServerUrl(serverUrl: string): string;
|
|
15
|
+
/** `sha256(canonicalMcpServerUrl(serverUrl))`, first 16 hex characters. The ONE key derivation. */
|
|
16
|
+
export declare function mcpOAuthAccountId(serverUrl: string): string;
|
|
17
|
+
/** The token item's full account name for a server URL: `mcp-oauth:<id>`. This is the `account` every door takes. */
|
|
18
|
+
export declare function mcpOAuthTokenAccount(serverUrl: string): string;
|
|
19
|
+
/** The client-registration item's full account name for a server URL: `mcp-oauth-client:<id>`. */
|
|
20
|
+
export declare function mcpOAuthClientAccount(serverUrl: string): string;
|
|
21
|
+
/** The pre-registered client secret's account prefix: `mcp-oauth-client-secret:<id>`. Only the host reads it. */
|
|
22
|
+
export declare const MCP_OAUTH_CLIENT_SECRET_ACCOUNT_PREFIX = "mcp-oauth-client-secret:";
|
|
23
|
+
/**
|
|
24
|
+
* The ONE Keychain account a pre-registered client's secret is read from: `mcp-oauth-client-secret:<id>`,
|
|
25
|
+
* DERIVED from the server URL and never named by a config (see `McpOAuthSecretRef`: a config-named
|
|
26
|
+
* account could point the sign-in at a provider API key and hand it to an attacker's authorization
|
|
27
|
+
* server). The host writes the secret here (a masked prompt, a secure field); the sign-in reads it here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mcpOAuthClientSecretAccount(serverUrl: string): string;
|
|
30
|
+
/** True for a well-formed token account name (`mcp-oauth:` + 16 lower-case hex). */
|
|
31
|
+
export declare function isMcpOAuthTokenAccount(account: string): boolean;
|
|
32
|
+
/** The client item's account for a TOKEN account -- the prefix swap. Refuses anything that is not a token account. */
|
|
33
|
+
export declare function clientAccountForTokenAccount(account: string): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** `undefined` when `oauth` is a valid `McpOAuthConfig`; otherwise why not (prose naming the field, never quoting a secret). */
|
|
2
|
+
/**
|
|
3
|
+
* `serverUrl` is the server config's own `url`: an `authServerMetadataUrl` may name a loopback address
|
|
4
|
+
* only when the server itself is on loopback (fix round 1 M4). Absent, loopback is refused.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateMcpOAuthConfig(oauth: unknown, serverUrl?: string): string | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Winter's Client ID Metadata Document (CIMD, draft-ietf-oauth-client-id-metadata-document; MCP spec
|
|
3
|
+
* 2025-11-25's preferred registration): an authorization server that advertises
|
|
4
|
+
* `client_id_metadata_document_supported` fetches THIS URL as Winter's client id, and no per-server
|
|
5
|
+
* registration exists at all.
|
|
6
|
+
*
|
|
7
|
+
* CONFIRMED by the user (2026-09-27, spec §1.5): the permanent URL on the user's Cloudflare domain
|
|
8
|
+
* `yanlinglabs.com`. Publishing the document there is a separate controller step. It is ONE constant,
|
|
9
|
+
* and every door that uses it takes a `clientMetadataUrl` override (tests pass their own).
|
|
10
|
+
*
|
|
11
|
+
* THE DOCUMENT'S REDIRECT URIS (spec §4.2, settled): `["http://127.0.0.1/callback"]` -- a loopback IP
|
|
12
|
+
* literal with NO port. A CIMD document is global, so it cannot name the port one machine's listener
|
|
13
|
+
* got; RFC 8252 §7.3 obliges an authorization server to "allow any port to be specified at the time of
|
|
14
|
+
* the request for loopback IP redirect URIs", which is exactly how a native client with an ephemeral
|
|
15
|
+
* port registers once. Winter binds `127.0.0.1` (never `localhost`, RFC 8252 §8.3) on the config's
|
|
16
|
+
* `callbackPort` or an ephemeral port, and sends that exact URI. An authorization server that ignores
|
|
17
|
+
* §7.3 fails at its own consent page; the fallback is a pre-registered client (`oauth.clientId`) with a
|
|
18
|
+
* fixed `oauth.callbackPort`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WINTER_MCP_CLIENT_METADATA_URL = "https://yanlinglabs.com/winter/oauth-client.json";
|
|
21
|
+
/** The loopback redirect's path. The CIMD document registers `http://127.0.0.1/callback` with it. */
|
|
22
|
+
export declare const MCP_OAUTH_CALLBACK_PATH = "/callback";
|
|
23
|
+
/** A token within this many ms of `expiresAt` counts as expired: it is refreshed before a request can fail with it (spec §1.2). */
|
|
24
|
+
export declare const MCP_OAUTH_EXPIRY_SKEW_MS = 60000;
|
|
25
|
+
/** How long a session trusts its last Keychain read of a token before re-reading it (spec §3, "~30 s"). */
|
|
26
|
+
export declare const MCP_OAUTH_SESSION_CACHE_MS = 30000;
|
|
27
|
+
/** An interactive sign-in's whole budget: the listener closes and `done` settles `login_timeout` after it. */
|
|
28
|
+
export declare const MCP_OAUTH_LOGIN_TIMEOUT_MS: number;
|
|
29
|
+
/** The `state` parameter's entropy: 32 random bytes, base64url. */
|
|
30
|
+
export declare const MCP_OAUTH_STATE_BYTES = 32;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type AuthorizationServerMetadata } from "@modelcontextprotocol/client";
|
|
2
|
+
import type { McpOAuthConfig } from "@yanlinglabs/winter-agent-sdk";
|
|
3
|
+
import { type McpAuthFetch } from "./fetch-policy.js";
|
|
4
|
+
/** True when `url` names a metadata DOCUMENT (the config's `oauth.authServerMetadataUrl`) rather than an issuer. */
|
|
5
|
+
export declare function isMetadataDocumentUrl(url: string): boolean;
|
|
6
|
+
export declare function fetchAuthorizationServerMetadataDocument(url: string, fetchFn: McpAuthFetch): Promise<AuthorizationServerMetadata>;
|
|
7
|
+
export interface LoadedAuthorizationServer {
|
|
8
|
+
/** What the token-endpoint helpers take as `authorizationServerUrl` (the legacy `/token` fallback's base). */
|
|
9
|
+
authorizationServerUrl: string;
|
|
10
|
+
/** Absent for the legacy variant (no RFC 8414 document anywhere): the helpers then use `/token` on the base. */
|
|
11
|
+
metadata?: AuthorizationServerMetadata;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The authorization server a stored sign-in belongs to. `expectedIssuer` is the token record's: a
|
|
15
|
+
* document that now names another issuer is refused (tokens are bound to the issuer that minted them,
|
|
16
|
+
* SEP-2352) rather than sent a refresh token it never issued.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadAuthorizationServer(opts: {
|
|
19
|
+
authorizationServerUrl: string;
|
|
20
|
+
expectedIssuer: string;
|
|
21
|
+
fetchFn: McpAuthFetch;
|
|
22
|
+
}): Promise<LoadedAuthorizationServer>;
|
|
23
|
+
/**
|
|
24
|
+
* The RFC 8707 `resource` a token request names: the protected-resource metadata's own `resource`
|
|
25
|
+
* string, VERBATIM (the MCP client's `auth()` sends it that way, #1968), when the document exists and
|
|
26
|
+
* matches the server; absent for a server that publishes none (the legacy variant).
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveResourceIndicator(opts: {
|
|
29
|
+
serverUrl: string;
|
|
30
|
+
resourceMetadataUrl?: string;
|
|
31
|
+
fetchFn: McpAuthFetch;
|
|
32
|
+
}): Promise<string | undefined>;
|
|
33
|
+
export interface DiscoverMcpOAuthIssuerOptions {
|
|
34
|
+
/** The MCP server's URL (the protected resource) -- the same value `startMcpOAuthLogin` takes. */
|
|
35
|
+
serverUrl: string;
|
|
36
|
+
/** The server config's `oauth` block, when it has one (only `authServerMetadataUrl` matters here). */
|
|
37
|
+
oauth?: McpOAuthConfig;
|
|
38
|
+
/** The network under the auth-HTTP policy (tests; a host that must go through its own dispatcher). */
|
|
39
|
+
fetch?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
export interface McpOAuthIssuerInfo {
|
|
42
|
+
/** The full verified issuer string -- identical to what a sign-in on this config would store/return. */
|
|
43
|
+
issuer: string;
|
|
44
|
+
/** `issuer`'s origin, for display only (compare tenants by `issuer`/`sameIssuer`, never this). */
|
|
45
|
+
issuerOrigin: string;
|
|
46
|
+
/** The origin the authorize request would load in a browser -- usually the issuer's. */
|
|
47
|
+
authorizeOrigin: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* WS-25, additive: finds a server's authorization server WITHOUT signing in -- no registration, no code
|
|
51
|
+
* exchange, no loopback listener, no store read or write, no client secret ever touched. For a caller that
|
|
52
|
+
* must know a server's issuer before (or without) starting an interactive sign-in: the daemon's own
|
|
53
|
+
* authorization-server bookkeeping, which compares SERVERS, not origins (an origin can host many issuers
|
|
54
|
+
* behind tenant paths -- `https://host/tenant/a` and `https://host/tenant/b` are two authorization servers
|
|
55
|
+
* on the same origin, and a comparison that stopped at `issuerOrigin` would treat them as one).
|
|
56
|
+
*
|
|
57
|
+
* MIRRORS `startMcpOAuthLogin`'s own discovery leg exactly, so this door's `issuer` is PROVABLY the same
|
|
58
|
+
* string a sign-in on the same config would store (`flow.test.ts` pins the equality against the fixture):
|
|
59
|
+
* - `oauth.authServerMetadataUrl` set: `fetchAuthorizationServerMetadataDocument` (login.ts's seeded
|
|
60
|
+
* path, `startMcpOAuthLogin` line ~252-261) -- the SAME RFC 8414 issuer-location check
|
|
61
|
+
* (`fetchedFromIssuersOwnLocation`, above), refusing `metadata_issuer_mismatch` on a document a
|
|
62
|
+
* config's `authServerMetadataUrl` did not actually come from.
|
|
63
|
+
* - absent: `discoverOAuthServerInfo` (RFC 9728 PRM -> RFC 8414 / OIDC discovery) -- the exact function
|
|
64
|
+
* the MCP client package's own `auth()` calls for a fresh (uncached) flow, so an unseeded sign-in's
|
|
65
|
+
* issuer is this door's issuer too, not a second implementation of the same two steps.
|
|
66
|
+
*
|
|
67
|
+
* `authorizeOrigin` is derived without ever building a real authorize URL (that needs a registered
|
|
68
|
+
* client, which this door never creates): a metadata document's own `authorization_endpoint`, or --
|
|
69
|
+
* the legacy variant, no document anywhere -- `startAuthorization`'s own fallback, `/authorize` on the
|
|
70
|
+
* authorization server's base URL (same origin either way).
|
|
71
|
+
*/
|
|
72
|
+
export declare function discoverMcpOAuthIssuer(opts: DiscoverMcpOAuthIssuerOptions): Promise<McpOAuthIssuerInfo>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AttachmentPayload } from "../context/attachments.js";
|
|
2
|
+
import type { ProviderMessage } from "../engine.js";
|
|
3
|
+
import type { McpServerState } from "../mcp/state.js";
|
|
4
|
+
import { type McpSessionOAuth } from "./session-provider.js";
|
|
5
|
+
import type { McpOAuthStore } from "./store.js";
|
|
6
|
+
/** How long a session waits for its host to refresh a sign-in before treating the ask as transient. */
|
|
7
|
+
export declare const MCP_OAUTH_HOST_REFRESH_TIMEOUT_MS = 60000;
|
|
8
|
+
/** The narrow view of the session's control bridge this needs (the runtime's `RpcBridge` satisfies it). */
|
|
9
|
+
export interface McpOAuthHostSender {
|
|
10
|
+
request<T = unknown>(subtype: string, payload: unknown, opts?: {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The session's sign-in wiring. `askHost` answers `"unhandled"` ONLY for the host's own "no handler
|
|
16
|
+
* registered" reply -- the standalone case, which refreshes in-process. Every other failure of the round
|
|
17
|
+
* trip (a timeout, a dead transport, a malformed answer) is `transient`: a host that HAS a handler must
|
|
18
|
+
* never be bypassed by a session that then posts the refresh itself.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSessionMcpOAuth(opts: {
|
|
21
|
+
store: McpOAuthStore;
|
|
22
|
+
sender: McpOAuthHostSender;
|
|
23
|
+
brand: {
|
|
24
|
+
homeDirName: string;
|
|
25
|
+
productName: string;
|
|
26
|
+
};
|
|
27
|
+
hostOwnsRefresh?: boolean;
|
|
28
|
+
}): McpSessionOAuth;
|
|
29
|
+
/** The servers in `needsAuth`, sorted: the notice's subject and the hint's lookup. */
|
|
30
|
+
export declare function needsAuthServers(states: readonly McpServerState[] | undefined): string[];
|
|
31
|
+
/**
|
|
32
|
+
* The hint a call to a tool of a server that needs sign-in gets, appended to "No such tool available"
|
|
33
|
+
* (spec §1.3: the call answers with the door, never a browser and never a model-callable auth tool).
|
|
34
|
+
* Matched on the `mcp__<server>__` prefix of each needs-auth server by NAME, not by splitting the tool
|
|
35
|
+
* name (a server name may itself contain `__`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function needsAuthToolHint(states: readonly McpServerState[] | undefined, toolName: string, brand: {
|
|
38
|
+
homeDirName: string;
|
|
39
|
+
productName: string;
|
|
40
|
+
}): string;
|
|
41
|
+
export interface McpNeedsAuthAttachment extends AttachmentPayload {
|
|
42
|
+
type: "mcp_needs_auth";
|
|
43
|
+
/** The servers that need sign-in now (sorted); empty means "none any more". */
|
|
44
|
+
servers: string[];
|
|
45
|
+
/** The door, captured at production time for the `<server>` placeholder (a renderer has no brand). */
|
|
46
|
+
door: string;
|
|
47
|
+
}
|
|
48
|
+
/** The notice to append now, or `undefined` when the needs-auth set is what the history already says. */
|
|
49
|
+
export declare function mcpNeedsAuthAttachment(states: readonly McpServerState[] | undefined, messages: readonly ProviderMessage[], brand: {
|
|
50
|
+
homeDirName: string;
|
|
51
|
+
productName: string;
|
|
52
|
+
}): McpNeedsAuthAttachment | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* - `malformed_record` / `unsupported_record_version` -- a Keychain item that is not a record this SDK
|
|
3
|
+
* wrote (bad JSON, a missing field, the wrong `kind`), or one from a NEWER record version (`v`), which
|
|
4
|
+
* is refused rather than guessed at.
|
|
5
|
+
* - `invalid_server_url` -- not an absolute http(s) URL, or one carrying userinfo.
|
|
6
|
+
* - `invalid_account` -- not an `mcp-oauth:<16 hex>` token account name.
|
|
7
|
+
* - `policy_refused` -- the auth-HTTP policy refused a URL (plain http off loopback, a literal private or
|
|
8
|
+
* link-local address, a cross-origin redirect, an oversized answer).
|
|
9
|
+
* - `network` -- an auth request never got an answer (a refused connection, a timeout): retry later.
|
|
10
|
+
* - `login_superseded` / `login_cancelled` / `login_timeout` -- the interactive sign-in ended without a
|
|
11
|
+
* callback (a newer sign-in for the same server, `cancel()`, the 5-minute bound).
|
|
12
|
+
* - `state_mismatch` -- the loopback callback carried a `state` this flow never issued.
|
|
13
|
+
* - `authorization_denied` -- the authorization server redirected back with `error=...`.
|
|
14
|
+
* - `client_secret_unavailable` -- a pre-registered client's `clientSecretRef` could not be read.
|
|
15
|
+
* - `callback_port_unavailable` -- the configured `oauth.callbackPort` is taken.
|
|
16
|
+
* - `metadata_issuer_mismatch` -- a configured authorization server metadata document names an issuer it
|
|
17
|
+
* was not published under (RFC 8414 §3.3).
|
|
18
|
+
* - `client_secret_issuer_mismatch` -- a pre-registered client secret would go to an authorization server
|
|
19
|
+
* other than the one it is bound to (its stamped/expected issuer, or an existing registration's).
|
|
20
|
+
* - `login_failed` -- discovery, registration or the code exchange failed (the message says which step,
|
|
21
|
+
* and on the exchange never quotes the authorization server).
|
|
22
|
+
* - `not_implemented` -- a contract stub (WS-25 lands its types first).
|
|
23
|
+
*/
|
|
24
|
+
export type McpOAuthErrorCode = "malformed_record" | "unsupported_record_version" | "invalid_server_url" | "invalid_account" | "policy_refused" | "network" | "login_superseded" | "login_cancelled" | "login_timeout" | "state_mismatch" | "authorization_denied" | "client_secret_unavailable" | "callback_port_unavailable" | "client_secret_issuer_mismatch" | "metadata_issuer_mismatch" | "login_failed" | "not_implemented";
|
|
25
|
+
export declare class McpOAuthError extends Error {
|
|
26
|
+
readonly code: McpOAuthErrorCode;
|
|
27
|
+
constructor(code: McpOAuthErrorCode, message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Error text safe to hand a host: an `McpOAuthError`'s own message (already bounded and code-shaped by its
|
|
31
|
+
* throw site), an `OAuthError`'s code alone, or a bounded, control-free NAME + message -- never an
|
|
32
|
+
* authorization server's raw response body (a token endpoint that echoes its request would put a refresh
|
|
33
|
+
* token or a client secret in it). Shared by every `mcp-auth` door that wraps a step it did not throw
|
|
34
|
+
* itself (login's discovery/registration leg, this file's discovery-only door) so the bound lives in ONE
|
|
35
|
+
* place rather than one copy per door drifting apart.
|
|
36
|
+
*/
|
|
37
|
+
export declare function boundedReason(err: unknown): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The MCP client package's `FetchLike`: what `auth()`, `refreshAuthorization()` and friends call. */
|
|
2
|
+
export type McpAuthFetch = (url: string | URL, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export declare const MCP_AUTH_FETCH_TIMEOUT_MS = 30000;
|
|
4
|
+
/** Metadata documents, a registration answer and a token answer are all small; 1 MiB is generous. */
|
|
5
|
+
export declare const MCP_AUTH_FETCH_MAX_BODY_BYTES: number;
|
|
6
|
+
export interface McpAuthFetchOptions {
|
|
7
|
+
/** The network. Absent: provider-runtime's `boundedFetch`. See this file's header for what changes when it is injected. */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
maxBodyBytes?: number;
|
|
11
|
+
/** Whether a literal loopback address may be reached at all: true only when the MCP server itself is loopback. Default false. */
|
|
12
|
+
allowLoopback?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type McpAuthUrlVerdict = {
|
|
15
|
+
ok: true;
|
|
16
|
+
origin: string;
|
|
17
|
+
loopback: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The URL rules (1, 2 and 5 of the header), for ONE absolute URL. Exported because the sign-in applies
|
|
24
|
+
* the same rules to URLs it never fetches: the authorize URL a browser opens, and the MCP server URL
|
|
25
|
+
* itself.
|
|
26
|
+
*/
|
|
27
|
+
export declare function evaluateMcpAuthUrl(raw: string | URL, opts?: {
|
|
28
|
+
allowLoopback?: boolean;
|
|
29
|
+
}): McpAuthUrlVerdict;
|
|
30
|
+
/** True when the MCP server URL is itself a literal loopback address -- the one case its auth URLs may be too. */
|
|
31
|
+
export declare function isLoopbackMcpServer(serverUrl: string): boolean;
|
|
32
|
+
/** Builds THE auth fetch. Every `mcp-auth` door builds one per operation from its own `fetch?` option. */
|
|
33
|
+
export declare function createMcpAuthFetch(opts?: McpAuthFetchOptions): McpAuthFetch;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { McpOAuthConfig } from "@yanlinglabs/winter-agent-sdk";
|
|
2
|
+
import { type McpOAuthStore } from "./store.js";
|
|
3
|
+
export interface StartMcpOAuthLoginOptions {
|
|
4
|
+
/** The MCP server's URL (the protected resource). Its canonical form keys the Keychain items. */
|
|
5
|
+
serverUrl: string;
|
|
6
|
+
/** The server config's `oauth` block, when it has one. */
|
|
7
|
+
oauth?: McpOAuthConfig;
|
|
8
|
+
/** Where the sign-in lands: the token item and the client registration. */
|
|
9
|
+
store: McpOAuthStore;
|
|
10
|
+
/** Overrides `WINTER_MCP_CLIENT_METADATA_URL` (tests; a host that publishes its own CIMD document). */
|
|
11
|
+
clientMetadataUrl?: string;
|
|
12
|
+
/** The network under the auth-HTTP policy (tests pass the fixture's). The policy is applied on top, always. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
/** The clock (tests). Epoch ms. */
|
|
15
|
+
now?: () => number;
|
|
16
|
+
/**
|
|
17
|
+
* WS-25, additive: reads a pre-registered client's secret when the config marks one
|
|
18
|
+
* (`oauth.clientSecretRef: { kind: "keychain" }`). It is called with the ONE account the secret may live
|
|
19
|
+
* at -- `mcpOAuthClientSecretAccount(serverUrl)`, derived, never config-named (fix round 1) -- and a
|
|
20
|
+
* host passes it only to read that account from somewhere other than `store`. Absent: `store` is read
|
|
21
|
+
* at that account.
|
|
22
|
+
*/
|
|
23
|
+
readClientSecret?: (account: string) => Promise<string | null>;
|
|
24
|
+
/** WS-25, additive: the whole sign-in's budget in ms (default `MCP_OAUTH_LOGIN_TIMEOUT_MS`, 5 minutes). */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export type McpOAuthLoginOutcome = {
|
|
28
|
+
ok: true;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
export interface McpOAuthLogin {
|
|
34
|
+
/** The authorization server's authorize URL, for the HOST to open in a browser. HTTPS (or literal loopback). */
|
|
35
|
+
authUrl: string;
|
|
36
|
+
/**
|
|
37
|
+
* The FULL verified issuer string this sign-in used -- RFC 8414 `issuer` when the server published
|
|
38
|
+
* metadata, else the authorization server URL discovery landed on (the legacy variant). Two tenants
|
|
39
|
+
* behind one reverse proxy (`https://host/tenant/a`, `https://host/tenant/b`) share an ORIGIN but never
|
|
40
|
+
* this string: a caller that must tell them apart (the daemon's own authorization-server bookkeeping)
|
|
41
|
+
* compares THIS, with `sameIssuer` (`records.ts`), never `issuerOrigin`.
|
|
42
|
+
*/
|
|
43
|
+
issuer: string;
|
|
44
|
+
/** The authorization server's issuer ORIGIN, to show the user before the browser opens. */
|
|
45
|
+
issuerOrigin: string;
|
|
46
|
+
/**
|
|
47
|
+
* WS-25 fix round 1, additive: the ORIGIN of `authUrl` itself -- the page the browser will actually load.
|
|
48
|
+
* Usually the issuer's; a host shows both when they differ (metadata may place the authorize endpoint on
|
|
49
|
+
* another host).
|
|
50
|
+
*/
|
|
51
|
+
authorizeOrigin: string;
|
|
52
|
+
/** Settles once: the callback arrived and the tokens are stored, or the sign-in ended without them. */
|
|
53
|
+
done: Promise<McpOAuthLoginOutcome>;
|
|
54
|
+
/** Ends the sign-in now (closes the listener); `done` settles `{ ok: false }`. Idempotent. */
|
|
55
|
+
cancel(): void;
|
|
56
|
+
}
|
|
57
|
+
export declare function startMcpOAuthLogin(opts: StartMcpOAuthLoginOptions): Promise<McpOAuthLogin>;
|
|
58
|
+
/** Test seam: how many sign-in flows are waiting for a callback. */
|
|
59
|
+
export declare function activeMcpOAuthLoginCount(): number;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in a SESSION reads (`mcp-oauth:<id>`). `generation` counts writes: every refresh and every new
|
|
3
|
+
* sign-in bumps it, and the `mcp_oauth_refresh` request carries the generation the session last read, so a
|
|
4
|
+
* host that already refreshed past it answers without posting again.
|
|
5
|
+
*
|
|
6
|
+
* `expiresAt` is epoch MILLISECONDS, absent when the token endpoint gave no `expires_in` -- such a token is
|
|
7
|
+
* treated as valid until the server answers 401 (spec §1.2).
|
|
8
|
+
*/
|
|
9
|
+
export interface McpOAuthTokenRecord {
|
|
10
|
+
v: 1;
|
|
11
|
+
kind: "mcp-oauth";
|
|
12
|
+
serverUrl: string;
|
|
13
|
+
issuer: string;
|
|
14
|
+
accessToken: string;
|
|
15
|
+
refreshToken?: string;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
scope?: string;
|
|
18
|
+
generation: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The client registration only the HOST reads (`mcp-oauth-client:<id>`): which client Winter is to this
|
|
22
|
+
* server's authorization server, how it got that identity, and where the sign-in's callback listens.
|
|
23
|
+
*
|
|
24
|
+
* - `registeredVia` -- `"preregistered"` (the config's `oauth.clientId`), `"cimd"` (the client id IS the
|
|
25
|
+
* Client ID Metadata Document's URL) or `"dcr"` (RFC 7591 Dynamic Client Registration).
|
|
26
|
+
* - `redirectUri` -- the exact loopback redirect this client registered or last used,
|
|
27
|
+
* `http://127.0.0.1:<port>/callback`. For DCR it is the registration's own, and its port is reused on
|
|
28
|
+
* the next sign-in (spec §1.6).
|
|
29
|
+
* - `resourceMetadataUrl` / `authorizationServerUrl` -- where discovery found the RFC 9728 and RFC 8414
|
|
30
|
+
* documents, so a refresh re-reads them without a probe of the MCP server.
|
|
31
|
+
* - `stepUpScope` -- a scope a `403 insufficient_scope` asked for; the next sign-in requests it.
|
|
32
|
+
*/
|
|
33
|
+
export interface McpOAuthClientRecord {
|
|
34
|
+
v: 1;
|
|
35
|
+
kind: "mcp-oauth-client";
|
|
36
|
+
serverUrl: string;
|
|
37
|
+
issuer: string;
|
|
38
|
+
clientId: string;
|
|
39
|
+
clientSecret?: string;
|
|
40
|
+
registeredVia: "dcr" | "cimd" | "preregistered";
|
|
41
|
+
redirectUri: string;
|
|
42
|
+
resourceMetadataUrl?: string;
|
|
43
|
+
authorizationServerUrl?: string;
|
|
44
|
+
stepUpScope?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function decodeMcpOAuthTokenRecord(raw: string): McpOAuthTokenRecord;
|
|
47
|
+
export declare function decodeMcpOAuthClientRecord(raw: string): McpOAuthClientRecord;
|
|
48
|
+
/** Serialises a token record (re-validated, so an in-memory object built by hand cannot store garbage). */
|
|
49
|
+
export declare function encodeMcpOAuthTokenRecord(record: McpOAuthTokenRecord): string;
|
|
50
|
+
/** Serialises a client record (re-validated, like `encodeMcpOAuthTokenRecord`). */
|
|
51
|
+
export declare function encodeMcpOAuthClientRecord(record: McpOAuthClientRecord): string;
|
|
52
|
+
/**
|
|
53
|
+
* The item at `mcp-oauth-client-secret:<id>`. A secret is only ever sent to the authorization server it
|
|
54
|
+
* belongs to: a config source that can redeclare the server (a trusted project overriding the user's
|
|
55
|
+
* scope) could otherwise point `authServerMetadataUrl` at its own token endpoint and have a Sign in click
|
|
56
|
+
* post the user's secret there.
|
|
57
|
+
*
|
|
58
|
+
* - `issuer` present: the secret goes to that issuer only (the host set it when the user knew it --
|
|
59
|
+
* `encodeMcpOAuthClientSecretItem(secret, expectedIssuer)`, or the sign-in stamped it, below).
|
|
60
|
+
* - `issuer` absent: TRUST ON FIRST USE -- the first SUCCESSFUL pre-registered sign-in stamps the issuer it
|
|
61
|
+
* used, and every later sign-in must match it (so does an existing pre-registered client record's).
|
|
62
|
+
*
|
|
63
|
+
* A bare string (a secret a host wrote before this shape) reads as `{ secret, issuer: undefined }`.
|
|
64
|
+
*/
|
|
65
|
+
export interface McpOAuthClientSecretItem {
|
|
66
|
+
secret: string;
|
|
67
|
+
issuer?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function decodeMcpOAuthClientSecretItem(raw: string): McpOAuthClientSecretItem;
|
|
70
|
+
/** What a host writes at `mcpOAuthClientSecretAccount(serverUrl)`; `expectedIssuer` binds it up front. */
|
|
71
|
+
export declare function encodeMcpOAuthClientSecretItem(secret: string, expectedIssuer?: string): string;
|
|
72
|
+
/** Issuer equality as the MCP client compares it: exact, or differing only by one trailing slash. */
|
|
73
|
+
export declare function sameIssuer(a: string, b: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type McpOAuthStore } from "./store.js";
|
|
2
|
+
export interface RefreshMcpOAuthTokenOptions {
|
|
3
|
+
/** The token item's FULL account name, `mcp-oauth:<id>`. The client item is its prefix swap. */
|
|
4
|
+
account: string;
|
|
5
|
+
store: McpOAuthStore;
|
|
6
|
+
/** The network under the auth-HTTP policy (tests). The policy is applied on top, always. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/** The clock (tests). Epoch ms. */
|
|
9
|
+
now?: () => number;
|
|
10
|
+
/**
|
|
11
|
+
* WS-25, additive: the generation the ASKING session last read (`McpOAuthRefreshRequest.generation`).
|
|
12
|
+
* When the stored record has already moved past it, another caller refreshed first: the answer is
|
|
13
|
+
* `{ ok: true }` with the stored generation, and nothing is posted.
|
|
14
|
+
*/
|
|
15
|
+
generation?: number;
|
|
16
|
+
/**
|
|
17
|
+
* WS-25, additive: a scope a `403 insufficient_scope` asked for (`McpOAuthRefreshRequest.stepUpScope`).
|
|
18
|
+
* A refresh cannot widen a grant (RFC 6749 §6), so it is recorded on the client registration for the
|
|
19
|
+
* next sign-in and the answer is `needs_auth`.
|
|
20
|
+
*/
|
|
21
|
+
stepUpScope?: string;
|
|
22
|
+
}
|
|
23
|
+
export type RefreshMcpOAuthTokenResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
generation: number;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: "needs_auth" | "transient";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Refreshes ONE sign-in (see this file's header). Pass the SAME `store` instance on every call in a process:
|
|
32
|
+
* the single-flight is per store object.
|
|
33
|
+
*/
|
|
34
|
+
export declare function refreshMcpOAuthToken(opts: RefreshMcpOAuthTokenOptions): Promise<RefreshMcpOAuthTokenResult>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type McpOAuthStore } from "./store.js";
|
|
2
|
+
export interface RevokeMcpOAuthOptions {
|
|
3
|
+
/** The token item's FULL account name, `mcp-oauth:<id>`. */
|
|
4
|
+
account: string;
|
|
5
|
+
store: McpOAuthStore;
|
|
6
|
+
/** The network under the auth-HTTP policy (tests). The policy is applied on top, always. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/**
|
|
9
|
+
* WS-25, additive (`winter mcp logout --forget-client`): also remove the client registration. Absent, it
|
|
10
|
+
* is KEPT (spec §1.6) -- re-registering on every sign-in would be DCR spam, and a pre-registered or CIMD
|
|
11
|
+
* client has nothing to re-register.
|
|
12
|
+
*/
|
|
13
|
+
forgetClient?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function revokeMcpOAuth(opts: RevokeMcpOAuthOptions): Promise<void>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { McpOAuthRefreshAnswer, McpOAuthRefreshRequest } from "@yanlinglabs/winter-agent-sdk";
|
|
2
|
+
import { type McpOAuthStore } from "./store.js";
|
|
3
|
+
/** The host's answer, or `"unhandled"` when the host registered no `mcp_oauth_refresh` handler. */
|
|
4
|
+
export type McpOAuthHostAsk = (request: McpOAuthRefreshRequest) => Promise<McpOAuthRefreshAnswer | "unhandled">;
|
|
5
|
+
/** What a session hands every remote server's connection. */
|
|
6
|
+
export interface McpSessionOAuth {
|
|
7
|
+
/** The Keychain (the session's own service), read-only unless the in-process fallback refreshes. */
|
|
8
|
+
store: McpOAuthStore;
|
|
9
|
+
/** Asks the host to refresh. Absent: always in-process. */
|
|
10
|
+
askHost?: McpOAuthHostAsk;
|
|
11
|
+
/**
|
|
12
|
+
* WS-25 §7: the host OWNS renewal (a host-brokered session: `store` is read-only, and no refresh token
|
|
13
|
+
* is in this process). A host answer of "unhandled" is then `transient`, never the in-process refresh.
|
|
14
|
+
*/
|
|
15
|
+
hostOwnsRefresh?: boolean;
|
|
16
|
+
/** The sign-in door, for a server name -- the text a `needs-auth` error names (e.g. `winter mcp login linear`). */
|
|
17
|
+
signInHint: (serverName: string) => string;
|
|
18
|
+
/** The clock (tests). Epoch ms. */
|
|
19
|
+
now?: () => number;
|
|
20
|
+
/** The network the in-process fallback refreshes over (tests). */
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
/** The server needs a (new) sign-in. Classified `needs_auth` wherever it surfaces. */
|
|
24
|
+
export declare class McpNeedsAuthError extends Error {
|
|
25
|
+
readonly serverName: string;
|
|
26
|
+
constructor(serverName: string, why: string, hint: string);
|
|
27
|
+
}
|
|
28
|
+
/** A refresh that could not complete right now (the network, the authorization server's 5xx). */
|
|
29
|
+
export declare class McpAuthRefreshUnavailableError extends Error {
|
|
30
|
+
constructor(serverName: string);
|
|
31
|
+
}
|
|
32
|
+
export interface McpSessionAuthProvider {
|
|
33
|
+
/** The MCP client's `AuthProvider.token()`: the bearer for the next request, or none. */
|
|
34
|
+
token(): Promise<string | undefined>;
|
|
35
|
+
/** The MCP client's `AuthProvider.onUnauthorized()`: make the next `token()` valid, or throw. */
|
|
36
|
+
onUnauthorized(): Promise<void>;
|
|
37
|
+
/** Before the transport exists: throws `McpNeedsAuthError` when the stored sign-in is already dead (spec §1.2). */
|
|
38
|
+
preflight(): Promise<void>;
|
|
39
|
+
/** A `403 insufficient_scope`: the host records the scope for the next sign-in (the answer is not awaited for anything). */
|
|
40
|
+
reportInsufficientScope(scope: string | undefined): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createSessionAuthProvider(opts: {
|
|
43
|
+
serverName: string;
|
|
44
|
+
serverUrl: string;
|
|
45
|
+
oauth: McpSessionOAuth;
|
|
46
|
+
}): McpSessionAuthProvider;
|
|
47
|
+
/**
|
|
48
|
+
* The sign-in door a `needs-auth` error names, for a brand: `<cli> mcp login <server>` and the app's MCP
|
|
49
|
+
* settings. The CLI name is the home directory's token (the dot-dir without its dot), the one brand field that
|
|
50
|
+
* IS the command a user types.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mcpSignInHint(brand: {
|
|
53
|
+
homeDirName: string;
|
|
54
|
+
productName: string;
|
|
55
|
+
}, serverName: string): string;
|