@ai-ecoverse/slicc-agent 3.35.0 → 3.36.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/mcp/index.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
5
- "mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n signIn: async (id) => {\n throw new Error(`${id} is not an MCP server that signs in`);\n },\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
5
+ "mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,QAAQ,OAAO,OAAO;AACpB,UAAM,IAAI,MAAM,GAAG,EAAE,qCAAqC;AAAA,EAC5D;AAAA,EACA,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,268 @@
1
+ import {
2
+ authorizeMcp,
3
+ discoverOAuthServerInfo,
4
+ McpOAuthAuthorizationRequiredError,
5
+ parseWwwAuthenticate,
6
+ registerClient,
7
+ selectResource,
8
+ stepUpScope
9
+ } from "@earendil-works/pi-mcp/oauth";
10
+ import { credentialKey } from "./credentials.js";
11
+ const CLIENT_NAME = "SLICC";
12
+ function sentence(text) {
13
+ const trimmed = text.trim();
14
+ const lowered = /^[A-Z][a-z]/.test(trimmed) ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}` : trimmed;
15
+ return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;
16
+ }
17
+ const LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
18
+ function secureUrl(url) {
19
+ return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK.has(url.hostname);
20
+ }
21
+ function signInRequired(cause) {
22
+ const error = new McpOAuthAuthorizationRequiredError();
23
+ if (cause !== void 0) error.cause = cause;
24
+ return error;
25
+ }
26
+ function authProvider(provider, server) {
27
+ const configured = server.config.oauth?.authServerMetadataUrl;
28
+ let inFlight;
29
+ const authorize = async (context, step, challenge) => {
30
+ const granted = step ? await provider.tokens() : void 0;
31
+ try {
32
+ await authorizeMcp(provider, {
33
+ serverUrl: context.serverUrl,
34
+ ...challenge.resourceMetadataUrl ? { resourceMetadataUrl: challenge.resourceMetadataUrl } : {},
35
+ ...step ? { scope: stepUpScope(granted?.scope, challenge.scope), skipRefresh: true } : challenge.scope ? { scope: challenge.scope } : {},
36
+ ...configured ? { authorizationServerMetadataUrl: new URL(configured) } : {},
37
+ fetch: context.fetch
38
+ });
39
+ } catch (error) {
40
+ throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);
41
+ }
42
+ };
43
+ return {
44
+ token: async () => (await provider.tokens())?.access_token,
45
+ async onUnauthorized(context) {
46
+ const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
47
+ const step = challenge.error === "insufficient_scope";
48
+ if (!step && !inFlight && context.token !== void 0) {
49
+ const current = (await provider.tokens())?.access_token;
50
+ if (current !== void 0 && current !== context.token) return;
51
+ }
52
+ inFlight ??= authorize(context, step, challenge).finally(() => {
53
+ inFlight = void 0;
54
+ });
55
+ await inFlight;
56
+ }
57
+ };
58
+ }
59
+ const bytesToBase64Url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
60
+ const random = (length) => crypto.getRandomValues(new Uint8Array(length));
61
+ async function pkce() {
62
+ const verifier = bytesToBase64Url(random(32));
63
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
64
+ return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };
65
+ }
66
+ function randomState() {
67
+ return [...random(32)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
68
+ }
69
+ class StoredOAuth {
70
+ redirectUrl;
71
+ clientMetadata;
72
+ #credentials;
73
+ #server;
74
+ #writes = Promise.resolve();
75
+ constructor(server, credentials, redirectUrl) {
76
+ this.#server = server;
77
+ this.#credentials = credentials;
78
+ this.redirectUrl = redirectUrl;
79
+ this.clientMetadata = {
80
+ client_name: server.config.oauth?.clientName ?? CLIENT_NAME,
81
+ redirect_uris: [redirectUrl],
82
+ grant_types: ["authorization_code", "refresh_token"],
83
+ response_types: ["code"],
84
+ token_endpoint_auth_method: "none",
85
+ ...server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}
86
+ };
87
+ }
88
+ get serverUrl() {
89
+ return String(new URL(this.#server.config.url));
90
+ }
91
+ async #key() {
92
+ return credentialKey(this.#server.name, this.#server.config.url);
93
+ }
94
+ async load() {
95
+ await this.#writes;
96
+ const stored = await this.#credentials.read(await this.#key());
97
+ const state = stored?.type === "oauth" ? stored.state : void 0;
98
+ return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };
99
+ }
100
+ async update(change) {
101
+ const write = this.#writes.then(async () => {
102
+ const key = await this.#key();
103
+ await this.#credentials.modify(key, async (current) => {
104
+ const before = current?.type === "oauth" ? current.state : void 0;
105
+ const next = change(
106
+ before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl }
107
+ );
108
+ return {
109
+ type: "oauth",
110
+ access: next.tokens?.access_token ?? "",
111
+ refresh: next.tokens?.refresh_token ?? "",
112
+ expires: next.tokensExpireAt ?? 0,
113
+ state: next
114
+ };
115
+ });
116
+ });
117
+ this.#writes = write.catch(() => void 0);
118
+ await write;
119
+ }
120
+ async signedIn() {
121
+ return (await this.load()).tokens !== void 0;
122
+ }
123
+ state() {
124
+ throw new McpOAuthAuthorizationRequiredError();
125
+ }
126
+ async clientInformation() {
127
+ const configured = this.#server.config.oauth?.clientId;
128
+ return configured ? { client_id: configured } : (await this.load()).clientInformation;
129
+ }
130
+ async saveClientInformation(information) {
131
+ if (this.#server.config.oauth?.clientId) return;
132
+ await this.update((state) => ({ ...state, clientInformation: information }));
133
+ }
134
+ async tokens() {
135
+ return (await this.load()).tokens;
136
+ }
137
+ async saveTokens(tokens) {
138
+ const expires = tokens.expires_in === void 0 ? void 0 : Date.now() + tokens.expires_in * 1e3;
139
+ await this.update((state) => {
140
+ const next = { ...state, tokens };
141
+ if (expires === void 0) delete next.tokensExpireAt;
142
+ else next.tokensExpireAt = expires;
143
+ return next;
144
+ });
145
+ }
146
+ redirectToAuthorization() {
147
+ throw new McpOAuthAuthorizationRequiredError();
148
+ }
149
+ async saveCodeVerifier(verifier) {
150
+ await this.update((state) => ({ ...state, codeVerifier: verifier }));
151
+ }
152
+ async codeVerifier() {
153
+ const verifier = (await this.load()).codeVerifier;
154
+ if (!verifier) throw new Error("No OAuth PKCE code verifier is stored");
155
+ return verifier;
156
+ }
157
+ async invalidateCredentials(kind) {
158
+ await this.update((state) => {
159
+ const next = { ...state };
160
+ if (kind === "all" || kind === "client") delete next.clientInformation;
161
+ if (kind === "all" || kind === "tokens") {
162
+ delete next.tokens;
163
+ delete next.tokensExpireAt;
164
+ }
165
+ if (kind === "all" || kind === "verifier") delete next.codeVerifier;
166
+ if (kind === "all" || kind === "discovery") delete next.discovery;
167
+ if (kind === "all") delete next.oauthState;
168
+ return next;
169
+ });
170
+ }
171
+ async saveDiscoveryState(discovery) {
172
+ await this.update((state) => ({ ...state, discovery }));
173
+ }
174
+ async discoveryState() {
175
+ return (await this.load()).discovery;
176
+ }
177
+ async forget() {
178
+ await this.#writes;
179
+ await this.#credentials.delete(await this.#key());
180
+ }
181
+ }
182
+ async function beginSignIn(provider, server, options) {
183
+ const configured = server.config.oauth?.authServerMetadataUrl;
184
+ const info = await discoverOAuthServerInfo(server.config.url, {
185
+ fetch: options.fetch,
186
+ ...options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {},
187
+ ...configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}
188
+ });
189
+ if (!configured) await provider.saveDiscoveryState(info);
190
+ const metadata = info.authorizationServerMetadata;
191
+ if (metadata && !metadata.response_types_supported.includes("code"))
192
+ throw new Error("The authorization server doesn't support authorization codes.");
193
+ if (metadata?.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256"))
194
+ throw new Error("The authorization server doesn't support PKCE S256.");
195
+ const scope = server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(" ") || void 0;
196
+ let client = await provider.clientInformation();
197
+ if (!client) {
198
+ client = await registerClient(info.authorizationServerUrl, {
199
+ ...metadata ? { metadata } : {},
200
+ clientMetadata: provider.clientMetadata,
201
+ ...scope ? { scope } : {},
202
+ fetch: options.fetch
203
+ });
204
+ await provider.saveClientInformation(client);
205
+ }
206
+ const { verifier, challenge } = await pkce();
207
+ const state = randomState();
208
+ await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));
209
+ const url = new URL(
210
+ metadata?.authorization_endpoint ?? new URL("/authorize", info.authorizationServerUrl)
211
+ );
212
+ if (!secureUrl(url)) throw new Error("The authorization server's sign-in address isn't https.");
213
+ url.searchParams.set("response_type", "code");
214
+ url.searchParams.set("client_id", client.client_id);
215
+ url.searchParams.set("code_challenge", challenge);
216
+ url.searchParams.set("code_challenge_method", "S256");
217
+ url.searchParams.set("redirect_uri", provider.redirectUrl);
218
+ url.searchParams.set("state", state);
219
+ if (scope) url.searchParams.set("scope", scope);
220
+ if (scope?.split(/\s+/).includes("offline_access")) url.searchParams.set("prompt", "consent");
221
+ const resource = selectResource(server.config.url, info.resourceMetadata);
222
+ if (resource) url.searchParams.set("resource", resource);
223
+ return { url: url.href, state };
224
+ }
225
+ async function finishSignIn(provider, server, answer, fetch) {
226
+ let callback;
227
+ try {
228
+ callback = JSON.parse(answer);
229
+ } catch {
230
+ throw new Error("The sign-in answer isn't a callback.");
231
+ }
232
+ const expected = (await provider.load()).oauthState;
233
+ if (!expected || callback.state !== expected)
234
+ throw new Error("The sign-in answer doesn't match this sign-in. Try again.");
235
+ if (callback.error)
236
+ throw new Error(
237
+ `Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`
238
+ );
239
+ if (!callback.code) throw new Error("The sign-in answer has no authorization code.");
240
+ const configured = server.config.oauth?.authServerMetadataUrl;
241
+ await authorizeMcp(provider, {
242
+ serverUrl: server.config.url,
243
+ authorizationCode: callback.code,
244
+ ...callback.iss ? { iss: callback.iss } : {},
245
+ ...server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {},
246
+ ...configured ? { authorizationServerMetadataUrl: new URL(configured) } : {},
247
+ fetch
248
+ });
249
+ await provider.update((state) => {
250
+ const next = { ...state };
251
+ delete next.oauthState;
252
+ delete next.codeVerifier;
253
+ return next;
254
+ });
255
+ }
256
+ export {
257
+ CLIENT_NAME,
258
+ StoredOAuth,
259
+ authProvider,
260
+ beginSignIn,
261
+ finishSignIn,
262
+ pkce,
263
+ randomState,
264
+ secureUrl,
265
+ sentence,
266
+ signInRequired
267
+ };
268
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/mcp/oauth.ts"],
4
+ "sourcesContent": ["import type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { AuthProvider, McpFetch, UnauthorizedContext } from '@earendil-works/pi-mcp';\nimport {\n authorizeMcp,\n discoverOAuthServerInfo,\n McpOAuthAuthorizationRequiredError,\n type McpOAuthState,\n type OAuthChallenge,\n parseWwwAuthenticate,\n registerClient,\n selectResource,\n stepUpScope,\n} from '@earendil-works/pi-mcp/oauth';\nimport type { McpSignIn } from '../services.ts';\nimport type { McpServerConfig } from './config.ts';\nimport { credentialKey } from './credentials.ts';\n\ntype Provider = Parameters<typeof authorizeMcp>[0];\ntype Tokens = Awaited<ReturnType<Provider['tokens']>>;\ntype Client = Awaited<ReturnType<Provider['clientInformation']>>;\ntype Discovery = NonNullable<McpOAuthState['discovery']>;\n\nexport const CLIENT_NAME = 'SLICC';\n\nexport function sentence(text: string): string {\n const trimmed = text.trim();\n const lowered = /^[A-Z][a-z]/.test(trimmed)\n ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}`\n : trimmed;\n return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;\n}\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function secureUrl(url: URL): boolean {\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nexport function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError {\n const error = new McpOAuthAuthorizationRequiredError();\n if (cause !== undefined) error.cause = cause;\n return error;\n}\n\nexport function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider {\n const configured = server.config.oauth?.authServerMetadataUrl;\n let inFlight: Promise<void> | undefined;\n const authorize = async (\n context: UnauthorizedContext,\n step: boolean,\n challenge: OAuthChallenge\n ) => {\n const granted = step ? await provider.tokens() : undefined;\n try {\n await authorizeMcp(provider, {\n serverUrl: context.serverUrl,\n ...(challenge.resourceMetadataUrl\n ? { resourceMetadataUrl: challenge.resourceMetadataUrl }\n : {}),\n ...(step\n ? { scope: stepUpScope(granted?.scope, challenge.scope), skipRefresh: true }\n : challenge.scope\n ? { scope: challenge.scope }\n : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch: context.fetch,\n });\n } catch (error) {\n throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);\n }\n };\n return {\n token: async () => (await provider.tokens())?.access_token,\n async onUnauthorized(context) {\n const challenge = parseWwwAuthenticate(context.response.headers.get('www-authenticate'));\n const step = challenge.error === 'insufficient_scope';\n if (!step && !inFlight && context.token !== undefined) {\n const current = (await provider.tokens())?.access_token;\n if (current !== undefined && current !== context.token) return;\n }\n inFlight ??= authorize(context, step, challenge).finally(() => {\n inFlight = undefined;\n });\n await inFlight;\n },\n };\n}\n\nconst bytesToBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nconst random = (length: number) => crypto.getRandomValues(new Uint8Array(length));\n\nexport async function pkce(): Promise<{ verifier: string; challenge: string }> {\n const verifier = bytesToBase64Url(random(32));\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };\n}\n\nexport function randomState(): string {\n return [...random(32)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport interface OAuthServer {\n name: string;\n config: McpServerConfig;\n}\n\nexport class StoredOAuth implements Provider {\n readonly redirectUrl: string;\n readonly clientMetadata: Provider['clientMetadata'];\n readonly #credentials: CredentialStore;\n readonly #server: OAuthServer;\n #writes: Promise<unknown> = Promise.resolve();\n\n constructor(server: OAuthServer, credentials: CredentialStore, redirectUrl: string) {\n this.#server = server;\n this.#credentials = credentials;\n this.redirectUrl = redirectUrl;\n this.clientMetadata = {\n client_name: server.config.oauth?.clientName ?? CLIENT_NAME,\n redirect_uris: [redirectUrl],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n };\n }\n\n get serverUrl(): string {\n return String(new URL(this.#server.config.url));\n }\n\n async #key(): Promise<string> {\n return credentialKey(this.#server.name, this.#server.config.url);\n }\n\n async load(): Promise<McpOAuthState> {\n await this.#writes;\n const stored = await this.#credentials.read(await this.#key());\n const state =\n stored?.type === 'oauth' ? (stored.state as McpOAuthState | undefined) : undefined;\n return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };\n }\n\n async update(change: (state: McpOAuthState) => McpOAuthState): Promise<void> {\n const write = this.#writes.then(async () => {\n const key = await this.#key();\n await this.#credentials.modify(key, async (current) => {\n const before =\n current?.type === 'oauth' ? (current.state as McpOAuthState | undefined) : undefined;\n const next = change(\n before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl }\n );\n return {\n type: 'oauth',\n access: next.tokens?.access_token ?? '',\n refresh: next.tokens?.refresh_token ?? '',\n expires: next.tokensExpireAt ?? 0,\n state: next,\n };\n });\n });\n this.#writes = write.catch(() => undefined);\n await write;\n }\n\n async signedIn(): Promise<boolean> {\n return (await this.load()).tokens !== undefined;\n }\n\n state(): string {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async clientInformation(): Promise<Client> {\n const configured = this.#server.config.oauth?.clientId;\n return configured ? { client_id: configured } : (await this.load()).clientInformation;\n }\n\n async saveClientInformation(information: NonNullable<Client>): Promise<void> {\n if (this.#server.config.oauth?.clientId) return;\n await this.update((state) => ({ ...state, clientInformation: information }));\n }\n\n async tokens(): Promise<Tokens> {\n return (await this.load()).tokens;\n }\n\n async saveTokens(tokens: NonNullable<Tokens>): Promise<void> {\n const expires =\n tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;\n await this.update((state) => {\n const next: McpOAuthState = { ...state, tokens };\n if (expires === undefined) delete next.tokensExpireAt;\n else next.tokensExpireAt = expires;\n return next;\n });\n }\n\n redirectToAuthorization(): void {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.update((state) => ({ ...state, codeVerifier: verifier }));\n }\n\n async codeVerifier(): Promise<string> {\n const verifier = (await this.load()).codeVerifier;\n if (!verifier) throw new Error('No OAuth PKCE code verifier is stored');\n return verifier;\n }\n\n async invalidateCredentials(\n kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'\n ): Promise<void> {\n await this.update((state) => {\n const next = { ...state };\n if (kind === 'all' || kind === 'client') delete next.clientInformation;\n if (kind === 'all' || kind === 'tokens') {\n delete next.tokens;\n delete next.tokensExpireAt;\n }\n if (kind === 'all' || kind === 'verifier') delete next.codeVerifier;\n if (kind === 'all' || kind === 'discovery') delete next.discovery;\n if (kind === 'all') delete next.oauthState;\n return next;\n });\n }\n\n async saveDiscoveryState(discovery: Discovery): Promise<void> {\n await this.update((state) => ({ ...state, discovery }));\n }\n\n async discoveryState(): Promise<Discovery | undefined> {\n return (await this.load()).discovery;\n }\n\n async forget(): Promise<void> {\n await this.#writes;\n await this.#credentials.delete(await this.#key());\n }\n}\n\nexport type { McpSignIn } from '../services.ts';\n\nexport async function beginSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n options: { fetch: McpFetch; resourceMetadataUrl?: URL }\n): Promise<McpSignIn> {\n const configured = server.config.oauth?.authServerMetadataUrl;\n const info = await discoverOAuthServerInfo(server.config.url, {\n fetch: options.fetch,\n ...(options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n });\n if (!configured) await provider.saveDiscoveryState(info);\n const metadata = info.authorizationServerMetadata;\n if (metadata && !metadata.response_types_supported.includes('code'))\n throw new Error(\"The authorization server doesn't support authorization codes.\");\n if (\n metadata?.code_challenge_methods_supported &&\n !metadata.code_challenge_methods_supported.includes('S256')\n )\n throw new Error(\"The authorization server doesn't support PKCE S256.\");\n const scope =\n server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(' ') || undefined;\n let client = await provider.clientInformation();\n if (!client) {\n client = await registerClient(info.authorizationServerUrl, {\n ...(metadata ? { metadata } : {}),\n clientMetadata: provider.clientMetadata,\n ...(scope ? { scope } : {}),\n fetch: options.fetch,\n });\n await provider.saveClientInformation(client);\n }\n const { verifier, challenge } = await pkce();\n const state = randomState();\n await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));\n const url = new URL(\n metadata?.authorization_endpoint ?? new URL('/authorize', info.authorizationServerUrl)\n );\n if (!secureUrl(url)) throw new Error(\"The authorization server's sign-in address isn't https.\");\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', client.client_id);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('redirect_uri', provider.redirectUrl);\n url.searchParams.set('state', state);\n if (scope) url.searchParams.set('scope', scope);\n if (scope?.split(/\\s+/).includes('offline_access')) url.searchParams.set('prompt', 'consent');\n const resource = selectResource(server.config.url, info.resourceMetadata);\n if (resource) url.searchParams.set('resource', resource);\n return { url: url.href, state };\n}\n\nexport interface Callback {\n code?: string;\n state?: string;\n iss?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function finishSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n answer: string,\n fetch: McpFetch\n): Promise<void> {\n let callback: Callback;\n try {\n callback = JSON.parse(answer) as Callback;\n } catch {\n throw new Error(\"The sign-in answer isn't a callback.\");\n }\n const expected = (await provider.load()).oauthState;\n if (!expected || callback.state !== expected)\n throw new Error(\"The sign-in answer doesn't match this sign-in. Try again.\");\n if (callback.error)\n throw new Error(\n `Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`\n );\n if (!callback.code) throw new Error('The sign-in answer has no authorization code.');\n const configured = server.config.oauth?.authServerMetadataUrl;\n await authorizeMcp(provider, {\n serverUrl: server.config.url,\n authorizationCode: callback.code,\n ...(callback.iss ? { iss: callback.iss } : {}),\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch,\n });\n await provider.update((state) => {\n const next = { ...state };\n delete next.oauthState;\n delete next.codeVerifier;\n return next;\n });\n}\n"],
5
+ "mappings": "AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,qBAAqB;AAOvB,MAAM,cAAc;AAEpB,SAAS,SAAS,MAAsB;AAC7C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,UAAU,cAAc,KAAK,OAAO,IACtC,GAAG,QAAQ,CAAC,GAAG,YAAY,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,KAC/C;AACJ,SAAO,SAAS,KAAK,OAAO,IAAI,UAAU,GAAG,OAAO;AACtD;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,UAAU,KAAmB;AAC3C,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEO,SAAS,eAAe,OAAqD;AAClF,QAAM,QAAQ,IAAI,mCAAmC;AACrD,MAAI,UAAU,OAAW,OAAM,QAAQ;AACvC,SAAO;AACT;AAEO,SAAS,aAAa,UAAuB,QAAmC;AACrF,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,MAAI;AACJ,QAAM,YAAY,OAChB,SACA,MACA,cACG;AACH,UAAM,UAAU,OAAO,MAAM,SAAS,OAAO,IAAI;AACjD,QAAI;AACF,YAAM,aAAa,UAAU;AAAA,QAC3B,WAAW,QAAQ;AAAA,QACnB,GAAI,UAAU,sBACV,EAAE,qBAAqB,UAAU,oBAAoB,IACrD,CAAC;AAAA,QACL,GAAI,OACA,EAAE,OAAO,YAAY,SAAS,OAAO,UAAU,KAAK,GAAG,aAAa,KAAK,IACzE,UAAU,QACR,EAAE,OAAO,UAAU,MAAM,IACzB,CAAC;AAAA,QACP,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,QAC5E,OAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,iBAAiB,qCAAqC,QAAQ,eAAe,KAAK;AAAA,IAC1F;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO,aAAa,MAAM,SAAS,OAAO,IAAI;AAAA,IAC9C,MAAM,eAAe,SAAS;AAC5B,YAAM,YAAY,qBAAqB,QAAQ,SAAS,QAAQ,IAAI,kBAAkB,CAAC;AACvF,YAAM,OAAO,UAAU,UAAU;AACjC,UAAI,CAAC,QAAQ,CAAC,YAAY,QAAQ,UAAU,QAAW;AACrD,cAAM,WAAW,MAAM,SAAS,OAAO,IAAI;AAC3C,YAAI,YAAY,UAAa,YAAY,QAAQ,MAAO;AAAA,MAC1D;AACA,mBAAa,UAAU,SAAS,MAAM,SAAS,EAAE,QAAQ,MAAM;AAC7D,mBAAW;AAAA,MACb,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,MAAM,mBAAmB,CAAC,UACxB,KAAK,OAAO,aAAa,GAAG,KAAK,CAAC,EAC/B,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AAEtB,MAAM,SAAS,CAAC,WAAmB,OAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AAEhF,eAAsB,OAAyD;AAC7E,QAAM,WAAW,iBAAiB,OAAO,EAAE,CAAC;AAC5C,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AACvF,SAAO,EAAE,UAAU,WAAW,iBAAiB,IAAI,WAAW,MAAM,CAAC,EAAE;AACzE;AAEO,SAAS,cAAsB;AACpC,SAAO,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAClF;AAOO,MAAM,YAAgC;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,UAA4B,QAAQ,QAAQ;AAAA,EAE5C,YAAY,QAAqB,aAA8B,aAAqB;AAClF,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,iBAAiB;AAAA,MACpB,aAAa,OAAO,OAAO,OAAO,cAAc;AAAA,MAChD,eAAe,CAAC,WAAW;AAAA,MAC3B,aAAa,CAAC,sBAAsB,eAAe;AAAA,MACnD,gBAAgB,CAAC,MAAM;AAAA,MACvB,4BAA4B;AAAA,MAC5B,GAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,OAAO,IAAI,IAAI,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,cAAc,KAAK,QAAQ,MAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,EACjE;AAAA,EAEA,MAAM,OAA+B;AACnC,UAAM,KAAK;AACX,UAAM,SAAS,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,KAAK,CAAC;AAC7D,UAAM,QACJ,QAAQ,SAAS,UAAW,OAAO,QAAsC;AAC3E,WAAO,OAAO,cAAc,KAAK,YAAY,QAAQ,EAAE,WAAW,KAAK,UAAU;AAAA,EACnF;AAAA,EAEA,MAAM,OAAO,QAAgE;AAC3E,UAAM,QAAQ,KAAK,QAAQ,KAAK,YAAY;AAC1C,YAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,YAAM,KAAK,aAAa,OAAO,KAAK,OAAO,YAAY;AACrD,cAAM,SACJ,SAAS,SAAS,UAAW,QAAQ,QAAsC;AAC7E,cAAM,OAAO;AAAA,UACX,QAAQ,cAAc,KAAK,YAAY,SAAS,EAAE,WAAW,KAAK,UAAU;AAAA,QAC9E;AACA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ,KAAK,QAAQ,gBAAgB;AAAA,UACrC,SAAS,KAAK,QAAQ,iBAAiB;AAAA,UACvC,SAAS,KAAK,kBAAkB;AAAA,UAChC,OAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,SAAK,UAAU,MAAM,MAAM,MAAM,MAAS;AAC1C,UAAM;AAAA,EACR;AAAA,EAEA,MAAM,WAA6B;AACjC,YAAQ,MAAM,KAAK,KAAK,GAAG,WAAW;AAAA,EACxC;AAAA,EAEA,QAAgB;AACd,UAAM,IAAI,mCAAmC;AAAA,EAC/C;AAAA,EAEA,MAAM,oBAAqC;AACzC,UAAM,aAAa,KAAK,QAAQ,OAAO,OAAO;AAC9C,WAAO,aAAa,EAAE,WAAW,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG;AAAA,EACtE;AAAA,EAEA,MAAM,sBAAsB,aAAiD;AAC3E,QAAI,KAAK,QAAQ,OAAO,OAAO,SAAU;AACzC,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,mBAAmB,YAAY,EAAE;AAAA,EAC7E;AAAA,EAEA,MAAM,SAA0B;AAC9B,YAAQ,MAAM,KAAK,KAAK,GAAG;AAAA,EAC7B;AAAA,EAEA,MAAM,WAAW,QAA4C;AAC3D,UAAM,UACJ,OAAO,eAAe,SAAY,SAAY,KAAK,IAAI,IAAI,OAAO,aAAa;AACjF,UAAM,KAAK,OAAO,CAAC,UAAU;AAC3B,YAAM,OAAsB,EAAE,GAAG,OAAO,OAAO;AAC/C,UAAI,YAAY,OAAW,QAAO,KAAK;AAAA,UAClC,MAAK,iBAAiB;AAC3B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,0BAAgC;AAC9B,UAAM,IAAI,mCAAmC;AAAA,EAC/C;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,cAAc,SAAS,EAAE;AAAA,EACrE;AAAA,EAEA,MAAM,eAAgC;AACpC,UAAM,YAAY,MAAM,KAAK,KAAK,GAAG;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,uCAAuC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,sBACJ,MACe;AACf,UAAM,KAAK,OAAO,CAAC,UAAU;AAC3B,YAAM,OAAO,EAAE,GAAG,MAAM;AACxB,UAAI,SAAS,SAAS,SAAS,SAAU,QAAO,KAAK;AACrD,UAAI,SAAS,SAAS,SAAS,UAAU;AACvC,eAAO,KAAK;AACZ,eAAO,KAAK;AAAA,MACd;AACA,UAAI,SAAS,SAAS,SAAS,WAAY,QAAO,KAAK;AACvD,UAAI,SAAS,SAAS,SAAS,YAAa,QAAO,KAAK;AACxD,UAAI,SAAS,MAAO,QAAO,KAAK;AAChC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,mBAAmB,WAAqC;AAC5D,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,UAAU,EAAE;AAAA,EACxD;AAAA,EAEA,MAAM,iBAAiD;AACrD,YAAQ,MAAM,KAAK,KAAK,GAAG;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAwB;AAC5B,UAAM,KAAK;AACX,UAAM,KAAK,aAAa,OAAO,MAAM,KAAK,KAAK,CAAC;AAAA,EAClD;AACF;AAIA,eAAsB,YACpB,UACA,QACA,SACoB;AACpB,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,QAAM,OAAO,MAAM,wBAAwB,OAAO,OAAO,KAAK;AAAA,IAC5D,OAAO,QAAQ;AAAA,IACf,GAAI,QAAQ,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC1F,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,EAC9E,CAAC;AACD,MAAI,CAAC,WAAY,OAAM,SAAS,mBAAmB,IAAI;AACvD,QAAM,WAAW,KAAK;AACtB,MAAI,YAAY,CAAC,SAAS,yBAAyB,SAAS,MAAM;AAChE,UAAM,IAAI,MAAM,+DAA+D;AACjF,MACE,UAAU,oCACV,CAAC,SAAS,iCAAiC,SAAS,MAAM;AAE1D,UAAM,IAAI,MAAM,qDAAqD;AACvE,QAAM,QACJ,OAAO,OAAO,OAAO,SAAS,KAAK,kBAAkB,kBAAkB,KAAK,GAAG,KAAK;AACtF,MAAI,SAAS,MAAM,SAAS,kBAAkB;AAC9C,MAAI,CAAC,QAAQ;AACX,aAAS,MAAM,eAAe,KAAK,wBAAwB;AAAA,MACzD,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/B,gBAAgB,SAAS;AAAA,MACzB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,MACzB,OAAO,QAAQ;AAAA,IACjB,CAAC;AACD,UAAM,SAAS,sBAAsB,MAAM;AAAA,EAC7C;AACA,QAAM,EAAE,UAAU,UAAU,IAAI,MAAM,KAAK;AAC3C,QAAM,QAAQ,YAAY;AAC1B,QAAM,SAAS,OAAO,CAAC,aAAa,EAAE,GAAG,SAAS,cAAc,UAAU,YAAY,MAAM,EAAE;AAC9F,QAAM,MAAM,IAAI;AAAA,IACd,UAAU,0BAA0B,IAAI,IAAI,cAAc,KAAK,sBAAsB;AAAA,EACvF;AACA,MAAI,CAAC,UAAU,GAAG,EAAG,OAAM,IAAI,MAAM,yDAAyD;AAC9F,MAAI,aAAa,IAAI,iBAAiB,MAAM;AAC5C,MAAI,aAAa,IAAI,aAAa,OAAO,SAAS;AAClD,MAAI,aAAa,IAAI,kBAAkB,SAAS;AAChD,MAAI,aAAa,IAAI,yBAAyB,MAAM;AACpD,MAAI,aAAa,IAAI,gBAAgB,SAAS,WAAW;AACzD,MAAI,aAAa,IAAI,SAAS,KAAK;AACnC,MAAI,MAAO,KAAI,aAAa,IAAI,SAAS,KAAK;AAC9C,MAAI,OAAO,MAAM,KAAK,EAAE,SAAS,gBAAgB,EAAG,KAAI,aAAa,IAAI,UAAU,SAAS;AAC5F,QAAM,WAAW,eAAe,OAAO,OAAO,KAAK,KAAK,gBAAgB;AACxE,MAAI,SAAU,KAAI,aAAa,IAAI,YAAY,QAAQ;AACvD,SAAO,EAAE,KAAK,IAAI,MAAM,MAAM;AAChC;AAUA,eAAsB,aACpB,UACA,QACA,QACA,OACe;AACf,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,MAAM;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,QAAM,YAAY,MAAM,SAAS,KAAK,GAAG;AACzC,MAAI,CAAC,YAAY,SAAS,UAAU;AAClC,UAAM,IAAI,MAAM,2DAA2D;AAC7E,MAAI,SAAS;AACX,UAAM,IAAI;AAAA,MACR,mBAAmB,SAAS,SAAS,mBAAmB,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,IACnF;AACF,MAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,+CAA+C;AACnF,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,QAAM,aAAa,UAAU;AAAA,IAC3B,WAAW,OAAO,OAAO;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,GAAI,SAAS,MAAM,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC;AAAA,IAC5C,GAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IACzE,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,IAC5E;AAAA,EACF,CAAC;AACD,QAAM,SAAS,OAAO,CAAC,UAAU;AAC/B,UAAM,OAAO,EAAE,GAAG,MAAM;AACxB,WAAO,KAAK;AACZ,WAAO,KAAK;AACZ,WAAO;AAAA,EACT,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -1,12 +1,14 @@
1
1
  import { StreamableHttpTransport } from "@earendil-works/pi-mcp";
2
+ import { mcpAccounts } from "./accounts.js";
2
3
  import { McpCatalog } from "./catalog.js";
3
4
  import {
4
5
  parseMcpConfig,
5
- sameConfig
6
+ sameConfig,
7
+ usesOAuth
6
8
  } from "./config.js";
7
9
  import { McpServerConnection } from "./connection.js";
8
- import { credentialKey, resolveHeaders, scrub, secretNames } from "./credentials.js";
9
- import { MCP_ACCOUNT } from "./label.js";
10
+ import { resolveHeaders, scrub, secretNames } from "./credentials.js";
11
+ import { authProvider as oauthAuth, StoredOAuth } from "./oauth.js";
10
12
  import { patchSse } from "./sse.js";
11
13
  const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;
12
14
  function configPath(home) {
@@ -49,7 +51,7 @@ ${connection.missing.join(",")}`
49
51
  if (connection.state === "needs-auth")
50
52
  return {
51
53
  ...base,
52
- title: `MCP server "${name}" rejected its token`,
54
+ title: secretNames(connection.entry.config).length ? `MCP server "${name}" rejected its token` : `MCP server "${name}" needs sign-in`,
53
55
  text: connection.signInMessage(),
54
56
  severity: "warn",
55
57
  eventId: `${name}
@@ -104,52 +106,6 @@ function serverLog(options, context) {
104
106
  settled: () => logging
105
107
  };
106
108
  }
107
- function mcpAccounts(catalog, credentials, restart, listeners) {
108
- const find = (id) => {
109
- const connection = catalog.connections.get(id.slice(MCP_ACCOUNT.length));
110
- if (!id.startsWith(MCP_ACCOUNT) || !connection || !secretNames(connection.entry.config).length)
111
- throw new Error(`${id} is not an MCP server that takes a token`);
112
- return connection;
113
- };
114
- const keyOf = (connection) => credentialKey(connection.name, connection.entry.config.url);
115
- return {
116
- async accounts() {
117
- const rows = [];
118
- for (const connection of catalog.connections.values()) {
119
- if (!secretNames(connection.entry.config).length) continue;
120
- const stored = await credentials.read(await keyOf(connection)) !== void 0;
121
- rows.push({
122
- id: `${MCP_ACCOUNT}${connection.name}`,
123
- provider: connection.name,
124
- identity: new URL(connection.entry.config.url).host,
125
- status: connection.state === "needs-auth" ? "expired" : stored ? "connected" : "disconnected",
126
- auth: "api-key",
127
- needs: null,
128
- group: "mcp",
129
- secret: "token"
130
- });
131
- }
132
- return rows;
133
- },
134
- async connect(id, secret) {
135
- const connection = find(id);
136
- await credentials.modify(await keyOf(connection), async () => ({
137
- type: "api_key",
138
- key: secret
139
- }));
140
- restart(connection);
141
- },
142
- async disconnect(id) {
143
- const connection = find(id);
144
- await credentials.delete(await keyOf(connection));
145
- restart(connection);
146
- },
147
- onChange(listener) {
148
- listeners.add(listener);
149
- return () => listeners.delete(listener);
150
- }
151
- };
152
- }
153
109
  async function attachMcp(catalog, options, context) {
154
110
  const { env, home } = options;
155
111
  catalog.codemodeOn = options.codemode;
@@ -166,15 +122,21 @@ async function attachMcp(catalog, options, context) {
166
122
  let initial = /* @__PURE__ */ new Set();
167
123
  let first = true;
168
124
  const log = serverLog(options, context);
169
- const make = (url, headers) => new StreamableHttpTransport({
170
- url,
171
- headers,
172
- fetch: (input, init) => options.fetch(input, init)
173
- });
125
+ const fetcher = (input, init) => options.fetch(input, init);
126
+ const oauth = (entry) => new StoredOAuth(entry, options.credentials, options.redirect);
174
127
  const transport = async (entry) => {
175
128
  const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);
176
129
  if (!resolved.ok) return { missing: resolved.missing };
177
- return { transport: make(entry.config.url, resolved.headers), secrets: resolved.secrets };
130
+ const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : void 0;
131
+ return {
132
+ transport: new StreamableHttpTransport({
133
+ url: entry.config.url,
134
+ headers: resolved.headers,
135
+ fetch: fetcher,
136
+ ...authProvider ? { authProvider } : {}
137
+ }),
138
+ secrets: resolved.secrets
139
+ };
178
140
  };
179
141
  const open = (entry) => {
180
142
  const connection = new McpServerConnection({
@@ -225,13 +187,17 @@ async function attachMcp(catalog, options, context) {
225
187
  const watched = await env.watch([{ path: configPath(home) }], schedule, context);
226
188
  const watcher = watched.ok ? watched.value : void 0;
227
189
  const restart = (connection) => {
228
- catalog.connections.delete(connection.name);
229
- void connection.close();
230
190
  open(connection.entry);
191
+ void connection.close();
231
192
  catalog.rebuild();
232
193
  };
233
194
  return {
234
- ...mcpAccounts(catalog, options.credentials, restart, listeners),
195
+ ...mcpAccounts(
196
+ catalog,
197
+ { credentials: options.credentials, oauth, fetch: fetcher },
198
+ restart,
199
+ listeners
200
+ ),
235
201
  status: () => [...catalog.connections.values()].map((connection) => ({
236
202
  name: connection.name,
237
203
  state: connection.state,
@@ -262,7 +228,6 @@ export {
262
228
  connectedLick,
263
229
  logLine,
264
230
  logPath,
265
- mcpAccounts,
266
231
  problemLick,
267
232
  startMcp,
268
233
  stateLick
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/mcp/runtime.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport type { AccountState } from '../services.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { credentialKey, resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { MCP_ACCOUNT } from './label.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport interface McpAccounts {\n accounts(): Promise<AccountState[]>;\n connect(id: string, secret: string): Promise<void>;\n disconnect(id: string): Promise<void>;\n onChange(listener: () => void): () => void;\n}\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: `MCP server \"${name}\" rejected its token`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Run slicc-node, slicc-swift or the SLICC extension, which fetch without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport function mcpAccounts(\n catalog: McpCatalog,\n credentials: CredentialStore,\n restart: (connection: McpServerConnection) => void,\n listeners: Set<() => void>\n): McpAccounts {\n const find = (id: string) => {\n const connection = catalog.connections.get(id.slice(MCP_ACCOUNT.length));\n if (!id.startsWith(MCP_ACCOUNT) || !connection || !secretNames(connection.entry.config).length)\n throw new Error(`${id} is not an MCP server that takes a token`);\n return connection;\n };\n const keyOf = (connection: McpServerConnection) =>\n credentialKey(connection.name, connection.entry.config.url);\n return {\n async accounts() {\n const rows: AccountState[] = [];\n for (const connection of catalog.connections.values()) {\n if (!secretNames(connection.entry.config).length) continue;\n const stored = (await credentials.read(await keyOf(connection))) !== undefined;\n rows.push({\n id: `${MCP_ACCOUNT}${connection.name}`,\n provider: connection.name,\n identity: new URL(connection.entry.config.url).host,\n status:\n connection.state === 'needs-auth' ? 'expired' : stored ? 'connected' : 'disconnected',\n auth: 'api-key',\n needs: null,\n group: 'mcp',\n secret: 'token',\n });\n }\n return rows;\n },\n async connect(id, secret) {\n const connection = find(id);\n await credentials.modify(await keyOf(connection), async () => ({\n type: 'api_key',\n key: secret,\n }));\n restart(connection);\n },\n async disconnect(id) {\n const connection = find(id);\n await credentials.delete(await keyOf(connection));\n restart(connection);\n },\n onChange(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const make = (url: string, headers: Record<string, string>) =>\n new StreamableHttpTransport({\n url,\n headers,\n fetch: (input, init) => options.fetch(input, init),\n });\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n return { transport: make(entry.config.url, resolved.headers), secrets: resolved.secrets };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n catalog.connections.delete(connection.name);\n void connection.close();\n open(connection.entry);\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(catalog, options.credentials, restart, listeners),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport { type McpAccounts, mcpAccounts } from './accounts.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n usesOAuth,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { authProvider as oauthAuth, StoredOAuth } from './oauth.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport function configPath(home: string): string {\n return `${home}/.pi/agent/mcp.json`;\n}\n\nexport function logPath(home: string): string {\n return `${home}/.pi/agent/mcp.log`;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n redirect: string;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport type { McpAccounts } from './accounts.ts';\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string): LickEvent {\n const file = `~${configPath(home).slice(home.length)}`;\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: secretNames(connection.entry.config).length\n ? `MCP server \"${name}\" rejected its token`\n : `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Run slicc-node, slicc-swift or the SLICC extension, which fetch without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const fetcher = (input: string | URL, init?: RequestInit) => options.fetch(input, init);\n const oauth = (entry: McpServerEntry) =>\n new StoredOAuth(entry, options.credentials, options.redirect);\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : undefined;\n return {\n transport: new StreamableHttpTransport({\n url: entry.config.url,\n headers: resolved.headers,\n fetch: fetcher,\n ...(authProvider ? { authProvider } : {}),\n }),\n secrets: resolved.secrets,\n };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems) await report(problemLick(problem, home), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch([{ path: configPath(home) }], schedule, context);\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n open(connection.entry);\n void connection.close();\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(\n catalog,\n { credentials: options.credentials, oauth, fetch: fetcher },\n restart,\n listeners\n ),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }