@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.
- package/dist/embedded-worker.js +6 -4
- package/dist/embedded.js +6 -4
- package/dist/engine.d.ts +16 -0
- package/dist/{index-gmxxtqpe.js → index-0xqzk1hm.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-c7kq6erg.js} +2 -2
- package/dist/{index-jz5d90c6.js → index-d4pmmr27.js} +191 -128
- 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 +9 -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/rpc/bridge.d.ts +5 -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/embedded-worker.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import"./index-
|
|
1
|
+
import"./index-d4pmmr27.js";
|
|
2
2
|
import {
|
|
3
3
|
liveProcessGroups,
|
|
4
4
|
onProcessGroupChange
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-9s68t0pd.js";
|
|
6
|
+
import"./index-z6s3r4xm.js";
|
|
7
|
+
import"./index-bgeqc7ax.js";
|
|
6
8
|
import"./index-bef62z3r.js";
|
|
7
|
-
import"./index-
|
|
9
|
+
import"./index-0xqzk1hm.js";
|
|
8
10
|
import {
|
|
9
11
|
runEmbeddedSession2
|
|
10
|
-
} from "./index-
|
|
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-
|
|
4
|
-
import"./index-
|
|
5
|
-
import"./index-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
|
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-
|
|
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-
|
|
19
|
+
} from "./index-0xqzk1hm.js";
|
|
20
20
|
|
|
21
21
|
// src/embedded.ts
|
|
22
22
|
import { splitFrames, encodeFrame } from "@yanlinglabs/winter-agent-sdk";
|