@yanlinglabs/winter-agent-runtime 0.0.29 → 0.0.31

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.
Files changed (42) hide show
  1. package/dist/embedded-worker.js +6 -4
  2. package/dist/embedded.js +6 -4
  3. package/dist/engine.d.ts +16 -0
  4. package/dist/{index-gmxxtqpe.js → index-0xqzk1hm.js} +30 -12
  5. package/dist/{index-8jgwp1px.js → index-9s68t0pd.js} +170 -11
  6. package/dist/index-bgeqc7ax.js +52 -0
  7. package/dist/{index-eyddzcgs.js → index-c7kq6erg.js} +2 -2
  8. package/dist/{index-jz5d90c6.js → index-d4pmmr27.js} +191 -128
  9. package/dist/index-z6s3r4xm.js +903 -0
  10. package/dist/index.js +4 -2
  11. package/dist/mcp/client.d.ts +20 -1
  12. package/dist/mcp/lifecycle.d.ts +9 -0
  13. package/dist/mcp/transports/http.d.ts +10 -1
  14. package/dist/mcp/transports/sse.d.ts +4 -2
  15. package/dist/mcp-auth/account.d.ts +33 -0
  16. package/dist/mcp-auth/config.d.ts +6 -0
  17. package/dist/mcp-auth/constants.d.ts +30 -0
  18. package/dist/mcp-auth/discovery.d.ts +72 -0
  19. package/dist/mcp-auth/engine-wiring.d.ts +52 -0
  20. package/dist/mcp-auth/errors.d.ts +37 -0
  21. package/dist/mcp-auth/fetch-policy.d.ts +33 -0
  22. package/dist/mcp-auth/login.d.ts +59 -0
  23. package/dist/mcp-auth/records.d.ts +73 -0
  24. package/dist/mcp-auth/refresh.d.ts +34 -0
  25. package/dist/mcp-auth/revoke.d.ts +15 -0
  26. package/dist/mcp-auth/session-provider.d.ts +55 -0
  27. package/dist/mcp-auth/store.d.ts +85 -0
  28. package/dist/mcp-auth/test-fixture-as.d.ts +72 -0
  29. package/dist/mcp-auth.d.ts +14 -0
  30. package/dist/mcp-auth.js +493 -0
  31. package/dist/mcp-client.d.ts +12 -2
  32. package/dist/mcp-client.js +15 -2
  33. package/dist/production-wiring.d.ts +6 -0
  34. package/dist/provider/host-credentials.d.ts +48 -0
  35. package/dist/provider/keychain-store.d.ts +29 -1
  36. package/dist/provider/session-provider.d.ts +1 -1
  37. package/dist/rpc/bridge.d.ts +5 -1
  38. package/dist/store/dialect.d.ts +1 -1
  39. package/dist/testing.js +5 -3
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +8 -4
@@ -0,0 +1,903 @@
1
+ // src/mcp-auth/errors.ts
2
+ import { OAuthError } from "@modelcontextprotocol/client";
3
+
4
+ class McpOAuthError2 extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "McpOAuthError";
9
+ this.code = code;
10
+ }
11
+ }
12
+ function boundedReason(err) {
13
+ if (err instanceof McpOAuthError2)
14
+ return err.message.slice(0, 300);
15
+ if (err instanceof OAuthError)
16
+ return `oauth_error:${err.code}`;
17
+ if (err instanceof Error)
18
+ return `${err.name}: ${err.message.replace(/[\u0000-\u001f\u007f]+/g, " ").slice(0, 200)}`;
19
+ return "failed";
20
+ }
21
+
22
+ // src/mcp-auth/account.ts
23
+ import { createHash } from "node:crypto";
24
+ var MCP_OAUTH_TOKEN_ACCOUNT_PREFIX2 = "mcp-oauth:";
25
+ var MCP_OAUTH_CLIENT_ACCOUNT_PREFIX2 = "mcp-oauth-client:";
26
+ function canonicalMcpServerUrl2(serverUrl) {
27
+ let url;
28
+ try {
29
+ url = new URL(serverUrl);
30
+ } catch {
31
+ throw new McpOAuthError2("invalid_server_url", "an MCP server URL must be an absolute http(s) URL");
32
+ }
33
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
34
+ throw new McpOAuthError2("invalid_server_url", `an MCP server URL must be http(s), not "${url.protocol}"`);
35
+ }
36
+ if (url.username !== "" || url.password !== "") {
37
+ throw new McpOAuthError2("invalid_server_url", `the MCP server URL for ${url.origin} carries userinfo -- a credential never rides a server URL`);
38
+ }
39
+ return `${url.protocol}//${url.host}${url.pathname.replace(/\/+$/, "")}`;
40
+ }
41
+ function mcpOAuthAccountId2(serverUrl) {
42
+ return createHash("sha256").update(canonicalMcpServerUrl2(serverUrl)).digest("hex").slice(0, 16);
43
+ }
44
+ function mcpOAuthTokenAccount2(serverUrl) {
45
+ return `${MCP_OAUTH_TOKEN_ACCOUNT_PREFIX2}${mcpOAuthAccountId2(serverUrl)}`;
46
+ }
47
+ function mcpOAuthClientAccount2(serverUrl) {
48
+ return `${MCP_OAUTH_CLIENT_ACCOUNT_PREFIX2}${mcpOAuthAccountId2(serverUrl)}`;
49
+ }
50
+ var MCP_OAUTH_CLIENT_SECRET_ACCOUNT_PREFIX = "mcp-oauth-client-secret:";
51
+ function mcpOAuthClientSecretAccount2(serverUrl) {
52
+ return `${MCP_OAUTH_CLIENT_SECRET_ACCOUNT_PREFIX}${mcpOAuthAccountId2(serverUrl)}`;
53
+ }
54
+ var ACCOUNT_SHAPE = /^mcp-oauth:[0-9a-f]{16}$/;
55
+ function isMcpOAuthTokenAccount2(account) {
56
+ return ACCOUNT_SHAPE.test(account);
57
+ }
58
+ function clientAccountForTokenAccount2(account) {
59
+ if (!isMcpOAuthTokenAccount2(account)) {
60
+ throw new McpOAuthError2("invalid_account", `"${account}" is not an MCP sign-in account (expected mcp-oauth:<16 hex>)`);
61
+ }
62
+ return `${MCP_OAUTH_CLIENT_ACCOUNT_PREFIX2}${account.slice(MCP_OAUTH_TOKEN_ACCOUNT_PREFIX2.length)}`;
63
+ }
64
+
65
+ // src/mcp-auth/constants.ts
66
+ var WINTER_MCP_CLIENT_METADATA_URL2 = "https://yanlinglabs.com/winter/oauth-client.json";
67
+ var MCP_OAUTH_CALLBACK_PATH2 = "/callback";
68
+ var MCP_OAUTH_EXPIRY_SKEW_MS2 = 60000;
69
+ var MCP_OAUTH_SESSION_CACHE_MS2 = 30000;
70
+ var MCP_OAUTH_LOGIN_TIMEOUT_MS2 = 5 * 60000;
71
+ var MCP_OAUTH_STATE_BYTES = 32;
72
+
73
+ // src/mcp-auth/fetch-policy.ts
74
+ import { boundedFetch, classifyAddress, DEFAULT_MAX_REDIRECTS, ProviderBodyLimitError, ProviderRequestError } from "@yanlinglabs/winter-provider-runtime";
75
+ import { isIP } from "node:net";
76
+ var MCP_AUTH_FETCH_TIMEOUT_MS = 30000;
77
+ var MCP_AUTH_FETCH_MAX_BODY_BYTES = 1024 * 1024;
78
+ function stripBrackets(host) {
79
+ return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
80
+ }
81
+ function evaluateMcpAuthUrl2(raw, opts = {}) {
82
+ let url;
83
+ try {
84
+ url = new URL(String(raw));
85
+ } catch {
86
+ return { ok: false, reason: "an auth URL is not a parseable absolute URL" };
87
+ }
88
+ if (url.protocol !== "https:" && url.protocol !== "http:")
89
+ return { ok: false, reason: `${url.protocol} is not an auth URL scheme (https only)` };
90
+ if (url.username !== "" || url.password !== "")
91
+ return { ok: false, reason: `the auth URL for ${url.origin} carries userinfo` };
92
+ const host = stripBrackets(url.hostname);
93
+ const family = isIP(host);
94
+ const cls = family !== 0 ? classifyAddress(host, family) : host.toLowerCase() === "localhost" ? "loopback" : undefined;
95
+ if (cls !== undefined && cls !== "loopback" && cls !== "public") {
96
+ return { ok: false, reason: `${url.origin} is a literal ${cls} address, which an auth request never reaches` };
97
+ }
98
+ const loopback = cls === "loopback";
99
+ if (loopback && opts.allowLoopback !== true)
100
+ return { ok: false, reason: `${url.origin} is a loopback address, which an auth request reaches only for an MCP server that is itself on loopback` };
101
+ if (url.protocol === "http:" && !loopback)
102
+ return { ok: false, reason: `${url.origin} uses plain http; auth requests are https-only except to a literal loopback address` };
103
+ return { ok: true, origin: url.origin, loopback };
104
+ }
105
+ function isLoopbackMcpServer(serverUrl) {
106
+ const verdict = evaluateMcpAuthUrl2(serverUrl, { allowLoopback: true });
107
+ return verdict.ok && verdict.loopback;
108
+ }
109
+ function refusal(reason) {
110
+ return new McpOAuthError2("policy_refused", `auth request refused: ${reason}`);
111
+ }
112
+ function sameOriginOnlyPolicy(origin, loopback, allowLoopback) {
113
+ return {
114
+ origin,
115
+ local: loopback,
116
+ generated: false,
117
+ evaluateRedirect(target) {
118
+ const verdict = evaluateMcpAuthUrl2(target, { allowLoopback });
119
+ if (!verdict.ok)
120
+ return { ok: false, reason: verdict.reason };
121
+ if (verdict.origin !== origin)
122
+ return { ok: false, reason: `a redirect from ${origin} to ${verdict.origin} (auth requests never follow a cross-origin redirect)` };
123
+ return { ok: true, origin: verdict.origin, sameOrigin: true };
124
+ }
125
+ };
126
+ }
127
+ function headersOf(init) {
128
+ return new Headers(init?.headers ?? {});
129
+ }
130
+ function capResponse(response, maxBodyBytes) {
131
+ if (response.body === null)
132
+ return response;
133
+ const reader = response.body.getReader();
134
+ let seen = 0;
135
+ const body = new ReadableStream({
136
+ async pull(controller) {
137
+ const { done, value } = await reader.read();
138
+ if (done) {
139
+ controller.close();
140
+ return;
141
+ }
142
+ seen += value.byteLength;
143
+ if (seen > maxBodyBytes) {
144
+ reader.cancel().catch(() => {});
145
+ controller.error(new ProviderBodyLimitError(maxBodyBytes));
146
+ return;
147
+ }
148
+ controller.enqueue(value);
149
+ },
150
+ cancel(reason) {
151
+ reader.cancel(reason).catch(() => {});
152
+ }
153
+ });
154
+ return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
155
+ }
156
+ function createMcpAuthFetch(opts = {}) {
157
+ const timeoutMs = opts.timeoutMs ?? MCP_AUTH_FETCH_TIMEOUT_MS;
158
+ const maxBodyBytes = opts.maxBodyBytes ?? MCP_AUTH_FETCH_MAX_BODY_BYTES;
159
+ const allowLoopback = opts.allowLoopback === true;
160
+ return async (input, init) => {
161
+ const target = String(input);
162
+ const verdict = evaluateMcpAuthUrl2(target, { allowLoopback });
163
+ if (!verdict.ok)
164
+ throw refusal(verdict.reason);
165
+ const { redirect: _redirect, signal, ...rest } = init ?? {};
166
+ const deadline = new AbortController;
167
+ const timer = setTimeout(() => deadline.abort(), timeoutMs);
168
+ timer.unref?.();
169
+ const combined = signal !== undefined && signal !== null ? AbortSignal.any([deadline.signal, signal]) : deadline.signal;
170
+ if (opts.fetch !== undefined) {
171
+ try {
172
+ const response = await opts.fetch(target, { ...rest, headers: headersOf(init), redirect: "error", signal: combined });
173
+ return capResponse(response, maxBodyBytes);
174
+ } catch (err) {
175
+ clearTimeout(timer);
176
+ if (err instanceof McpOAuthError2)
177
+ throw err;
178
+ throw new McpOAuthError2("network", `the auth request to ${verdict.origin} failed (${err instanceof Error ? err.name : "error"}); redirects are refused on an injected network`);
179
+ }
180
+ }
181
+ try {
182
+ return await boundedFetch(target, {
183
+ ...rest,
184
+ headers: headersOf(init),
185
+ timeoutMs,
186
+ maxBodyBytes,
187
+ maxRedirects: DEFAULT_MAX_REDIRECTS,
188
+ policy: sameOriginOnlyPolicy(verdict.origin, verdict.loopback, allowLoopback),
189
+ signal: combined
190
+ });
191
+ } catch (err) {
192
+ clearTimeout(timer);
193
+ if (err instanceof ProviderRequestError && err.code === "capability")
194
+ throw refusal(err.message);
195
+ throw new McpOAuthError2("network", `the auth request to ${verdict.origin} failed: ${err instanceof Error ? err.message : String(err)}`);
196
+ }
197
+ };
198
+ }
199
+
200
+ // src/mcp-auth/records.ts
201
+ var REGISTERED_VIA = new Set(["dcr", "cimd", "preregistered"]);
202
+ function parseObject(raw, what) {
203
+ let parsed;
204
+ try {
205
+ parsed = JSON.parse(raw);
206
+ } catch {
207
+ throw new McpOAuthError2("malformed_record", `the ${what} record is not valid JSON`);
208
+ }
209
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
210
+ throw new McpOAuthError2("malformed_record", `the ${what} record is not a JSON object`);
211
+ }
212
+ return parsed;
213
+ }
214
+ function checkEnvelope(o, kind, what) {
215
+ if (o.v !== 1) {
216
+ throw new McpOAuthError2(typeof o.v === "number" ? "unsupported_record_version" : "malformed_record", `the ${what} record has ${typeof o.v === "number" ? `version ${o.v}, which this Winter does not read` : "no version"}`);
217
+ }
218
+ if (o.kind !== kind)
219
+ throw new McpOAuthError2("malformed_record", `the ${what} record is not of kind "${kind}"`);
220
+ }
221
+ function requireString(o, key, what) {
222
+ const value = o[key];
223
+ if (typeof value !== "string" || value.length === 0)
224
+ throw new McpOAuthError2("malformed_record", `the ${what} record has no "${key}"`);
225
+ return value;
226
+ }
227
+ function optionalString(o, key, what) {
228
+ const value = o[key];
229
+ if (value === undefined)
230
+ return;
231
+ if (typeof value !== "string")
232
+ throw new McpOAuthError2("malformed_record", `the ${what} record's "${key}" is not a string`);
233
+ return value;
234
+ }
235
+ function decodeMcpOAuthTokenRecord2(raw) {
236
+ const what = "MCP sign-in";
237
+ const o = parseObject(raw, what);
238
+ checkEnvelope(o, "mcp-oauth", what);
239
+ const generation = o.generation;
240
+ if (typeof generation !== "number" || !Number.isInteger(generation) || generation < 0) {
241
+ throw new McpOAuthError2("malformed_record", `the ${what} record's "generation" is not a non-negative integer`);
242
+ }
243
+ const expiresAt = o.expiresAt;
244
+ if (expiresAt !== undefined && (typeof expiresAt !== "number" || !Number.isFinite(expiresAt))) {
245
+ throw new McpOAuthError2("malformed_record", `the ${what} record's "expiresAt" is not a number`);
246
+ }
247
+ const refreshToken = optionalString(o, "refreshToken", what);
248
+ const scope = optionalString(o, "scope", what);
249
+ return {
250
+ v: 1,
251
+ kind: "mcp-oauth",
252
+ serverUrl: requireString(o, "serverUrl", what),
253
+ issuer: requireString(o, "issuer", what),
254
+ accessToken: requireString(o, "accessToken", what),
255
+ ...refreshToken !== undefined && refreshToken !== "" ? { refreshToken } : {},
256
+ ...expiresAt !== undefined ? { expiresAt } : {},
257
+ ...scope !== undefined ? { scope } : {},
258
+ generation
259
+ };
260
+ }
261
+ function decodeMcpOAuthClientRecord2(raw) {
262
+ const what = "MCP client registration";
263
+ const o = parseObject(raw, what);
264
+ checkEnvelope(o, "mcp-oauth-client", what);
265
+ const registeredVia = o.registeredVia;
266
+ if (typeof registeredVia !== "string" || !REGISTERED_VIA.has(registeredVia)) {
267
+ throw new McpOAuthError2("malformed_record", `the ${what} record's "registeredVia" is not dcr, cimd or preregistered`);
268
+ }
269
+ const clientSecret = optionalString(o, "clientSecret", what);
270
+ const resourceMetadataUrl = optionalString(o, "resourceMetadataUrl", what);
271
+ const authorizationServerUrl = optionalString(o, "authorizationServerUrl", what);
272
+ const stepUpScope = optionalString(o, "stepUpScope", what);
273
+ return {
274
+ v: 1,
275
+ kind: "mcp-oauth-client",
276
+ serverUrl: requireString(o, "serverUrl", what),
277
+ issuer: requireString(o, "issuer", what),
278
+ clientId: requireString(o, "clientId", what),
279
+ ...clientSecret !== undefined && clientSecret !== "" ? { clientSecret } : {},
280
+ registeredVia,
281
+ redirectUri: requireString(o, "redirectUri", what),
282
+ ...resourceMetadataUrl !== undefined ? { resourceMetadataUrl } : {},
283
+ ...authorizationServerUrl !== undefined ? { authorizationServerUrl } : {},
284
+ ...stepUpScope !== undefined && stepUpScope !== "" ? { stepUpScope } : {}
285
+ };
286
+ }
287
+ function encodeMcpOAuthTokenRecord2(record) {
288
+ const text = JSON.stringify(record);
289
+ decodeMcpOAuthTokenRecord2(text);
290
+ return text;
291
+ }
292
+ function encodeMcpOAuthClientRecord2(record) {
293
+ const text = JSON.stringify(record);
294
+ decodeMcpOAuthClientRecord2(text);
295
+ return text;
296
+ }
297
+ function decodeMcpOAuthClientSecretItem2(raw) {
298
+ if (raw.startsWith("{")) {
299
+ let parsed;
300
+ try {
301
+ parsed = JSON.parse(raw);
302
+ } catch {
303
+ parsed = undefined;
304
+ }
305
+ if (typeof parsed === "object" && parsed !== null && parsed.kind === "mcp-oauth-client-secret") {
306
+ const o = parsed;
307
+ if (o.v !== 1)
308
+ throw new McpOAuthError2(typeof o.v === "number" ? "unsupported_record_version" : "malformed_record", "the MCP client secret record has an unreadable version");
309
+ if (typeof o.secret !== "string" || o.secret === "")
310
+ throw new McpOAuthError2("malformed_record", 'the MCP client secret record has no "secret"');
311
+ if (o.issuer !== undefined && (typeof o.issuer !== "string" || o.issuer === ""))
312
+ throw new McpOAuthError2("malformed_record", `the MCP client secret record's "issuer" is not a string`);
313
+ return { secret: o.secret, ...typeof o.issuer === "string" ? { issuer: o.issuer } : {} };
314
+ }
315
+ }
316
+ return { secret: raw };
317
+ }
318
+ function encodeMcpOAuthClientSecretItem2(secret, expectedIssuer) {
319
+ if (secret === "")
320
+ throw new McpOAuthError2("malformed_record", "an MCP client secret cannot be empty");
321
+ return JSON.stringify({ v: 1, kind: "mcp-oauth-client-secret", secret, ...expectedIssuer !== undefined ? { issuer: expectedIssuer } : {} });
322
+ }
323
+ function sameIssuer2(a, b) {
324
+ return a === b || a === `${b}/` || `${a}/` === b;
325
+ }
326
+
327
+ // src/mcp-auth/discovery.ts
328
+ import { buildDiscoveryUrls, checkResourceAllowed, discoverAuthorizationServerMetadata, discoverOAuthProtectedResourceMetadata, discoverOAuthServerInfo, resourceUrlFromServerUrl } from "@modelcontextprotocol/client";
329
+ function isMetadataDocumentUrl(url) {
330
+ try {
331
+ return new URL(url).pathname.includes("/.well-known/");
332
+ } catch {
333
+ return false;
334
+ }
335
+ }
336
+ function fetchedFromIssuersOwnLocation(fetchUrl, issuer) {
337
+ let fetched;
338
+ try {
339
+ fetched = new URL(fetchUrl);
340
+ new URL(issuer);
341
+ } catch {
342
+ return false;
343
+ }
344
+ if (fetched.search !== "" || fetched.hash !== "")
345
+ return false;
346
+ return buildDiscoveryUrls(issuer).some((candidate) => candidate.url.href === fetched.href);
347
+ }
348
+ async function fetchAuthorizationServerMetadataDocument(url, fetchFn) {
349
+ const response = await fetchFn(url, { headers: { accept: "application/json" } });
350
+ if (!response.ok) {
351
+ await response.text().catch(() => {});
352
+ throw new Error(`the authorization server metadata document answered HTTP ${response.status}`);
353
+ }
354
+ const doc = await response.json();
355
+ for (const key of ["issuer", "authorization_endpoint", "token_endpoint"]) {
356
+ if (typeof doc[key] !== "string")
357
+ throw new Error(`the authorization server metadata document has no "${key}"`);
358
+ }
359
+ if (!Array.isArray(doc.response_types_supported))
360
+ throw new Error('the authorization server metadata document has no "response_types_supported"');
361
+ if (!fetchedFromIssuersOwnLocation(url, doc.issuer)) {
362
+ throw new McpOAuthError2("metadata_issuer_mismatch", `the authorization server metadata document at ${new URL(url).origin} names an issuer it was not published under (RFC 8414 §3.3); it is refused`);
363
+ }
364
+ return doc;
365
+ }
366
+ async function loadAuthorizationServer(opts) {
367
+ const { authorizationServerUrl, expectedIssuer, fetchFn } = opts;
368
+ const metadata = isMetadataDocumentUrl(authorizationServerUrl) ? await fetchAuthorizationServerMetadataDocument(authorizationServerUrl, fetchFn) : await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn });
369
+ if (metadata !== undefined && !sameIssuer2(metadata.issuer, expectedIssuer)) {
370
+ throw new Error("the authorization server now names a different issuer than the one this sign-in came from");
371
+ }
372
+ const base = isMetadataDocumentUrl(authorizationServerUrl) && metadata !== undefined ? metadata.issuer : authorizationServerUrl;
373
+ return { authorizationServerUrl: base, ...metadata !== undefined ? { metadata } : {} };
374
+ }
375
+ async function resolveResourceIndicator(opts) {
376
+ let resource;
377
+ try {
378
+ const prm = await discoverOAuthProtectedResourceMetadata(opts.serverUrl, opts.resourceMetadataUrl !== undefined ? { resourceMetadataUrl: new URL(opts.resourceMetadataUrl) } : undefined, opts.fetchFn);
379
+ resource = prm.resource;
380
+ } catch {
381
+ return;
382
+ }
383
+ if (!checkResourceAllowed({ requestedResource: resourceUrlFromServerUrl(opts.serverUrl), configuredResource: resource })) {
384
+ throw new Error("the protected-resource metadata names a resource that is not this MCP server");
385
+ }
386
+ return resource;
387
+ }
388
+ async function discoverMcpOAuthIssuer2(opts) {
389
+ const serverVerdict = evaluateMcpAuthUrl2(opts.serverUrl, { allowLoopback: true });
390
+ if (!serverVerdict.ok)
391
+ throw new McpOAuthError2("policy_refused", `the MCP server URL is refused: ${serverVerdict.reason}`);
392
+ const oauth = opts.oauth ?? {};
393
+ const allowLoopback = isLoopbackMcpServer(opts.serverUrl);
394
+ const fetchFn = createMcpAuthFetch({ ...opts.fetch !== undefined ? { fetch: opts.fetch } : {}, allowLoopback });
395
+ let issuer;
396
+ let authorizeEndpoint;
397
+ try {
398
+ if (oauth.authServerMetadataUrl !== undefined) {
399
+ const metadata = await fetchAuthorizationServerMetadataDocument(oauth.authServerMetadataUrl, fetchFn);
400
+ issuer = metadata.issuer;
401
+ authorizeEndpoint = metadata.authorization_endpoint;
402
+ } else {
403
+ const info = await discoverOAuthServerInfo(opts.serverUrl, { fetchFn });
404
+ issuer = info.authorizationServerMetadata?.issuer ?? info.authorizationServerUrl;
405
+ authorizeEndpoint = info.authorizationServerMetadata?.authorization_endpoint ?? new URL("/authorize", info.authorizationServerUrl).toString();
406
+ }
407
+ } catch (err) {
408
+ if (err instanceof McpOAuthError2)
409
+ throw err;
410
+ throw new McpOAuthError2("login_failed", `mcp-auth issuer discovery failed: ${boundedReason(err)}`);
411
+ }
412
+ const authorizeVerdict = evaluateMcpAuthUrl2(authorizeEndpoint, { allowLoopback });
413
+ if (!authorizeVerdict.ok)
414
+ throw new McpOAuthError2("policy_refused", `the authorization endpoint is refused: ${authorizeVerdict.reason}`);
415
+ return { issuer, issuerOrigin: new URL(issuer).origin, authorizeOrigin: authorizeVerdict.origin };
416
+ }
417
+
418
+ // src/mcp-auth/store.ts
419
+ import { readFileSync, renameSync, writeFileSync } from "node:fs";
420
+ import { DEFAULT_KEYCHAIN_SERVICE as DEFAULT_KEYCHAIN_SERVICE2, envName, WINTER_BRAND } from "@yanlinglabs/winter-agent-sdk";
421
+
422
+ // src/provider/keychain-store.ts
423
+ import { CredentialResolutionError, redactRef } from "@yanlinglabs/winter-provider-runtime";
424
+ import { DEFAULT_KEYCHAIN_SERVICE } from "@yanlinglabs/winter-agent-sdk";
425
+ function defaultSecretsBackend() {
426
+ const secrets = globalThis.Bun?.secrets;
427
+ if (secrets === undefined) {
428
+ throw new CredentialResolutionError("unsupported", "the Keychain credential store requires Bun.secrets, which is unavailable in this runtime");
429
+ }
430
+ return secrets;
431
+ }
432
+ function createKeychainCredentialStore(service = DEFAULT_KEYCHAIN_SERVICE, opts = {}) {
433
+ const backend = () => opts.secrets ?? defaultSecretsBackend();
434
+ return {
435
+ async get(ref) {
436
+ if (ref.kind !== "keychain")
437
+ return null;
438
+ let raw;
439
+ try {
440
+ raw = await backend().get({ service: ref.service ?? service, name: ref.account });
441
+ } catch (err) {
442
+ if (err instanceof CredentialResolutionError)
443
+ throw err;
444
+ throw new CredentialResolutionError("io", `keychain lookup failed for ${redactRef(ref)}`);
445
+ }
446
+ if (raw === null)
447
+ return null;
448
+ let parsed;
449
+ try {
450
+ parsed = JSON.parse(raw);
451
+ } catch {
452
+ throw new CredentialResolutionError("malformed", `the keychain record for ${redactRef(ref)} is not valid JSON credential material`);
453
+ }
454
+ const material = coerceMaterial(parsed);
455
+ if (material === undefined)
456
+ throw new CredentialResolutionError("malformed", `the keychain record for ${redactRef(ref)} is not a recognized credential material shape`);
457
+ return material;
458
+ },
459
+ async set(ref, material) {
460
+ try {
461
+ await backend().set({ service: ref.service ?? service, name: ref.account, value: JSON.stringify(material) });
462
+ } catch (err) {
463
+ if (err instanceof CredentialResolutionError)
464
+ throw err;
465
+ throw new CredentialResolutionError("io", `keychain write failed for ${redactRef(ref)}`);
466
+ }
467
+ },
468
+ async delete(ref) {
469
+ try {
470
+ await backend().delete({ service: ref.service ?? service, name: ref.account });
471
+ } catch (err) {
472
+ if (err instanceof CredentialResolutionError)
473
+ throw err;
474
+ throw new CredentialResolutionError("io", `keychain delete failed for ${redactRef(ref)}`);
475
+ }
476
+ }
477
+ };
478
+ }
479
+ function createKeychainSecretReader(service = DEFAULT_KEYCHAIN_SERVICE, opts = {}) {
480
+ const backend = () => opts.secrets ?? defaultSecretsBackend();
481
+ return async (ref) => {
482
+ try {
483
+ return await backend().get({ service: ref.service ?? service, name: ref.account });
484
+ } catch (err) {
485
+ if (err instanceof CredentialResolutionError)
486
+ throw err;
487
+ throw new CredentialResolutionError("io", `keychain lookup failed for ${redactRef(ref)}`);
488
+ }
489
+ };
490
+ }
491
+ function createKeychainRawStore(service = DEFAULT_KEYCHAIN_SERVICE, opts = {}) {
492
+ const backend = () => opts.secrets ?? defaultSecretsBackend();
493
+ const failure = (what, account) => new CredentialResolutionError("io", `keychain ${what} failed for account ${JSON.stringify(account)}`);
494
+ return {
495
+ async read(account) {
496
+ try {
497
+ return await backend().get({ service, name: account });
498
+ } catch (err) {
499
+ if (err instanceof CredentialResolutionError)
500
+ throw err;
501
+ throw failure("lookup", account);
502
+ }
503
+ },
504
+ async write(account, value) {
505
+ try {
506
+ await backend().set({ service, name: account, value });
507
+ } catch (err) {
508
+ if (err instanceof CredentialResolutionError)
509
+ throw err;
510
+ throw failure("write", account);
511
+ }
512
+ },
513
+ async remove(account) {
514
+ try {
515
+ await backend().delete({ service, name: account });
516
+ } catch (err) {
517
+ if (err instanceof CredentialResolutionError)
518
+ throw err;
519
+ throw failure("delete", account);
520
+ }
521
+ }
522
+ };
523
+ }
524
+ function keychainAccountName(providerId, accountId) {
525
+ return `${providerId}:${accountId}`;
526
+ }
527
+ function parseStoredCredentialMaterial(raw, ref) {
528
+ let parsed;
529
+ try {
530
+ parsed = JSON.parse(raw);
531
+ } catch {
532
+ throw new CredentialResolutionError("malformed", `the credential record for ${redactRef(ref)} is not valid JSON credential material`);
533
+ }
534
+ const material = coerceMaterial(parsed);
535
+ if (material === undefined)
536
+ throw new CredentialResolutionError("malformed", `the credential record for ${redactRef(ref)} is not a recognized credential material shape`);
537
+ return material;
538
+ }
539
+ var MATERIAL_KINDS = new Set(["api-key", "bearer", "oauth", "aws", "gcp-service-account", "gcp-access-token"]);
540
+ function coerceMaterial(value) {
541
+ if (typeof value !== "object" || value === null)
542
+ return;
543
+ const v = value;
544
+ if (typeof v.kind !== "string" || !MATERIAL_KINDS.has(v.kind))
545
+ return;
546
+ switch (v.kind) {
547
+ case "api-key":
548
+ return typeof v.key === "string" ? { kind: "api-key", key: v.key } : undefined;
549
+ case "bearer":
550
+ return typeof v.token === "string" ? { kind: "bearer", token: v.token, ...typeof v.expiresAt === "number" ? { expiresAt: v.expiresAt } : {} } : undefined;
551
+ case "oauth":
552
+ return typeof v.accessToken === "string" ? {
553
+ kind: "oauth",
554
+ accessToken: v.accessToken,
555
+ ...typeof v.refreshToken === "string" ? { refreshToken: v.refreshToken } : {},
556
+ ...typeof v.expiresAt === "number" ? { expiresAt: v.expiresAt } : {},
557
+ ...typeof v.accountId === "string" ? { accountId: v.accountId } : {},
558
+ ...typeof v.idToken === "string" ? { idToken: v.idToken } : {}
559
+ } : undefined;
560
+ case "aws":
561
+ return typeof v.accessKeyId === "string" && typeof v.secretAccessKey === "string" ? { kind: "aws", accessKeyId: v.accessKeyId, secretAccessKey: v.secretAccessKey, ...typeof v.sessionToken === "string" ? { sessionToken: v.sessionToken } : {} } : undefined;
562
+ case "gcp-service-account":
563
+ return typeof v.clientEmail === "string" && typeof v.privateKeyPem === "string" && typeof v.tokenUri === "string" ? { kind: "gcp-service-account", clientEmail: v.clientEmail, privateKeyPem: v.privateKeyPem, tokenUri: v.tokenUri } : undefined;
564
+ case "gcp-access-token":
565
+ return typeof v.token === "string" ? { kind: "gcp-access-token", token: v.token } : undefined;
566
+ default:
567
+ return;
568
+ }
569
+ }
570
+
571
+ // src/provider/host-credentials.ts
572
+ import { CREDENTIAL_RESOLVE_SUBTYPE } from "@yanlinglabs/winter-agent-sdk";
573
+ import { CredentialResolutionError as CredentialResolutionError2, redactRef as redactRef2 } from "@yanlinglabs/winter-provider-runtime";
574
+ var HOST_CREDENTIAL_TIMEOUT_MS = 60000;
575
+ var HOST_CREDENTIAL_BIND_TIMEOUT_MS = 30000;
576
+ function createHostCredentialChannel(opts = {}) {
577
+ let sender;
578
+ const waiters = [];
579
+ const bound = () => {
580
+ if (sender !== undefined)
581
+ return Promise.resolve(sender);
582
+ return new Promise((resolve, reject) => {
583
+ const timer = setTimeout(() => reject(new CredentialResolutionError2("io", "the host credential channel was never bound to the session's control bridge")), opts.bindTimeoutMs ?? HOST_CREDENTIAL_BIND_TIMEOUT_MS);
584
+ timer.unref?.();
585
+ waiters.push((s) => {
586
+ clearTimeout(timer);
587
+ resolve(s);
588
+ });
589
+ });
590
+ };
591
+ return {
592
+ bind(next) {
593
+ sender = next;
594
+ for (const wake of waiters.splice(0))
595
+ wake(next);
596
+ },
597
+ async request(subtype, payload, requestOpts) {
598
+ return (await bound()).request(subtype, payload, requestOpts);
599
+ }
600
+ };
601
+ }
602
+ function isAnswer(value) {
603
+ if (typeof value !== "object" || value === null)
604
+ return false;
605
+ const v = value;
606
+ if (v.ok === true)
607
+ return typeof v.material === "string" && typeof v.generation === "number";
608
+ return v.ok === false && typeof v.reason === "string";
609
+ }
610
+ async function resolveFromHost(sender, ref, minGeneration) {
611
+ const request = { ref: { kind: "keychain", account: ref.account, ...ref.service !== undefined ? { service: ref.service } : {} }, ...minGeneration !== undefined ? { minGeneration } : {} };
612
+ let answer;
613
+ try {
614
+ answer = await sender.request(CREDENTIAL_RESOLVE_SUBTYPE, request, { timeoutMs: HOST_CREDENTIAL_TIMEOUT_MS });
615
+ } catch (err) {
616
+ if (err instanceof CredentialResolutionError2)
617
+ throw err;
618
+ throw new CredentialResolutionError2("io", `the host did not resolve ${redactRef2(ref)} (${err instanceof Error ? err.name : "error"})`);
619
+ }
620
+ if (!isAnswer(answer))
621
+ throw new CredentialResolutionError2("io", `the host answered ${redactRef2(ref)} with a malformed credential_resolve answer`);
622
+ if (!answer.ok) {
623
+ if (answer.reason === "not_found")
624
+ return null;
625
+ throw new CredentialResolutionError2("io", `the host refused ${redactRef2(ref)}: ${answer.reason}`);
626
+ }
627
+ return { material: answer.material, generation: answer.generation, ...answer.expiresAt !== undefined ? { expiresAt: answer.expiresAt } : {} };
628
+ }
629
+ function withoutRefreshToken(material) {
630
+ if (material.kind !== "oauth" || material.refreshToken === undefined)
631
+ return material;
632
+ const { refreshToken: _dropped, ...rest } = material;
633
+ return rest;
634
+ }
635
+ function createHostBrokeredCredentialStore(sender) {
636
+ const generations = new Map;
637
+ const key = (ref) => `${ref.service ?? ""}\x00${ref.account}`;
638
+ const noWrites = (ref) => new CredentialResolutionError2("unsupported", `a host-brokered session never persists credentials (${redactRef2(ref)}); its host renews them`);
639
+ return {
640
+ async get(ref) {
641
+ if (ref.kind !== "keychain")
642
+ throw new CredentialResolutionError2("unsupported", `the host-brokered credential store does not serve ${ref.kind} refs`);
643
+ const resolved = await resolveFromHost(sender, ref);
644
+ if (resolved === null)
645
+ return null;
646
+ generations.set(key(ref), resolved.generation);
647
+ return withoutRefreshToken(parseStoredCredentialMaterial(resolved.material, ref));
648
+ },
649
+ async set(ref) {
650
+ throw noWrites(ref);
651
+ },
652
+ async delete(ref) {
653
+ throw noWrites(ref);
654
+ },
655
+ async refresh(ref) {
656
+ const minGeneration = (generations.get(key(ref)) ?? 0) + 1;
657
+ const resolved = await resolveFromHost(sender, ref, minGeneration);
658
+ if (resolved === null)
659
+ throw new CredentialResolutionError2("io", `the host holds no credential for ${redactRef2(ref)} any more; sign in again`);
660
+ generations.set(key(ref), resolved.generation);
661
+ return withoutRefreshToken(parseStoredCredentialMaterial(resolved.material, ref));
662
+ }
663
+ };
664
+ }
665
+ function createHostBrokeredSecretReader(sender) {
666
+ return async (ref) => (await resolveFromHost(sender, ref))?.material ?? null;
667
+ }
668
+
669
+ // src/mcp-auth/store.ts
670
+ function createMemoryMcpOAuthStore2(initial = {}) {
671
+ const entries = new Map(Object.entries(initial));
672
+ return {
673
+ entries,
674
+ async read(account) {
675
+ return entries.get(account) ?? null;
676
+ },
677
+ async write(account, value) {
678
+ entries.set(account, value);
679
+ },
680
+ async remove(account) {
681
+ entries.delete(account);
682
+ }
683
+ };
684
+ }
685
+ function createKeychainMcpOAuthStore2(service) {
686
+ return service === undefined ? createKeychainRawStore() : createKeychainRawStore(service);
687
+ }
688
+ async function readTokenRecord(store, account) {
689
+ const raw = await store.read(account);
690
+ return raw === null ? null : decodeMcpOAuthTokenRecord2(raw);
691
+ }
692
+ async function readClientRecord(store, account) {
693
+ const raw = await store.read(account);
694
+ return raw === null ? null : decodeMcpOAuthClientRecord2(raw);
695
+ }
696
+ async function writeTokenRecord(store, account, record) {
697
+ await store.write(account, encodeMcpOAuthTokenRecord2(record));
698
+ }
699
+ async function writeClientRecord(store, account, record) {
700
+ await store.write(account, encodeMcpOAuthClientRecord2(record));
701
+ }
702
+ async function readTokenRecordLenient(store, account) {
703
+ try {
704
+ return await readTokenRecord(store, account);
705
+ } catch (err) {
706
+ if (err instanceof McpOAuthError2 && err.code === "malformed_record")
707
+ return null;
708
+ throw err;
709
+ }
710
+ }
711
+ function createTestFileMcpOAuthStore(path) {
712
+ const load = () => {
713
+ try {
714
+ return JSON.parse(readFileSync(path, "utf8"));
715
+ } catch {
716
+ return {};
717
+ }
718
+ };
719
+ const save = (entries) => {
720
+ const tmp = `${path}.${process.pid}.tmp`;
721
+ writeFileSync(tmp, JSON.stringify(entries), { mode: 384 });
722
+ renameSync(tmp, path);
723
+ };
724
+ return {
725
+ testFile: path,
726
+ async read(account) {
727
+ return load()[account] ?? null;
728
+ },
729
+ async write(account, value) {
730
+ save({ ...load(), [account]: value });
731
+ },
732
+ async remove(account) {
733
+ const entries = load();
734
+ delete entries[account];
735
+ save(entries);
736
+ }
737
+ };
738
+ }
739
+ function mcpOAuthTestStoreEnvName(brand) {
740
+ return envName(brand, "TEST_MCP_OAUTH_STORE_FILE");
741
+ }
742
+ var MCP_OAUTH_TEST_STORE_ENV = mcpOAuthTestStoreEnvName(WINTER_BRAND);
743
+ function resolveSessionMcpOAuthStore(opts) {
744
+ const { keychainService, testStoreFile } = opts;
745
+ if (testStoreFile !== undefined && testStoreFile !== "" && keychainService !== undefined && keychainService !== DEFAULT_KEYCHAIN_SERVICE2 && keychainService !== `${DEFAULT_KEYCHAIN_SERVICE2}.dev`) {
746
+ return createTestFileMcpOAuthStore(testStoreFile);
747
+ }
748
+ return createKeychainMcpOAuthStore2(keychainService);
749
+ }
750
+ var MCP_OAUTH_HOST_HELD_REFRESH_TOKEN2 = "host-held";
751
+ function toSessionMcpTokenRecord2(raw) {
752
+ const record = decodeMcpOAuthTokenRecord2(raw);
753
+ return encodeMcpOAuthTokenRecord2(record.refreshToken === undefined ? record : { ...record, refreshToken: MCP_OAUTH_HOST_HELD_REFRESH_TOKEN2 });
754
+ }
755
+ function createHostBrokeredMcpOAuthStore(sender) {
756
+ const refuse = (account) => new McpOAuthError2("invalid_account", `a host-brokered session never writes its MCP sign-ins (${account}); its host does`);
757
+ return {
758
+ async read(account) {
759
+ const material = (await resolveFromHost(sender, { kind: "keychain", account }))?.material;
760
+ if (material === undefined)
761
+ return null;
762
+ try {
763
+ return toSessionMcpTokenRecord2(material);
764
+ } catch {
765
+ return material;
766
+ }
767
+ },
768
+ async write(account) {
769
+ throw refuse(account);
770
+ },
771
+ async remove(account) {
772
+ throw refuse(account);
773
+ }
774
+ };
775
+ }
776
+
777
+ // src/mcp-auth/refresh.ts
778
+ import { computeScopeUnion, InsecureTokenEndpointError, OAuthError as OAuthError2, OAuthErrorCode, refreshAuthorization } from "@modelcontextprotocol/client";
779
+ var inflight = new WeakMap;
780
+ var SIGN_IN_AGAIN = new Set([OAuthErrorCode.InvalidGrant, OAuthErrorCode.InvalidClient, OAuthErrorCode.UnauthorizedClient, OAuthErrorCode.InvalidScope, OAuthErrorCode.UnsupportedGrantType]);
781
+ function log(account, what) {
782
+ console.error(`winter: mcp-auth: refresh for ${account}: ${what}`);
783
+ }
784
+ async function refreshMcpOAuthToken2(opts) {
785
+ const clientAccount = clientAccountForTokenAccount2(opts.account);
786
+ let perStore = inflight.get(opts.store);
787
+ if (perStore === undefined) {
788
+ perStore = new Map;
789
+ inflight.set(opts.store, perStore);
790
+ }
791
+ const running = perStore.get(opts.account);
792
+ if (running !== undefined)
793
+ return running;
794
+ const run = refreshOnce(opts, clientAccount).finally(() => {
795
+ perStore.delete(opts.account);
796
+ });
797
+ perStore.set(opts.account, run);
798
+ return run;
799
+ }
800
+ async function refreshOnce(opts, clientAccount) {
801
+ const { account, store } = opts;
802
+ const now = opts.now ?? Date.now;
803
+ let record;
804
+ try {
805
+ record = await readTokenRecordLenient(store, account);
806
+ } catch (err) {
807
+ log(account, err instanceof McpOAuthError2 ? err.code : "unreadable");
808
+ return { ok: false, reason: "transient" };
809
+ }
810
+ if (record === null)
811
+ return { ok: false, reason: "needs_auth" };
812
+ if (opts.generation !== undefined && record.generation !== opts.generation)
813
+ return { ok: true, generation: record.generation };
814
+ if (opts.stepUpScope !== undefined && opts.stepUpScope.trim() !== "") {
815
+ await recordStepUp(store, clientAccount, opts.stepUpScope);
816
+ return { ok: false, reason: "needs_auth" };
817
+ }
818
+ if (record.refreshToken === undefined)
819
+ return { ok: false, reason: "needs_auth" };
820
+ if (record.refreshToken === MCP_OAUTH_HOST_HELD_REFRESH_TOKEN2) {
821
+ log(account, "the record holds the host-held marker, not a refresh token (a session's view, not the host's store)");
822
+ return { ok: false, reason: "needs_auth" };
823
+ }
824
+ let client;
825
+ try {
826
+ client = await readClientRecord(store, clientAccount);
827
+ } catch {
828
+ client = null;
829
+ }
830
+ if (client === null || client.issuer !== record.issuer) {
831
+ log(account, "no client registration for this sign-in's issuer");
832
+ return { ok: false, reason: "needs_auth" };
833
+ }
834
+ const fetchFn = createMcpAuthFetch({ ...opts.fetch !== undefined ? { fetch: opts.fetch } : {}, allowLoopback: isLoopbackMcpServer(record.serverUrl) });
835
+ try {
836
+ const server = await loadAuthorizationServer({ authorizationServerUrl: client.authorizationServerUrl ?? record.issuer, expectedIssuer: record.issuer, fetchFn });
837
+ const resource = await resolveResourceIndicator({ serverUrl: record.serverUrl, ...client.resourceMetadataUrl !== undefined ? { resourceMetadataUrl: client.resourceMetadataUrl } : {}, fetchFn });
838
+ const fresh = await readTokenRecordLenient(store, account);
839
+ if (fresh === null)
840
+ return { ok: false, reason: "needs_auth" };
841
+ if (fresh.generation !== record.generation)
842
+ return { ok: true, generation: fresh.generation };
843
+ const tokens = await refreshAuthorization(server.authorizationServerUrl, {
844
+ ...server.metadata !== undefined ? { metadata: server.metadata } : {},
845
+ clientInformation: { client_id: client.clientId, ...client.clientSecret !== undefined ? { client_secret: client.clientSecret } : {} },
846
+ refreshToken: record.refreshToken,
847
+ ...resource !== undefined ? { resource } : {},
848
+ fetchFn
849
+ });
850
+ const generation = record.generation + 1;
851
+ const refreshToken = tokens.refresh_token ?? record.refreshToken;
852
+ const scope = tokens.scope ?? record.scope;
853
+ await writeTokenRecord(store, account, {
854
+ v: 1,
855
+ kind: "mcp-oauth",
856
+ serverUrl: record.serverUrl,
857
+ issuer: record.issuer,
858
+ accessToken: tokens.access_token,
859
+ ...refreshToken !== undefined ? { refreshToken } : {},
860
+ ...typeof tokens.expires_in === "number" ? { expiresAt: now() + tokens.expires_in * 1000 } : {},
861
+ ...scope !== undefined ? { scope } : {},
862
+ generation
863
+ });
864
+ return { ok: true, generation };
865
+ } catch (err) {
866
+ if (err instanceof OAuthError2) {
867
+ if (err.code === OAuthErrorCode.InvalidGrant) {
868
+ log(account, `${err.code}; the sign-in is cleared`);
869
+ await store.remove(account).catch(() => {});
870
+ return { ok: false, reason: "needs_auth" };
871
+ }
872
+ if (err.code === OAuthErrorCode.InvalidClient || err.code === OAuthErrorCode.UnauthorizedClient) {
873
+ log(account, `${err.code}; the client registration is cleared`);
874
+ await store.remove(clientAccount).catch(() => {});
875
+ return { ok: false, reason: "needs_auth" };
876
+ }
877
+ log(account, err.code);
878
+ return { ok: false, reason: SIGN_IN_AGAIN.has(err.code) ? "needs_auth" : "transient" };
879
+ }
880
+ if (err instanceof InsecureTokenEndpointError || err instanceof McpOAuthError2 && err.code === "policy_refused") {
881
+ log(account, "the token endpoint is refused by the auth-HTTP policy");
882
+ return { ok: false, reason: "needs_auth" };
883
+ }
884
+ log(account, err instanceof McpOAuthError2 ? err.code : "transient failure");
885
+ return { ok: false, reason: "transient" };
886
+ }
887
+ }
888
+ async function recordStepUp(store, clientAccount, scope) {
889
+ let client;
890
+ try {
891
+ client = await readClientRecord(store, clientAccount);
892
+ } catch {
893
+ return;
894
+ }
895
+ if (client === null)
896
+ return;
897
+ const stepUpScope = computeScopeUnion(client.stepUpScope, scope);
898
+ if (stepUpScope === undefined || stepUpScope === client.stepUpScope)
899
+ return;
900
+ await writeClientRecord(store, clientAccount, { ...client, stepUpScope });
901
+ }
902
+
903
+ export { McpOAuthError2, boundedReason, MCP_OAUTH_TOKEN_ACCOUNT_PREFIX2, MCP_OAUTH_CLIENT_ACCOUNT_PREFIX2, canonicalMcpServerUrl2, mcpOAuthAccountId2, mcpOAuthTokenAccount2, mcpOAuthClientAccount2, mcpOAuthClientSecretAccount2, isMcpOAuthTokenAccount2, clientAccountForTokenAccount2, WINTER_MCP_CLIENT_METADATA_URL2, MCP_OAUTH_CALLBACK_PATH2, MCP_OAUTH_EXPIRY_SKEW_MS2, MCP_OAUTH_SESSION_CACHE_MS2, MCP_OAUTH_LOGIN_TIMEOUT_MS2, MCP_OAUTH_STATE_BYTES, evaluateMcpAuthUrl2, isLoopbackMcpServer, createMcpAuthFetch, decodeMcpOAuthTokenRecord2, decodeMcpOAuthClientRecord2, encodeMcpOAuthTokenRecord2, encodeMcpOAuthClientRecord2, decodeMcpOAuthClientSecretItem2, encodeMcpOAuthClientSecretItem2, sameIssuer2, fetchAuthorizationServerMetadataDocument, loadAuthorizationServer, discoverMcpOAuthIssuer2, createKeychainCredentialStore, createKeychainSecretReader, keychainAccountName, createHostCredentialChannel, createHostBrokeredCredentialStore, createHostBrokeredSecretReader, createMemoryMcpOAuthStore2, createKeychainMcpOAuthStore2, readClientRecord, writeTokenRecord, writeClientRecord, readTokenRecordLenient, mcpOAuthTestStoreEnvName, resolveSessionMcpOAuthStore, MCP_OAUTH_HOST_HELD_REFRESH_TOKEN2, toSessionMcpTokenRecord2, createHostBrokeredMcpOAuthStore, refreshMcpOAuthToken2 };