@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
@@ -1,13 +1,15 @@
1
- import"./index-jz5d90c6.js";
1
+ import"./index-d4pmmr27.js";
2
2
  import {
3
3
  liveProcessGroups,
4
4
  onProcessGroupChange
5
- } from "./index-8jgwp1px.js";
5
+ } from "./index-9s68t0pd.js";
6
+ import"./index-z6s3r4xm.js";
7
+ import"./index-bgeqc7ax.js";
6
8
  import"./index-bef62z3r.js";
7
- import"./index-gmxxtqpe.js";
9
+ import"./index-0xqzk1hm.js";
8
10
  import {
9
11
  runEmbeddedSession2
10
- } from "./index-eyddzcgs.js";
12
+ } from "./index-c7kq6erg.js";
11
13
  import {
12
14
  Queue
13
15
  } from "./index-97t2rmtf.js";
package/dist/embedded.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  runEmbeddedSession2
3
- } from "./index-eyddzcgs.js";
4
- import"./index-gmxxtqpe.js";
5
- import"./index-jz5d90c6.js";
3
+ } from "./index-c7kq6erg.js";
4
+ import"./index-0xqzk1hm.js";
5
+ import"./index-d4pmmr27.js";
6
+ import"./index-bgeqc7ax.js";
6
7
  import"./index-bef62z3r.js";
7
- import"./index-8jgwp1px.js";
8
+ import"./index-9s68t0pd.js";
9
+ import"./index-z6s3r4xm.js";
8
10
  export {
9
11
  runEmbeddedSession2 as runEmbeddedSession
10
12
  };
package/dist/engine.d.ts CHANGED
@@ -12,6 +12,8 @@ import { type StructuredOutputSeam } from "./structured/seam.js";
12
12
  import { type FileCheckpointSink } from "./checkpoint/seam.js";
13
13
  import { type CommandResolver } from "./commands/seam.js";
14
14
  import { type McpServerSource } from "./mcp/lifecycle.js";
15
+ import type { McpOAuthStore } from "./mcp-auth/store.js";
16
+ import type { HostCredentialChannel } from "./provider/host-credentials.js";
15
17
  import { type ChildHandle } from "./subagents/child-handle.js";
16
18
  import { type SourcedRuleEntry } from "./permissions/ruleset.js";
17
19
  import { type AutoAuditRecorder, type ClassifierInterface } from "./permissions/auto/engine.js";
@@ -981,6 +983,20 @@ export interface EngineOptions {
981
983
  * clone connects freely.
982
984
  */
983
985
  extraMcpServerSources?: readonly McpServerSource[];
986
+ /**
987
+ * WS-25 (MCP OAuth): where this session READS its MCP sign-ins -- the session's own Keychain service,
988
+ * supplied by production wiring (`resolveSessionMcpOAuthStore`). Present, every remote (`http`/`sse`)
989
+ * server without a static `Authorization` header connects with a read-only bearer provider that asks
990
+ * the host (`mcp_oauth_refresh`) to refresh. ABSENT -- every direct `runEngine` caller, every unit test --
991
+ * no provider is attached anywhere, the Keychain is never touched, and a 401 is `needs-auth` as before.
992
+ */
993
+ mcpOAuthStore?: McpOAuthStore;
994
+ /**
995
+ * WS-25 §7: the host-credential channel production wiring built for a host-brokered session
996
+ * (`RuntimeConfig.hostCredentials`). Bound to this run's control bridge the moment it exists, so every
997
+ * `credential_resolve` rides the session's own stdio pipe. Absent: nothing to bind.
998
+ */
999
+ hostCredentialChannel?: HostCredentialChannel;
984
1000
  /**
985
1001
  * `system/init.slash_commands`. Produced by `slashCommandNames(resolver, cwd)`, which ALREADY
986
1002
  * includes the engine's own `/compact` -- the engine must not prepend it a second time.
@@ -15,6 +15,12 @@ import {
15
15
  mcpServerHasRegistrations,
16
16
  STRUCTURED_OUTPUT_TOOL_NAME,
17
17
  looksLikeCommand,
18
+ capBytes,
19
+ neutralizeReminderTags,
20
+ readWhole,
21
+ readCappedLinesAndBytes,
22
+ registerAttachmentRenderer,
23
+ attachmentsIn,
18
24
  registerChildEngineFactory,
19
25
  matchFileRule,
20
26
  rawToRuleValue,
@@ -29,12 +35,6 @@ import {
29
35
  resolveProjectDirName2,
30
36
  buildForkDirectiveText,
31
37
  buildForkInitialMessages,
32
- capBytes,
33
- neutralizeReminderTags,
34
- readWhole,
35
- readCappedLinesAndBytes,
36
- registerAttachmentRenderer,
37
- attachmentsIn,
38
38
  gitInstructionsEnabled,
39
39
  isUserInvocable,
40
40
  buildSkillListing,
@@ -79,15 +79,23 @@ import {
79
79
  childTranscriptSubpath,
80
80
  buildChildTranscriptWriter,
81
81
  listChildAgentIds,
82
- createKeychainCredentialStore,
83
- createKeychainSecretReader,
84
82
  resolveSessionProvider,
85
83
  redactCredentialRef,
86
84
  createProviderContext,
87
85
  providerCredentialRef2,
88
86
  createModelClassifier2,
89
87
  selectClassifierRoute2
90
- } from "./index-jz5d90c6.js";
88
+ } from "./index-d4pmmr27.js";
89
+ import {
90
+ createKeychainCredentialStore,
91
+ createKeychainSecretReader,
92
+ createHostCredentialChannel,
93
+ createHostBrokeredCredentialStore,
94
+ createHostBrokeredSecretReader,
95
+ mcpOAuthTestStoreEnvName,
96
+ resolveSessionMcpOAuthStore,
97
+ createHostBrokeredMcpOAuthStore
98
+ } from "./index-z6s3r4xm.js";
91
99
  import {
92
100
  createInMemoryChannel2
93
101
  } from "./index-97t2rmtf.js";
@@ -1700,8 +1708,8 @@ function apiProviderFor(providerId) {
1700
1708
  return providerId === undefined ? undefined : API_PROVIDER_BY_PROVIDER_ID[providerId];
1701
1709
  }
1702
1710
  var DEFAULT_PROVIDER_ACCOUNT_ID = "default";
1703
- function createProductionCredentialStore(config, env, home) {
1704
- const keychain = createKeychainCredentialStore(resolveSessionKeychainService(config));
1711
+ function createProductionCredentialStore(config, env, home, keychainMember) {
1712
+ const keychain = keychainMember ?? createKeychainCredentialStore(resolveSessionKeychainService(config));
1705
1713
  return createCompositeCredentialStore([
1706
1714
  {
1707
1715
  ...keychain,
@@ -2201,7 +2209,8 @@ var ALL_TIER_REFUSED_ENV = [
2201
2209
  storeHomeEnvName(WINTER_BRAND5),
2202
2210
  pluginCacheDirEnvName(WINTER_BRAND5),
2203
2211
  providerManagedByHostEnvName(WINTER_BRAND5),
2204
- disableCronEnvName(WINTER_BRAND5)
2212
+ disableCronEnvName(WINTER_BRAND5),
2213
+ mcpOAuthTestStoreEnvName(WINTER_BRAND5)
2205
2214
  ];
2206
2215
  function escapeRegExpLiteral2(literal) {
2207
2216
  return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -5469,6 +5478,7 @@ function describeCatalogModel(catalog, model, providerId) {
5469
5478
  async function buildProductionWiring(opts) {
5470
5479
  const { config, env } = opts;
5471
5480
  const brand = config.brand ?? WINTER_BRAND15;
5481
+ const mcpOAuthTestStoreFile = env[mcpOAuthTestStoreEnvName(brand)];
5472
5482
  const winterHome = opts.winterHome ?? config.winterHome ?? resolveWinterHome2(env, brand);
5473
5483
  const storeHomeEnv = env[storeHomeEnvName2(brand)];
5474
5484
  const storeHome = config.storeHome ?? (isUnset(storeHomeEnv) ? undefined : storeHomeEnv);
@@ -5643,9 +5653,15 @@ async function buildProductionWiring(opts) {
5643
5653
  ...skillListingBudgetFraction !== undefined ? { budgetFraction: skillListingBudgetFraction } : {},
5644
5654
  ...config.contextWindowTokens !== undefined ? { contextWindowTokens: config.contextWindowTokens } : {}
5645
5655
  });
5656
+ const hostCredentialChannel = config.hostCredentials === true ? createHostCredentialChannel() : undefined;
5657
+ const hostCredentialOptions = hostCredentialChannel !== undefined ? {
5658
+ credentials: createProductionCredentialStore(config, env, opts.provider?.home ?? env["HOME"] ?? "", createHostBrokeredCredentialStore(hostCredentialChannel)),
5659
+ readKeychainSecret: createHostBrokeredSecretReader(hostCredentialChannel)
5660
+ } : {};
5646
5661
  const providerWiring = buildSessionProvider({
5647
5662
  config,
5648
5663
  env,
5664
+ ...hostCredentialOptions,
5649
5665
  providerSettings: () => providerSettingsFrom(settingsGetter()),
5650
5666
  resolveSlot: (requested, currentModelKey) => resolveSlot(requested, currentModelKey),
5651
5667
  advisorModelSetting: () => settingsGetter()?.advisor?.model,
@@ -5912,6 +5928,8 @@ async function buildProductionWiring(opts) {
5912
5928
  ...pluginWorkflows.length > 0 ? { pluginWorkflows } : {},
5913
5929
  ...settingSources !== undefined ? { settingSources } : {},
5914
5930
  extraMcpServerSources,
5931
+ mcpOAuthStore: hostCredentialChannel !== undefined ? createHostBrokeredMcpOAuthStore(hostCredentialChannel) : resolveSessionMcpOAuthStore({ keychainService: resolveSessionKeychainService(config), testStoreFile: mcpOAuthTestStoreFile }),
5932
+ ...hostCredentialChannel !== undefined ? { hostCredentialChannel } : {},
5915
5933
  initSlashCommands,
5916
5934
  initSkills,
5917
5935
  initPlugins,
@@ -1,3 +1,11 @@
1
+ import {
2
+ mcpOAuthTokenAccount2,
3
+ MCP_OAUTH_EXPIRY_SKEW_MS2,
4
+ MCP_OAUTH_SESSION_CACHE_MS2,
5
+ readTokenRecordLenient,
6
+ refreshMcpOAuthToken2
7
+ } from "./index-z6s3r4xm.js";
8
+
1
9
  // src/process-groups.ts
2
10
  var live = new Map;
3
11
  var listeners = new Set;
@@ -109,8 +117,117 @@ function toWireElicitContent(content) {
109
117
  return out;
110
118
  }
111
119
 
120
+ // src/mcp-auth/session-provider.ts
121
+ class McpNeedsAuthError extends Error {
122
+ serverName;
123
+ constructor(serverName, why, hint) {
124
+ super(`MCP server "${serverName}" needs sign-in (${why}). ${hint}`);
125
+ this.name = "McpNeedsAuthError";
126
+ this.serverName = serverName;
127
+ }
128
+ }
129
+
130
+ class McpAuthRefreshUnavailableError extends Error {
131
+ constructor(serverName) {
132
+ super(`MCP server "${serverName}": its sign-in could not be refreshed right now (the authorization server did not answer); reconnect it later`);
133
+ this.name = "McpAuthRefreshUnavailableError";
134
+ }
135
+ }
136
+ function createSessionAuthProvider(opts) {
137
+ const { serverName, oauth } = opts;
138
+ const account = mcpOAuthTokenAccount2(opts.serverUrl);
139
+ const now = oauth.now ?? Date.now;
140
+ let cached;
141
+ let refreshing;
142
+ const needsAuth = (why) => new McpNeedsAuthError(serverName, why, oauth.signInHint(serverName));
143
+ let refreshedGeneration;
144
+ const usable = (record) => {
145
+ if (record.expiresAt === undefined)
146
+ return true;
147
+ const margin = record.refreshToken !== undefined && record.generation !== refreshedGeneration ? MCP_OAUTH_EXPIRY_SKEW_MS2 : 0;
148
+ return record.expiresAt - now() > margin;
149
+ };
150
+ async function read(force) {
151
+ if (!force && cached !== undefined && now() - cached.readAt < MCP_OAUTH_SESSION_CACHE_MS2 && (cached.record === null || usable(cached.record)))
152
+ return cached.record;
153
+ const record = await readTokenRecordLenient(oauth.store, account);
154
+ cached = { record, readAt: now() };
155
+ return record;
156
+ }
157
+ async function ask(generation, stepUpScope) {
158
+ const request = { server: serverName, account, generation, ...stepUpScope !== undefined ? { stepUpScope } : {} };
159
+ if (oauth.askHost !== undefined) {
160
+ const answer = await oauth.askHost(request).catch(() => ({ ok: false, reason: "transient" }));
161
+ if (answer !== "unhandled")
162
+ return answer;
163
+ if (oauth.hostOwnsRefresh === true)
164
+ return { ok: false, reason: "transient" };
165
+ }
166
+ if (oauth.hostOwnsRefresh === true)
167
+ return { ok: false, reason: "transient" };
168
+ const result = await refreshMcpOAuthToken2({ account, store: oauth.store, generation, ...stepUpScope !== undefined ? { stepUpScope } : {}, ...oauth.fetch !== undefined ? { fetch: oauth.fetch } : {}, ...oauth.now !== undefined ? { now: oauth.now } : {} });
169
+ return result.ok ? { ok: true } : result;
170
+ }
171
+ function refresh(generation) {
172
+ refreshing ??= ask(generation).finally(() => {
173
+ refreshing = undefined;
174
+ });
175
+ return refreshing;
176
+ }
177
+ async function refreshed(record) {
178
+ if (record.refreshToken === undefined)
179
+ throw needsAuth("the stored token has expired and there is no refresh token");
180
+ const answer = await refresh(record.generation);
181
+ if (!answer.ok) {
182
+ if (answer.reason === "needs_auth")
183
+ throw needsAuth("the sign-in could not be refreshed");
184
+ throw new McpAuthRefreshUnavailableError(serverName);
185
+ }
186
+ const next = await read(true);
187
+ if (next === null || next.generation === record.generation)
188
+ throw needsAuth("the refreshed sign-in was not found");
189
+ refreshedGeneration = next.generation;
190
+ return next;
191
+ }
192
+ async function current() {
193
+ const record = await read(false);
194
+ if (record === null)
195
+ return;
196
+ if (usable(record))
197
+ return record.accessToken;
198
+ return (await refreshed(record)).accessToken;
199
+ }
200
+ return {
201
+ token: current,
202
+ async preflight() {
203
+ await current();
204
+ },
205
+ async onUnauthorized() {
206
+ const before = cached?.record ?? null;
207
+ const record = await read(true);
208
+ if (record === null)
209
+ throw needsAuth("the server requires a sign-in");
210
+ if (before === null || record.accessToken !== before.accessToken)
211
+ return;
212
+ await refreshed(record);
213
+ },
214
+ async reportInsufficientScope(scope) {
215
+ const record = await read(true).catch(() => null);
216
+ if (record === null || scope === undefined || scope.trim() === "")
217
+ return;
218
+ await ask(record.generation, scope).catch(() => {
219
+ return;
220
+ });
221
+ }
222
+ };
223
+ }
224
+ function mcpSignInHint(brand, serverName) {
225
+ const cli = brand.homeDirName.replace(/^\./, "");
226
+ return `Sign in with \`${cli} mcp login ${serverName}\` (or ${brand.productName} Settings -> MCP), then reconnect the server.`;
227
+ }
228
+
112
229
  // src/mcp/client.ts
113
- import { Client, ProtocolError, ProtocolErrorCode, SdkError, SdkErrorCode, SdkHttpError, SseError, UnauthorizedError, UnsupportedProtocolVersionError } from "@modelcontextprotocol/client";
230
+ import { Client, InsufficientScopeError, ProtocolError, ProtocolErrorCode, SdkError, SdkErrorCode, SdkHttpError, SseError, UnauthorizedError, UnsupportedProtocolVersionError } from "@modelcontextprotocol/client";
114
231
 
115
232
  // src/mcp/transports/stdio.ts
116
233
  import { spawn } from "node:child_process";
@@ -261,19 +378,22 @@ function buildStdioTransport(cfg, cwd) {
261
378
 
262
379
  // src/mcp/transports/http.ts
263
380
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
381
+ var refusingRedirectsFetch = (url, init) => fetch(url, { ...init, redirect: "error" });
264
382
  function buildHttpTransport(cfg, opts = {}) {
265
383
  const requestInit = { ...cfg.headers !== undefined ? { headers: cfg.headers } : {}, ...opts.refuseRedirects === true ? { redirect: "error" } : {} };
266
384
  const transport = new StreamableHTTPClientTransport(new URL(cfg.url), {
267
- ...Object.keys(requestInit).length > 0 ? { requestInit } : {}
385
+ ...Object.keys(requestInit).length > 0 ? { requestInit } : {},
386
+ ...opts.authProvider !== undefined ? { authProvider: opts.authProvider, fetch: refusingRedirectsFetch } : {}
268
387
  });
269
388
  return transport;
270
389
  }
271
390
 
272
391
  // src/mcp/transports/sse.ts
273
392
  import { SSEClientTransport } from "@modelcontextprotocol/client";
274
- function buildSseTransport(cfg) {
393
+ function buildSseTransport(cfg, opts = {}) {
275
394
  const transport = new SSEClientTransport(new URL(cfg.url), {
276
- ...cfg.headers !== undefined ? { requestInit: { headers: cfg.headers } } : {}
395
+ ...cfg.headers !== undefined ? { requestInit: { headers: cfg.headers } } : {},
396
+ ...opts.authProvider !== undefined ? { authProvider: opts.authProvider, fetch: refusingRedirectsFetch } : {}
277
397
  });
278
398
  return transport;
279
399
  }
@@ -310,6 +430,12 @@ function toSdkNegotiationMode(mode) {
310
430
  function classifyConnectError(err, facts = { transportClosed: false, pinned: false }) {
311
431
  if (err instanceof McpConnectError2)
312
432
  return err;
433
+ if (err instanceof McpNeedsAuthError)
434
+ return new McpConnectError2("needs_auth", err.message);
435
+ if (err instanceof McpAuthRefreshUnavailableError)
436
+ return new McpConnectError2("auth_refresh_failed", err.message);
437
+ if (err instanceof InsufficientScopeError)
438
+ return new McpConnectError2("needs_auth", "the server requires additional permission (403 insufficient_scope); sign in again to grant it", 403);
313
439
  if (err instanceof UnauthorizedError)
314
440
  return new McpConnectError2("needs_auth", err.message);
315
441
  if (err instanceof SdkError && err.code === SdkErrorCode.RequestTimeout)
@@ -367,7 +493,7 @@ function autoProbeTimeoutMs(connectTimeoutMs) {
367
493
  return Math.max(1, Math.min(AUTO_PROBE_TIMEOUT_CAP_MS, Math.floor(connectTimeoutMs / 3)));
368
494
  }
369
495
  function retriesAsLegacy(err) {
370
- if (err.code === "needs_auth" || err.code === "spawn_failed" || err.code === "version_mismatch")
496
+ if (err.code === "needs_auth" || err.code === "auth_refresh_failed" || err.code === "spawn_failed" || err.code === "version_mismatch")
371
497
  return false;
372
498
  return err.httpStatus !== 401 && err.httpStatus !== 403;
373
499
  }
@@ -428,10 +554,11 @@ async function connectOnce(opts, mode, connectTimeoutMs, probeTimeoutMs) {
428
554
  throw new McpConnectError2("spawn_failed", `mcp client: server "${name}" is configured as type "sdk" but no in-process server instance was supplied to connectMcpServer`);
429
555
  }
430
556
  transport = await buildSdkTransport(opts.inProcessServer);
431
- } else if (config.type === "http") {
432
- transport = buildHttpTransport(config, opts.refuseHttpRedirects === true ? { refuseRedirects: true } : {});
433
- } else if (config.type === "sse") {
434
- transport = buildSseTransport(config);
557
+ } else if (config.type === "http" || config.type === "sse") {
558
+ if (opts.auth !== undefined)
559
+ await boundedPreflight(opts.auth, connectTimeoutMs);
560
+ const authProvider = opts.auth !== undefined ? { token: () => opts.auth.token(), onUnauthorized: () => opts.auth.onUnauthorized() } : undefined;
561
+ transport = config.type === "http" ? buildHttpTransport(config, { ...opts.refuseHttpRedirects === true ? { refuseRedirects: true } : {}, ...authProvider !== undefined ? { authProvider } : {} }) : buildSseTransport(config, authProvider !== undefined ? { authProvider } : {});
435
562
  } else {
436
563
  if (opts.cwd === undefined) {
437
564
  throw new McpConnectError2("spawn_failed", `mcp client: stdio server "${name}" has no session cwd to start in; it is never started in the host process's cwd`);
@@ -516,10 +643,13 @@ async function connectOnce(opts, mode, connectTimeoutMs, probeTimeoutMs) {
516
643
  }
517
644
  };
518
645
  } catch (err) {
646
+ const scopeChallenge = insufficientScopeOf(err);
647
+ if (scopeChallenge !== undefined && opts.auth !== undefined)
648
+ await opts.auth.reportInsufficientScope(scopeChallenge.requiredScope).catch(() => {});
519
649
  const stderrTail = transport instanceof WinterStdioTransport ? transport.stderrTail.trim() : "";
520
650
  const facts = { transportClosed: transport instanceof WinterStdioTransport && transport.exitedOnItsOwn, pinned: typeof mode === "object" };
521
651
  try {
522
- await transport.close();
652
+ await transport?.close();
523
653
  } catch {}
524
654
  const classified = classifyConnectError(err, facts);
525
655
  if (stderrTail === "" || classified.message.includes(stderrTail))
@@ -529,5 +659,34 @@ async function connectOnce(opts, mode, connectTimeoutMs, probeTimeoutMs) {
529
659
  ${stderrTail}`, classified.httpStatus);
530
660
  }
531
661
  }
662
+ function boundedPreflight(auth, timeoutMs) {
663
+ let timer;
664
+ const outer = new Promise((_, reject) => {
665
+ timer = setTimeout(() => reject(new McpConnectError2("timeout", `mcp client: the sign-in check exceeded ${timeoutMs}ms`)), timeoutMs);
666
+ timer.unref?.();
667
+ });
668
+ return Promise.race([auth.preflight(), outer]).finally(() => {
669
+ if (timer !== undefined)
670
+ clearTimeout(timer);
671
+ });
672
+ }
673
+ function insufficientScopeOf(err) {
674
+ for (let e = err, depth = 0;e !== undefined && e !== null && depth < 4; e = e.cause, depth++) {
675
+ if (e instanceof InsufficientScopeError)
676
+ return e;
677
+ }
678
+ return;
679
+ }
680
+ function classifyMcpAuthFailure(err) {
681
+ for (let e = err, depth = 0;e !== undefined && e !== null && depth < 4; e = e.cause, depth++) {
682
+ if (e instanceof InsufficientScopeError)
683
+ return "insufficient_scope";
684
+ if (e instanceof McpNeedsAuthError || e instanceof UnauthorizedError)
685
+ return "needs_auth";
686
+ if (e instanceof SdkHttpError && e.status === 401)
687
+ return "needs_auth";
688
+ }
689
+ return;
690
+ }
532
691
 
533
- export { trackProcessGroup, liveProcessGroups, onProcessGroupChange, createElicitationAsker2, McpConnectError2, resolveVersionNegotiation2, connectMcpServer2 };
692
+ export { trackProcessGroup, liveProcessGroups, onProcessGroupChange, createElicitationAsker2, createSessionAuthProvider, mcpSignInHint, McpConnectError2, resolveVersionNegotiation2, connectMcpServer2, insufficientScopeOf, classifyMcpAuthFailure };
@@ -0,0 +1,52 @@
1
+ import {
2
+ evaluateMcpAuthUrl2,
3
+ isLoopbackMcpServer
4
+ } from "./index-z6s3r4xm.js";
5
+
6
+ // src/mcp-auth/config.ts
7
+ var OAUTH_KEYS = new Set(["clientId", "clientSecretRef", "callbackPort", "authServerMetadataUrl", "scopes"]);
8
+ var SECRET_REF_KEYS = new Set(["kind"]);
9
+ function isPlainObject(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+ function validateMcpOAuthConfig2(oauth, serverUrl) {
13
+ if (!isPlainObject(oauth))
14
+ return "MCP server config 'oauth' must be an object";
15
+ for (const key of Object.keys(oauth)) {
16
+ if (key === "clientSecret")
17
+ return `MCP server config 'oauth.clientSecret' is refused: a client secret never lives in a config -- store it in the Keychain (the host's sign-in door) and mark it with 'oauth.clientSecretRef': { kind: "keychain" }`;
18
+ if (!OAUTH_KEYS.has(key))
19
+ return `MCP server config 'oauth' has an unknown key ${JSON.stringify(key)} (expected clientId, clientSecretRef, callbackPort, authServerMetadataUrl, scopes)`;
20
+ }
21
+ const { clientId, clientSecretRef, callbackPort, authServerMetadataUrl, scopes } = oauth;
22
+ if (clientId !== undefined && (typeof clientId !== "string" || clientId.trim() === ""))
23
+ return "MCP server config 'oauth.clientId' must be a non-empty string";
24
+ if (clientSecretRef !== undefined) {
25
+ if (clientId === undefined)
26
+ return "MCP server config 'oauth.clientSecretRef' needs 'oauth.clientId': a secret belongs to a pre-registered client";
27
+ if (!isPlainObject(clientSecretRef))
28
+ return `MCP server config 'oauth.clientSecretRef' must be { kind: "keychain" }`;
29
+ for (const key of Object.keys(clientSecretRef)) {
30
+ if (!SECRET_REF_KEYS.has(key))
31
+ return `MCP server config 'oauth.clientSecretRef' has the key ${JSON.stringify(key)}, which is refused: it is { kind: "keychain" } only -- the secret's Keychain account is derived from the server URL, never named by a config`;
32
+ }
33
+ if (clientSecretRef.kind !== "keychain")
34
+ return `MCP server config 'oauth.clientSecretRef.kind' must be "keychain"`;
35
+ }
36
+ if (callbackPort !== undefined && (typeof callbackPort !== "number" || !Number.isInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535)) {
37
+ return "MCP server config 'oauth.callbackPort' must be an integer port (1-65535)";
38
+ }
39
+ if (authServerMetadataUrl !== undefined) {
40
+ if (typeof authServerMetadataUrl !== "string")
41
+ return "MCP server config 'oauth.authServerMetadataUrl' must be a URL string";
42
+ const verdict = evaluateMcpAuthUrl2(authServerMetadataUrl, { allowLoopback: serverUrl !== undefined && isLoopbackMcpServer(serverUrl) });
43
+ if (!verdict.ok)
44
+ return `MCP server config 'oauth.authServerMetadataUrl' is refused: ${verdict.reason}`;
45
+ }
46
+ if (scopes !== undefined && (!Array.isArray(scopes) || scopes.some((s) => typeof s !== "string" || s === "" || /\s/.test(s)))) {
47
+ return "MCP server config 'oauth.scopes' must be an array of scope tokens (non-empty, no whitespace)";
48
+ }
49
+ return;
50
+ }
51
+
52
+ export { validateMcpOAuthConfig2 };
@@ -9,14 +9,14 @@ import {
9
9
  resolveEngineSession2,
10
10
  resolveProductionWinterHome2,
11
11
  resolveProductionStoreHome
12
- } from "./index-jz5d90c6.js";
12
+ } from "./index-d4pmmr27.js";
13
13
  import {
14
14
  registerDefaultChildEngineFactory,
15
15
  loadResumedChain,
16
16
  buildProductionWiring,
17
17
  withAutoSkillPermissions,
18
18
  restoreChildRoster
19
- } from "./index-gmxxtqpe.js";
19
+ } from "./index-0xqzk1hm.js";
20
20
 
21
21
  // src/embedded.ts
22
22
  import { splitFrames, encodeFrame } from "@yanlinglabs/winter-agent-sdk";