@executor-js/plugin-mcp 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +19 -15
  2. package/dist/api/group.d.ts +113 -144
  3. package/dist/api/handlers.d.ts +2 -2
  4. package/dist/chunk-DJANY5EU.js +1325 -0
  5. package/dist/chunk-DJANY5EU.js.map +1 -0
  6. package/dist/core.js +8 -52
  7. package/dist/core.js.map +1 -1
  8. package/dist/index.js +2 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/promise.d.ts +2 -6
  11. package/dist/react/AddMcpSource.d.ts +2 -0
  12. package/dist/react/McpSignInButton.d.ts +3 -0
  13. package/dist/react/atoms.d.ts +153 -0
  14. package/dist/react/client.d.ts +437 -3
  15. package/dist/react/index.d.ts +3 -2
  16. package/dist/react/source-plugin.d.ts +13 -1
  17. package/dist/sdk/binding-store.d.ts +79 -25
  18. package/dist/sdk/connection-pool.test.d.ts +1 -0
  19. package/dist/sdk/connection.d.ts +3 -1
  20. package/dist/sdk/cross-user-isolation.test.d.ts +1 -0
  21. package/dist/sdk/errors.d.ts +15 -23
  22. package/dist/sdk/index.d.ts +3 -3
  23. package/dist/sdk/invoke.d.ts +18 -17
  24. package/dist/sdk/manifest.d.ts +1 -0
  25. package/dist/sdk/per-user-auth-isolation.test.d.ts +1 -0
  26. package/dist/sdk/plugin.d.ts +108 -43
  27. package/dist/sdk/probe-shape.d.ts +39 -0
  28. package/dist/sdk/probe-shape.test.d.ts +1 -0
  29. package/dist/sdk/stdio-connector.d.ts +8 -0
  30. package/dist/sdk/stored-source.d.ts +31 -105
  31. package/dist/sdk/types.d.ts +77 -93
  32. package/dist/stdio-connector-KNHLETKM.js +12 -0
  33. package/dist/stdio-connector-KNHLETKM.js.map +1 -0
  34. package/package.json +11 -21
  35. package/dist/chunk-X3JTTDWJ.js +0 -1255
  36. package/dist/chunk-X3JTTDWJ.js.map +0 -1
  37. package/dist/react/McpSourceSummary.d.ts +0 -3
  38. package/dist/sdk/config-file-store.d.ts +0 -10
  39. package/dist/sdk/oauth.d.ts +0 -40
@@ -1,1255 +0,0 @@
1
- // src/sdk/binding-store.ts
2
- import { Effect as Effect2, Schema as Schema5 } from "effect";
3
- import { makeInMemoryScopedKv, scopeKv } from "@executor-js/sdk";
4
-
5
- // src/sdk/types.ts
6
- import { Schema } from "effect";
7
- var McpRemoteTransport = Schema.Literal("streamable-http", "sse", "auto");
8
- var McpTransport = Schema.Literal("streamable-http", "sse", "stdio", "auto");
9
- var McpConnectionAuth = Schema.Union(
10
- Schema.Struct({ kind: Schema.Literal("none") }),
11
- Schema.Struct({
12
- kind: Schema.Literal("header"),
13
- headerName: Schema.String,
14
- secretId: Schema.String,
15
- prefix: Schema.optional(Schema.String)
16
- }),
17
- Schema.Struct({
18
- kind: Schema.Literal("oauth2"),
19
- accessTokenSecretId: Schema.String,
20
- refreshTokenSecretId: Schema.NullOr(Schema.String),
21
- tokenType: Schema.optionalWith(Schema.String, { default: () => "Bearer" }),
22
- expiresAt: Schema.NullOr(Schema.Number),
23
- scope: Schema.NullOr(Schema.String)
24
- })
25
- );
26
- var StringMap = Schema.Record({ key: Schema.String, value: Schema.String });
27
- var McpRemoteSourceData = Schema.Struct({
28
- transport: Schema.Literal("remote"),
29
- /** The MCP server endpoint URL */
30
- endpoint: Schema.String,
31
- /** Transport preference for this remote source */
32
- remoteTransport: Schema.optionalWith(McpRemoteTransport, { default: () => "auto" }),
33
- /** Extra query params appended to the endpoint URL */
34
- queryParams: Schema.optional(StringMap),
35
- /** Extra headers sent on every request */
36
- headers: Schema.optional(StringMap),
37
- /** Auth configuration */
38
- auth: McpConnectionAuth
39
- });
40
- var McpStdioSourceData = Schema.Struct({
41
- transport: Schema.Literal("stdio"),
42
- /** The command to run */
43
- command: Schema.String,
44
- /** Arguments to the command */
45
- args: Schema.optional(Schema.Array(Schema.String)),
46
- /** Environment variables */
47
- env: Schema.optional(StringMap),
48
- /** Working directory */
49
- cwd: Schema.optional(Schema.String)
50
- });
51
- var McpStoredSourceData = Schema.Union(McpRemoteSourceData, McpStdioSourceData);
52
- var McpToolBinding = class extends Schema.Class("McpToolBinding")({
53
- toolId: Schema.String,
54
- toolName: Schema.String,
55
- description: Schema.NullOr(Schema.String),
56
- inputSchema: Schema.optional(Schema.Unknown),
57
- outputSchema: Schema.optional(Schema.Unknown)
58
- }) {
59
- };
60
-
61
- // src/sdk/oauth.ts
62
- import {
63
- auth
64
- } from "@modelcontextprotocol/sdk/client/auth.js";
65
- import { Effect, Schema as Schema3 } from "effect";
66
-
67
- // src/sdk/errors.ts
68
- import { Schema as Schema2 } from "effect";
69
- var McpConnectionError = class extends Schema2.TaggedError()(
70
- "McpConnectionError",
71
- {
72
- transport: Schema2.String,
73
- message: Schema2.String
74
- }
75
- ) {
76
- };
77
- var McpToolDiscoveryError = class extends Schema2.TaggedError()(
78
- "McpToolDiscoveryError",
79
- {
80
- stage: Schema2.Literal("connect", "list_tools"),
81
- message: Schema2.String
82
- }
83
- ) {
84
- };
85
- var McpInvocationError = class extends Schema2.TaggedError()(
86
- "McpInvocationError",
87
- {
88
- toolName: Schema2.String,
89
- message: Schema2.String
90
- }
91
- ) {
92
- };
93
- var McpOAuthError = class extends Schema2.TaggedError()("McpOAuthError", {
94
- message: Schema2.String
95
- }) {
96
- };
97
-
98
- // src/sdk/oauth.ts
99
- var JsonObject = Schema3.Record({ key: Schema3.String, value: Schema3.Unknown });
100
- var McpOAuthDiscoveryState = Schema3.Struct({
101
- resourceMetadataUrl: Schema3.NullOr(Schema3.String),
102
- authorizationServerUrl: Schema3.NullOr(Schema3.String),
103
- resourceMetadata: Schema3.NullOr(JsonObject),
104
- authorizationServerMetadata: Schema3.NullOr(JsonObject),
105
- clientInformation: Schema3.NullOr(JsonObject)
106
- });
107
- var McpOAuthSession = Schema3.Struct({
108
- ...McpOAuthDiscoveryState.fields,
109
- endpoint: Schema3.String,
110
- redirectUrl: Schema3.String,
111
- codeVerifier: Schema3.String
112
- });
113
- var toJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
114
- var CLIENT_METADATA = {
115
- grant_types: ["authorization_code", "refresh_token"],
116
- response_types: ["code"],
117
- token_endpoint_auth_method: "none",
118
- client_name: "Executor"
119
- };
120
- var extractDiscoveryState = (discoveryState, clientInformation) => ({
121
- resourceMetadataUrl: discoveryState?.resourceMetadataUrl ?? null,
122
- authorizationServerUrl: discoveryState?.authorizationServerUrl ?? null,
123
- resourceMetadata: toJsonObject(discoveryState?.resourceMetadata),
124
- authorizationServerMetadata: toJsonObject(discoveryState?.authorizationServerMetadata),
125
- clientInformation: toJsonObject(clientInformation)
126
- });
127
- var callAuth = (provider, opts) => Effect.tryPromise({
128
- try: () => auth(provider, opts),
129
- catch: (cause) => new McpOAuthError({
130
- message: cause instanceof Error ? cause.message : String(cause)
131
- })
132
- });
133
- var startMcpOAuthAuthorization = (input) => Effect.gen(function* () {
134
- let authorizationUrl;
135
- let codeVerifier;
136
- let discoveryState;
137
- let clientInformation;
138
- const provider = {
139
- get redirectUrl() {
140
- return input.redirectUrl;
141
- },
142
- get clientMetadata() {
143
- return { ...CLIENT_METADATA, redirect_uris: [input.redirectUrl] };
144
- },
145
- state: () => input.state,
146
- clientInformation: () => clientInformation,
147
- saveClientInformation: (ci) => {
148
- clientInformation = ci;
149
- },
150
- tokens: () => void 0,
151
- saveTokens: () => void 0,
152
- redirectToAuthorization: (url) => {
153
- authorizationUrl = url;
154
- },
155
- saveCodeVerifier: (cv) => {
156
- codeVerifier = cv;
157
- },
158
- codeVerifier: () => {
159
- if (!codeVerifier) throw new Error("Code verifier not captured");
160
- return codeVerifier;
161
- },
162
- saveDiscoveryState: (s) => {
163
- discoveryState = s;
164
- },
165
- discoveryState: () => discoveryState
166
- };
167
- const result = yield* callAuth(provider, { serverUrl: input.endpoint });
168
- if (result !== "REDIRECT" || !authorizationUrl || !codeVerifier) {
169
- return yield* new McpOAuthError({
170
- message: "OAuth flow did not produce an authorization redirect"
171
- });
172
- }
173
- return {
174
- authorizationUrl: authorizationUrl.toString(),
175
- codeVerifier,
176
- ...extractDiscoveryState(discoveryState, clientInformation)
177
- };
178
- });
179
- var exchangeMcpOAuthCode = (input) => Effect.gen(function* () {
180
- const { session } = input;
181
- let tokens;
182
- let discoveryState = {
183
- authorizationServerUrl: session.authorizationServerUrl ?? new URL("/", session.endpoint).toString(),
184
- resourceMetadataUrl: session.resourceMetadataUrl ?? void 0,
185
- resourceMetadata: session.resourceMetadata,
186
- authorizationServerMetadata: session.authorizationServerMetadata
187
- };
188
- let clientInformation = session.clientInformation;
189
- const provider = {
190
- get redirectUrl() {
191
- return session.redirectUrl;
192
- },
193
- get clientMetadata() {
194
- return { ...CLIENT_METADATA, redirect_uris: [session.redirectUrl] };
195
- },
196
- clientInformation: () => clientInformation,
197
- saveClientInformation: (ci) => {
198
- clientInformation = ci;
199
- },
200
- tokens: () => void 0,
201
- saveTokens: (t) => {
202
- tokens = t;
203
- },
204
- redirectToAuthorization: () => {
205
- throw new Error("Unexpected redirect during code exchange");
206
- },
207
- saveCodeVerifier: () => void 0,
208
- codeVerifier: () => session.codeVerifier,
209
- saveDiscoveryState: (s) => {
210
- discoveryState = s;
211
- },
212
- discoveryState: () => discoveryState
213
- };
214
- const result = yield* callAuth(provider, {
215
- serverUrl: session.endpoint,
216
- authorizationCode: input.code
217
- });
218
- if (result !== "AUTHORIZED" || !tokens) {
219
- return yield* new McpOAuthError({
220
- message: "OAuth exchange did not produce tokens"
221
- });
222
- }
223
- return {
224
- tokens,
225
- ...extractDiscoveryState(discoveryState, clientInformation)
226
- };
227
- });
228
-
229
- // src/sdk/stored-source.ts
230
- import { Schema as Schema4 } from "effect";
231
- var McpStoredSourceSchema = class extends Schema4.Class("McpStoredSource")({
232
- namespace: Schema4.String,
233
- name: Schema4.String,
234
- config: McpStoredSourceData
235
- }) {
236
- };
237
-
238
- // src/sdk/binding-store.ts
239
- var MCP_OAUTH_SESSION_TTL_MS = 15 * 60 * 1e3;
240
- var StoredOAuthSession = Schema5.Struct({
241
- session: McpOAuthSession,
242
- expiresAt: Schema5.Number
243
- });
244
- var encodeOAuthSession = Schema5.encodeSync(Schema5.parseJson(StoredOAuthSession));
245
- var decodeOAuthSession = Schema5.decodeUnknownSync(Schema5.parseJson(StoredOAuthSession));
246
- var encodeSource = Schema5.encodeSync(Schema5.parseJson(McpStoredSourceSchema));
247
- var decodeSource = Schema5.decodeUnknownSync(Schema5.parseJson(McpStoredSourceSchema));
248
- var StoredBindingEntry = Schema5.Struct({
249
- namespace: Schema5.String,
250
- binding: McpToolBinding
251
- });
252
- var encodeBindingEntry = Schema5.encodeSync(Schema5.parseJson(StoredBindingEntry));
253
- var decodeBindingEntry = Schema5.decodeUnknownSync(Schema5.parseJson(StoredBindingEntry));
254
- var makeStore = (bindings, sources, oauthSessions) => ({
255
- // ---- Bindings ----
256
- get: (toolId) => Effect2.gen(function* () {
257
- const raw = yield* bindings.get(toolId);
258
- if (!raw) return null;
259
- const entry = decodeBindingEntry(raw);
260
- return {
261
- binding: entry.binding,
262
- namespace: entry.namespace
263
- };
264
- }),
265
- put: (toolId, namespace, binding) => bindings.set([{ key: toolId, value: encodeBindingEntry({ namespace, binding }) }]),
266
- remove: (toolId) => bindings.delete([toolId]).pipe(Effect2.asVoid),
267
- listByNamespace: (namespace) => Effect2.gen(function* () {
268
- const entries = yield* bindings.list();
269
- const ids = [];
270
- for (const e of entries) {
271
- const entry = decodeBindingEntry(e.value);
272
- if (entry.namespace === namespace) ids.push(e.key);
273
- }
274
- return ids;
275
- }),
276
- removeByNamespace: (namespace) => Effect2.gen(function* () {
277
- const entries = yield* bindings.list();
278
- const ids = [];
279
- for (const e of entries) {
280
- const entry = decodeBindingEntry(e.value);
281
- if (entry.namespace === namespace) ids.push(e.key);
282
- }
283
- if (ids.length > 0) yield* bindings.delete(ids);
284
- return ids;
285
- }),
286
- // ---- Sources (meta + config combined) ----
287
- putSource: (source) => sources.set([{ key: source.namespace, value: encodeSource(source) }]),
288
- removeSource: (namespace) => sources.delete([namespace]).pipe(Effect2.asVoid),
289
- listSources: () => Effect2.gen(function* () {
290
- const entries = yield* sources.list();
291
- return entries.map((e) => decodeSource(e.value));
292
- }),
293
- getSource: (namespace) => Effect2.gen(function* () {
294
- const raw = yield* sources.get(namespace);
295
- if (!raw) return null;
296
- return decodeSource(raw);
297
- }),
298
- getSourceConfig: (namespace) => Effect2.gen(function* () {
299
- const raw = yield* sources.get(namespace);
300
- if (!raw) return null;
301
- return decodeSource(raw).config;
302
- }),
303
- // ---- Pending OAuth sessions (short-lived, between startOAuth and completeOAuth) ----
304
- putOAuthSession: (sessionId, session) => oauthSessions.set([
305
- {
306
- key: sessionId,
307
- value: encodeOAuthSession({
308
- session,
309
- expiresAt: Date.now() + MCP_OAUTH_SESSION_TTL_MS
310
- })
311
- }
312
- ]),
313
- getOAuthSession: (sessionId) => Effect2.gen(function* () {
314
- const raw = yield* oauthSessions.get(sessionId);
315
- if (!raw) return null;
316
- const entry = decodeOAuthSession(raw);
317
- if (entry.expiresAt < Date.now()) {
318
- yield* oauthSessions.delete([sessionId]);
319
- return null;
320
- }
321
- return entry.session;
322
- }),
323
- deleteOAuthSession: (sessionId) => oauthSessions.delete([sessionId]).pipe(Effect2.asVoid)
324
- });
325
- var makeKvBindingStore = (kv, namespace) => makeStore(
326
- scopeKv(kv, `${namespace}.bindings`),
327
- scopeKv(kv, `${namespace}.sources`),
328
- scopeKv(kv, `${namespace}.oauth-sessions`)
329
- );
330
- var makeInMemoryBindingStore = () => makeStore(makeInMemoryScopedKv(), makeInMemoryScopedKv(), makeInMemoryScopedKv());
331
-
332
- // src/sdk/plugin.ts
333
- import { Effect as Effect6, Exit, ScopedCache, Duration, Scope } from "effect";
334
- import {
335
- Source,
336
- SourceDetectionResult,
337
- definePlugin,
338
- ToolId,
339
- SecretId
340
- } from "@executor-js/sdk";
341
-
342
- // src/sdk/connection.ts
343
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
344
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
345
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
346
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
347
- import { Effect as Effect3 } from "effect";
348
- var buildEndpointUrl = (endpoint, queryParams) => {
349
- const url = new URL(endpoint);
350
- for (const [key, value] of Object.entries(queryParams)) {
351
- url.searchParams.set(key, value);
352
- }
353
- return url;
354
- };
355
- var createClient = () => new Client(
356
- { name: "executor-mcp", version: "0.1.0" },
357
- { capabilities: { elicitation: { form: {}, url: {} } } }
358
- );
359
- var connectionFromClient = (client) => ({
360
- client,
361
- close: () => client.close()
362
- });
363
- var connectClient = (input) => Effect3.gen(function* () {
364
- const client = createClient();
365
- const transportInstance = input.createTransport();
366
- yield* Effect3.tryPromise({
367
- try: () => client.connect(transportInstance),
368
- catch: (cause) => new McpConnectionError({
369
- transport: input.transport,
370
- message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
371
- })
372
- });
373
- return connectionFromClient(client);
374
- });
375
- var createMcpConnector = (input) => {
376
- if (input.transport === "stdio") {
377
- const command = input.command.trim();
378
- if (!command) {
379
- return new McpConnectionError({
380
- transport: "stdio",
381
- message: "MCP stdio transport requires a command"
382
- });
383
- }
384
- return connectClient({
385
- transport: "stdio",
386
- createTransport: () => new StdioClientTransport({
387
- command,
388
- args: input.args ? [...input.args] : void 0,
389
- env: input.env ? { ...process.env, ...input.env } : void 0,
390
- cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
391
- })
392
- });
393
- }
394
- const headers = input.headers ?? {};
395
- const remoteTransport = input.remoteTransport ?? "auto";
396
- const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
397
- const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
398
- const connectStreamableHttp = connectClient({
399
- transport: "streamable-http",
400
- createTransport: () => new StreamableHTTPClientTransport(endpoint, {
401
- requestInit,
402
- authProvider: input.authProvider
403
- })
404
- });
405
- const connectSse = connectClient({
406
- transport: "sse",
407
- createTransport: () => new SSEClientTransport(endpoint, {
408
- requestInit,
409
- authProvider: input.authProvider
410
- })
411
- });
412
- if (remoteTransport === "streamable-http") return connectStreamableHttp;
413
- if (remoteTransport === "sse") return connectSse;
414
- return connectStreamableHttp.pipe(Effect3.catchAll(() => connectSse));
415
- };
416
-
417
- // src/sdk/discover.ts
418
- import { Effect as Effect4 } from "effect";
419
-
420
- // src/sdk/manifest.ts
421
- import { Schema as Schema6 } from "effect";
422
- var ListedTool = Schema6.Struct({
423
- name: Schema6.String,
424
- description: Schema6.optional(Schema6.NullOr(Schema6.String)),
425
- inputSchema: Schema6.optional(Schema6.Unknown),
426
- parameters: Schema6.optional(Schema6.Unknown),
427
- outputSchema: Schema6.optional(Schema6.Unknown)
428
- });
429
- var ListToolsResult = Schema6.Struct({
430
- tools: Schema6.Array(ListedTool)
431
- });
432
- var ServerInfo = Schema6.Struct({
433
- name: Schema6.optional(Schema6.String),
434
- version: Schema6.optional(Schema6.String)
435
- });
436
- var decodeListToolsResult = Schema6.decodeUnknownOption(ListToolsResult);
437
- var decodeServerInfo = Schema6.decodeUnknownOption(ServerInfo);
438
- var sanitize = (value) => {
439
- const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
440
- return s || "tool";
441
- };
442
- var uniqueId = (value, seen) => {
443
- const base = sanitize(value);
444
- const n = (seen.get(base) ?? 0) + 1;
445
- seen.set(base, n);
446
- return n === 1 ? base : `${base}_${n}`;
447
- };
448
- var joinToolPath = (namespace, toolId) => namespace?.trim() ? `${namespace}.${toolId}` : toolId;
449
- var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
450
- const seen = /* @__PURE__ */ new Map();
451
- const listed = decodeListToolsResult(listToolsResult).pipe(
452
- (opt) => opt._tag === "Some" ? opt.value.tools : []
453
- );
454
- const server = decodeServerInfo(metadata?.serverInfo).pipe(
455
- (opt) => opt._tag === "Some" ? { name: opt.value.name ?? null, version: opt.value.version ?? null } : null
456
- );
457
- const tools = listed.flatMap((tool) => {
458
- const toolName = tool.name.trim();
459
- if (!toolName) return [];
460
- return [
461
- {
462
- toolId: uniqueId(toolName, seen),
463
- toolName,
464
- description: tool.description ?? null,
465
- inputSchema: tool.inputSchema ?? tool.parameters,
466
- outputSchema: tool.outputSchema
467
- }
468
- ];
469
- });
470
- return { server, tools };
471
- };
472
- var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
473
- var hostnameOf = (url) => {
474
- try {
475
- return new URL(url).hostname;
476
- } catch {
477
- return null;
478
- }
479
- };
480
- var basenameOf = (path) => path.trim().split(/[\\/]/).pop() ?? path.trim();
481
- var deriveMcpNamespace = (input) => {
482
- if (input.name?.trim()) return slugify(input.name) || "mcp";
483
- const fromEndpoint = input.endpoint?.trim() ? hostnameOf(input.endpoint) : null;
484
- if (fromEndpoint) return slugify(fromEndpoint) || "mcp";
485
- if (input.command?.trim()) return slugify(basenameOf(input.command)) || "mcp";
486
- return "mcp";
487
- };
488
-
489
- // src/sdk/discover.ts
490
- var discoverTools = (connector) => Effect4.gen(function* () {
491
- const connection = yield* connector.pipe(
492
- Effect4.mapError(
493
- (err) => new McpToolDiscoveryError({
494
- stage: "connect",
495
- message: `Failed connecting to MCP server: ${err.message}`
496
- })
497
- )
498
- );
499
- const listResult = yield* Effect4.tryPromise({
500
- try: () => connection.client.listTools(),
501
- catch: (cause) => new McpToolDiscoveryError({
502
- stage: "list_tools",
503
- message: `Failed listing MCP tools: ${cause instanceof Error ? cause.message : String(cause)}`
504
- })
505
- });
506
- const manifest = extractManifestFromListToolsResult(listResult, {
507
- serverInfo: connection.client.getServerVersion?.()
508
- });
509
- yield* Effect4.promise(() => connection.close().catch(() => {
510
- }));
511
- return manifest;
512
- });
513
-
514
- // src/sdk/invoke.ts
515
- import { Effect as Effect5, Schema as Schema7 } from "effect";
516
- import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
517
- import {
518
- ToolInvocationResult,
519
- ToolInvocationError,
520
- ToolAnnotations,
521
- ElicitationResponse,
522
- FormElicitation,
523
- UrlElicitation
524
- } from "@executor-js/sdk";
525
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
526
- var makeOAuthProvider = (accessToken, tokenType, refreshToken) => ({
527
- get redirectUrl() {
528
- return "http://localhost/oauth/callback";
529
- },
530
- get clientMetadata() {
531
- return {
532
- redirect_uris: ["http://localhost/oauth/callback"],
533
- grant_types: ["authorization_code", "refresh_token"],
534
- response_types: ["code"],
535
- token_endpoint_auth_method: "none",
536
- client_name: "Executor"
537
- };
538
- },
539
- clientInformation: () => void 0,
540
- saveClientInformation: () => {
541
- },
542
- tokens: async () => ({
543
- access_token: accessToken,
544
- token_type: tokenType,
545
- ...refreshToken ? { refresh_token: refreshToken } : {}
546
- }),
547
- saveTokens: async () => {
548
- },
549
- redirectToAuthorization: async () => {
550
- throw new Error("MCP OAuth re-authorization required");
551
- },
552
- saveCodeVerifier: () => {
553
- },
554
- codeVerifier: () => {
555
- throw new Error("No active PKCE verifier");
556
- },
557
- saveDiscoveryState: () => {
558
- },
559
- discoveryState: () => void 0
560
- });
561
- var McpElicitParams = Schema7.Union(
562
- Schema7.Struct({
563
- mode: Schema7.Literal("url"),
564
- message: Schema7.String,
565
- url: Schema7.String,
566
- elicitationId: Schema7.optional(Schema7.String),
567
- id: Schema7.optional(Schema7.String)
568
- }),
569
- Schema7.Struct({
570
- mode: Schema7.optional(Schema7.Literal("form")),
571
- message: Schema7.String,
572
- requestedSchema: Schema7.Record({ key: Schema7.String, value: Schema7.Unknown })
573
- })
574
- );
575
- var decodeElicitParams = Schema7.decodeUnknownSync(McpElicitParams);
576
- var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitation({
577
- message: params.message,
578
- url: params.url,
579
- elicitationId: params.elicitationId ?? params.id ?? ""
580
- }) : new FormElicitation({
581
- message: params.message,
582
- requestedSchema: params.requestedSchema
583
- });
584
- var installElicitationHandler = (client, toolId, args, handler) => {
585
- client.setRequestHandler(ElicitRequestSchema, async (request) => {
586
- const params = decodeElicitParams(request.params);
587
- const response = await Effect5.runPromise(
588
- handler({ toolId, args, request: toElicitationRequest(params) })
589
- );
590
- return {
591
- action: response.action,
592
- ...response.action === "accept" && response.content ? { content: response.content } : {}
593
- };
594
- });
595
- };
596
- var resolveConnectorInput = (sourceData, secrets, scopeId) => {
597
- if (sourceData.transport === "stdio") {
598
- return Effect5.succeed({
599
- transport: "stdio",
600
- command: sourceData.command,
601
- args: sourceData.args,
602
- env: sourceData.env,
603
- cwd: sourceData.cwd
604
- });
605
- }
606
- return Effect5.gen(function* () {
607
- const headers = { ...sourceData.headers };
608
- let authProvider;
609
- const auth2 = sourceData.auth;
610
- if (auth2.kind === "header") {
611
- const secretValue = yield* secrets.resolve(auth2.secretId, scopeId).pipe(
612
- Effect5.mapError(
613
- () => new ToolInvocationError({
614
- toolId: "",
615
- message: `Failed to resolve secret "${auth2.secretId}" for MCP auth`,
616
- cause: void 0
617
- })
618
- )
619
- );
620
- headers[auth2.headerName] = auth2.prefix ? `${auth2.prefix}${secretValue}` : secretValue;
621
- } else if (auth2.kind === "oauth2") {
622
- const accessToken = yield* secrets.resolve(auth2.accessTokenSecretId, scopeId).pipe(
623
- Effect5.mapError(
624
- () => new ToolInvocationError({
625
- toolId: "",
626
- message: "Failed to resolve OAuth access token for MCP auth",
627
- cause: void 0
628
- })
629
- )
630
- );
631
- let refreshToken;
632
- if (auth2.refreshTokenSecretId) {
633
- refreshToken = yield* secrets.resolve(auth2.refreshTokenSecretId, scopeId).pipe(
634
- Effect5.option,
635
- Effect5.map((o) => o._tag === "Some" ? o.value : void 0)
636
- );
637
- }
638
- authProvider = makeOAuthProvider(accessToken, auth2.tokenType ?? "Bearer", refreshToken);
639
- }
640
- return {
641
- transport: "remote",
642
- endpoint: sourceData.endpoint,
643
- remoteTransport: sourceData.remoteTransport,
644
- queryParams: sourceData.queryParams,
645
- headers: Object.keys(headers).length > 0 ? headers : void 0,
646
- authProvider
647
- };
648
- });
649
- };
650
- var connectionCacheKey = (sourceData) => sourceData.transport === "stdio" ? `stdio:${sourceData.command}` : `remote:${sourceData.endpoint}`;
651
- var resolveElicitationHandler = (options) => options.onElicitation === "accept-all" ? () => Effect5.succeed(new ElicitationResponse({ action: "accept" })) : options.onElicitation;
652
- var useMcpConnection = (connection, toolId, toolName, args, handler) => Effect5.gen(function* () {
653
- installElicitationHandler(connection.client, toolId, args, handler);
654
- return yield* Effect5.tryPromise({
655
- try: () => connection.client.callTool({ name: toolName, arguments: args }),
656
- catch: (cause) => new ToolInvocationError({
657
- toolId,
658
- message: `MCP tool call failed for ${toolName}: ${cause instanceof Error ? cause.message : String(cause)}`,
659
- cause
660
- })
661
- });
662
- }).pipe(Effect5.withSpan(`mcp.callTool.${toolName}`));
663
- var makeMcpInvoker = (opts) => {
664
- const { connectionCache, pendingConnectors } = opts;
665
- return {
666
- resolveAnnotations: () => Effect5.succeed(new ToolAnnotations({ requiresApproval: false })),
667
- invoke: (toolId, args, options) => Effect5.gen(function* () {
668
- const entry = yield* opts.bindingStore.get(toolId);
669
- if (!entry) {
670
- return yield* new ToolInvocationError({
671
- toolId,
672
- message: `No MCP binding found for tool "${toolId}"`,
673
- cause: void 0
674
- });
675
- }
676
- const sourceData = yield* opts.bindingStore.getSourceConfig(entry.namespace);
677
- if (!sourceData) {
678
- return yield* new ToolInvocationError({
679
- toolId,
680
- message: `No MCP source config found for namespace "${entry.namespace}"`,
681
- cause: void 0
682
- });
683
- }
684
- const { binding } = entry;
685
- const cacheKey = connectionCacheKey(sourceData);
686
- const connector = resolveConnectorInput(sourceData, opts.secrets, opts.scopeId).pipe(
687
- Effect5.flatMap((ci) => createMcpConnector(ci)),
688
- Effect5.mapError(
689
- (err) => new McpConnectionError({
690
- transport: "auto",
691
- message: err instanceof Error ? err.message : String(err)
692
- })
693
- )
694
- );
695
- pendingConnectors.set(cacheKey, connector);
696
- const connection = yield* connectionCache.get(cacheKey).pipe(
697
- Effect5.mapError(
698
- (err) => new ToolInvocationError({
699
- toolId,
700
- message: `Failed connecting to MCP server: ${err instanceof Error ? err.message : String(err)}`,
701
- cause: err
702
- })
703
- )
704
- );
705
- const elicitationHandler = resolveElicitationHandler(options);
706
- return yield* useMcpConnection(
707
- connection,
708
- toolId,
709
- binding.toolName,
710
- asRecord(args),
711
- elicitationHandler
712
- ).pipe(
713
- // On failure, invalidate the cached connection and retry once
714
- Effect5.catchAll(
715
- () => Effect5.gen(function* () {
716
- yield* connectionCache.invalidate(cacheKey);
717
- pendingConnectors.set(cacheKey, connector);
718
- const freshConnection = yield* connectionCache.get(cacheKey).pipe(
719
- Effect5.mapError(
720
- (retryErr) => new ToolInvocationError({
721
- toolId,
722
- message: `Failed reconnecting: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
723
- cause: retryErr
724
- })
725
- )
726
- );
727
- return yield* useMcpConnection(
728
- freshConnection,
729
- toolId,
730
- binding.toolName,
731
- asRecord(args),
732
- elicitationHandler
733
- );
734
- })
735
- )
736
- );
737
- }).pipe(
738
- Effect5.scoped,
739
- Effect5.map((callResult) => {
740
- const resultRecord = asRecord(callResult);
741
- const isError = resultRecord.isError === true;
742
- return new ToolInvocationResult({
743
- data: isError ? null : callResult ?? null,
744
- error: isError ? callResult : null
745
- });
746
- }),
747
- Effect5.catchAll((err) => {
748
- if (typeof err === "object" && err !== null && "_tag" in err && err._tag === "ToolInvocationError") {
749
- return Effect5.fail(err);
750
- }
751
- return Effect5.fail(
752
- new ToolInvocationError({
753
- toolId,
754
- message: `MCP invocation failed: ${err instanceof Error ? err.message : String(err)}`,
755
- cause: err
756
- })
757
- );
758
- })
759
- ),
760
- closeConnections: () => Effect5.sync(() => {
761
- pendingConnectors.clear();
762
- }).pipe(Effect5.flatMap(() => connectionCache.invalidateAll))
763
- };
764
- };
765
-
766
- // src/sdk/plugin.ts
767
- var toRegistration = (entry, namespace) => ({
768
- id: ToolId.make(joinToolPath(namespace, entry.toolId)),
769
- pluginKey: "mcp",
770
- sourceId: namespace,
771
- name: entry.toolName,
772
- description: entry.description ?? `MCP tool: ${entry.toolName}`,
773
- inputSchema: entry.inputSchema,
774
- outputSchema: entry.outputSchema
775
- });
776
- var toBinding = (entry) => new McpToolBinding({
777
- toolId: entry.toolId,
778
- toolName: entry.toolName,
779
- description: entry.description,
780
- inputSchema: entry.inputSchema,
781
- outputSchema: entry.outputSchema
782
- });
783
- var toStoredSourceData = (config) => {
784
- if (config.transport === "stdio") {
785
- return {
786
- transport: "stdio",
787
- command: config.command,
788
- args: config.args,
789
- env: config.env,
790
- cwd: config.cwd
791
- };
792
- }
793
- return {
794
- transport: "remote",
795
- endpoint: config.endpoint,
796
- remoteTransport: config.remoteTransport ?? "auto",
797
- queryParams: config.queryParams,
798
- headers: config.headers,
799
- auth: config.auth ?? { kind: "none" }
800
- };
801
- };
802
- var normalizeNamespace = (config) => config.namespace ?? deriveMcpNamespace({
803
- name: config.name,
804
- endpoint: config.transport === "remote" ? config.endpoint : void 0,
805
- command: config.transport === "stdio" ? config.command : void 0
806
- });
807
- var makeOAuthProvider2 = (accessToken, tokenType, refreshToken) => ({
808
- get redirectUrl() {
809
- return "http://localhost/oauth/callback";
810
- },
811
- get clientMetadata() {
812
- return {
813
- redirect_uris: ["http://localhost/oauth/callback"],
814
- grant_types: ["authorization_code", "refresh_token"],
815
- response_types: ["code"],
816
- token_endpoint_auth_method: "none",
817
- client_name: "Executor"
818
- };
819
- },
820
- clientInformation: () => void 0,
821
- saveClientInformation: () => {
822
- },
823
- tokens: async () => ({
824
- access_token: accessToken,
825
- token_type: tokenType,
826
- ...refreshToken ? { refresh_token: refreshToken } : {}
827
- }),
828
- saveTokens: async () => {
829
- },
830
- redirectToAuthorization: async () => {
831
- throw new Error("MCP OAuth re-authorization required");
832
- },
833
- saveCodeVerifier: () => {
834
- },
835
- codeVerifier: () => {
836
- throw new Error("No active PKCE verifier");
837
- },
838
- saveDiscoveryState: () => {
839
- },
840
- discoveryState: () => void 0
841
- });
842
- var remoteConnectionError = (message) => new McpConnectionError({ transport: "remote", message });
843
- var mcpOAuthError = (message) => new McpOAuthError({ message });
844
- var mcpDiscoveryError = (message) => new McpToolDiscoveryError({ stage: "list_tools", message });
845
- var mcpPlugin = (options) => {
846
- const bindingStore = options?.bindingStore ?? makeInMemoryBindingStore();
847
- const addedSources = /* @__PURE__ */ new Map();
848
- return definePlugin({
849
- key: "mcp",
850
- init: (ctx) => Effect6.gen(function* () {
851
- const cacheScope = yield* Scope.make();
852
- const pendingConnectors = /* @__PURE__ */ new Map();
853
- const connectionCache = yield* ScopedCache.make({
854
- lookup: (key) => Effect6.acquireRelease(
855
- Effect6.suspend(() => {
856
- const connector = pendingConnectors.get(key);
857
- if (!connector) {
858
- return Effect6.fail(
859
- new McpConnectionError({
860
- transport: "auto",
861
- message: `No pending connector for key: ${key}`
862
- })
863
- );
864
- }
865
- return connector;
866
- }),
867
- (connection) => Effect6.promise(() => connection.close().catch(() => {
868
- }))
869
- ),
870
- capacity: 64,
871
- timeToLive: Duration.minutes(5)
872
- }).pipe(Scope.extend(cacheScope));
873
- const invoker = makeMcpInvoker({
874
- bindingStore,
875
- secrets: ctx.secrets,
876
- scopeId: ctx.scope.id,
877
- connectionCache,
878
- pendingConnectors
879
- });
880
- yield* ctx.tools.registerInvoker("mcp", invoker);
881
- const savedSources = yield* bindingStore.listSources();
882
- for (const s of savedSources) {
883
- const isRemote = s.config.transport === "remote";
884
- addedSources.set(
885
- s.namespace,
886
- new Source({
887
- id: s.namespace,
888
- name: s.name,
889
- kind: "mcp",
890
- url: s.config.transport === "remote" ? s.config.endpoint : void 0,
891
- canEdit: isRemote
892
- })
893
- );
894
- }
895
- const resolveConnectorInput2 = (sd) => {
896
- if (sd.transport === "stdio") {
897
- return Effect6.succeed({
898
- transport: "stdio",
899
- command: sd.command,
900
- args: sd.args,
901
- env: sd.env,
902
- cwd: sd.cwd
903
- });
904
- }
905
- return Effect6.gen(function* () {
906
- const headers = {
907
- ...sd.headers
908
- };
909
- let authProvider;
910
- const auth2 = sd.auth;
911
- if (auth2.kind === "header") {
912
- const val = yield* ctx.secrets.resolve(SecretId.make(auth2.secretId), ctx.scope.id).pipe(
913
- Effect6.mapError(
914
- () => remoteConnectionError(`Failed to resolve secret "${auth2.secretId}"`)
915
- )
916
- );
917
- headers[auth2.headerName] = auth2.prefix ? `${auth2.prefix}${val}` : val;
918
- } else if (auth2.kind === "oauth2") {
919
- const accessToken = yield* ctx.secrets.resolve(SecretId.make(auth2.accessTokenSecretId), ctx.scope.id).pipe(
920
- Effect6.mapError(
921
- () => remoteConnectionError("Failed to resolve OAuth access token")
922
- )
923
- );
924
- let refreshToken;
925
- if (auth2.refreshTokenSecretId) {
926
- refreshToken = yield* ctx.secrets.resolve(SecretId.make(auth2.refreshTokenSecretId), ctx.scope.id).pipe(
927
- Effect6.option,
928
- Effect6.map((o) => o._tag === "Some" ? o.value : void 0)
929
- );
930
- }
931
- authProvider = makeOAuthProvider2(
932
- accessToken,
933
- auth2.tokenType ?? "Bearer",
934
- refreshToken
935
- );
936
- }
937
- return {
938
- transport: "remote",
939
- endpoint: sd.endpoint,
940
- remoteTransport: sd.remoteTransport,
941
- queryParams: sd.queryParams,
942
- headers: Object.keys(headers).length > 0 ? headers : void 0,
943
- authProvider
944
- };
945
- });
946
- };
947
- yield* ctx.sources.addManager({
948
- kind: "mcp",
949
- list: () => Effect6.sync(() => [...addedSources.values()]),
950
- remove: (sourceId) => Effect6.gen(function* () {
951
- yield* bindingStore.removeByNamespace(sourceId);
952
- yield* bindingStore.removeSource(sourceId);
953
- yield* ctx.tools.unregisterBySource(sourceId);
954
- addedSources.delete(sourceId);
955
- }),
956
- detect: (url) => Effect6.gen(function* () {
957
- const trimmed = url.trim();
958
- if (!trimmed) return null;
959
- const parsed = yield* Effect6.try(() => new URL(trimmed)).pipe(Effect6.option);
960
- if (parsed._tag === "None") return null;
961
- const name = parsed.value.hostname || "mcp";
962
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
963
- const connector = createMcpConnector({
964
- transport: "remote",
965
- endpoint: trimmed
966
- });
967
- const connected = yield* discoverTools(connector).pipe(
968
- Effect6.map(() => true),
969
- Effect6.catchAll(() => Effect6.succeed(false))
970
- );
971
- if (connected) {
972
- return new SourceDetectionResult({
973
- kind: "mcp",
974
- confidence: "high",
975
- endpoint: trimmed,
976
- name,
977
- namespace
978
- });
979
- }
980
- const hasOAuth = yield* startMcpOAuthAuthorization({
981
- endpoint: trimmed,
982
- redirectUrl: "http://127.0.0.1/executor/discovery/oauth/probe",
983
- state: "probe"
984
- }).pipe(
985
- Effect6.map(() => true),
986
- Effect6.catchAll(() => Effect6.succeed(false))
987
- );
988
- if (hasOAuth) {
989
- return new SourceDetectionResult({
990
- kind: "mcp",
991
- confidence: "high",
992
- endpoint: trimmed,
993
- name,
994
- namespace
995
- });
996
- }
997
- return null;
998
- }),
999
- refresh: (sourceId) => Effect6.gen(function* () {
1000
- const sd = yield* bindingStore.getSourceConfig(sourceId);
1001
- if (!sd || !addedSources.has(sourceId)) return;
1002
- const ci = yield* resolveConnectorInput2(sd).pipe(
1003
- Effect6.catchAll(() => Effect6.succeed(null))
1004
- );
1005
- if (!ci) return;
1006
- const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
1007
- Effect6.catchAll(() => Effect6.succeed(null))
1008
- );
1009
- if (!manifest) return;
1010
- const oldIds = yield* bindingStore.removeByNamespace(sourceId);
1011
- if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
1012
- yield* Effect6.forEach(
1013
- manifest.tools,
1014
- (e) => bindingStore.put(
1015
- ToolId.make(joinToolPath(sourceId, e.toolId)),
1016
- sourceId,
1017
- toBinding(e)
1018
- ),
1019
- { discard: true }
1020
- );
1021
- yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, sourceId)));
1022
- })
1023
- });
1024
- const probeEndpoint = (endpoint) => Effect6.gen(function* () {
1025
- const trimmed = endpoint.trim();
1026
- if (!trimmed) return yield* remoteConnectionError("Endpoint URL is required");
1027
- const name = yield* Effect6.try(() => new URL(trimmed).hostname).pipe(
1028
- Effect6.orElseSucceed(() => "mcp")
1029
- );
1030
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
1031
- const connector = createMcpConnector({
1032
- transport: "remote",
1033
- endpoint: trimmed
1034
- });
1035
- const result = yield* discoverTools(connector).pipe(
1036
- Effect6.map((m) => ({ ok: true, manifest: m })),
1037
- Effect6.catchAll(() => Effect6.succeed({ ok: false, manifest: null }))
1038
- );
1039
- if (result.ok && result.manifest) {
1040
- return {
1041
- connected: true,
1042
- requiresOAuth: false,
1043
- name: result.manifest.server?.name ?? name,
1044
- namespace,
1045
- toolCount: result.manifest.tools.length,
1046
- serverName: result.manifest.server?.name ?? null
1047
- };
1048
- }
1049
- const hasOAuth = yield* startMcpOAuthAuthorization({
1050
- endpoint: trimmed,
1051
- redirectUrl: "http://127.0.0.1/executor/discovery/oauth/probe",
1052
- state: "probe"
1053
- }).pipe(
1054
- Effect6.map(() => true),
1055
- Effect6.catchAll(() => Effect6.succeed(false))
1056
- );
1057
- if (hasOAuth) {
1058
- return {
1059
- connected: false,
1060
- requiresOAuth: true,
1061
- name,
1062
- namespace,
1063
- toolCount: null,
1064
- serverName: null
1065
- };
1066
- }
1067
- return yield* remoteConnectionError(
1068
- "Could not connect to MCP endpoint and no OAuth was detected"
1069
- );
1070
- });
1071
- const addSource = (config) => Effect6.gen(function* () {
1072
- const namespace = normalizeNamespace(config);
1073
- const sd = toStoredSourceData(config);
1074
- const ci = yield* resolveConnectorInput2(sd);
1075
- const connector = createMcpConnector(ci);
1076
- const manifest = yield* discoverTools(connector).pipe(
1077
- Effect6.mapError((err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`))
1078
- );
1079
- const registrations = manifest.tools.map((e) => toRegistration(e, namespace));
1080
- yield* Effect6.forEach(
1081
- manifest.tools,
1082
- (e) => bindingStore.put(
1083
- ToolId.make(joinToolPath(namespace, e.toolId)),
1084
- namespace,
1085
- toBinding(e)
1086
- ),
1087
- { discard: true }
1088
- );
1089
- yield* ctx.tools.register(registrations);
1090
- const sourceName = manifest.server?.name ?? config.name ?? namespace;
1091
- yield* bindingStore.putSource({
1092
- namespace,
1093
- name: sourceName,
1094
- config: sd
1095
- });
1096
- addedSources.set(
1097
- namespace,
1098
- new Source({
1099
- id: namespace,
1100
- name: sourceName,
1101
- kind: "mcp",
1102
- url: sd.transport === "remote" ? sd.endpoint : void 0,
1103
- canEdit: config.transport === "remote"
1104
- })
1105
- );
1106
- return { toolCount: registrations.length, namespace };
1107
- });
1108
- const removeSource = (namespace) => Effect6.gen(function* () {
1109
- const ids = yield* bindingStore.removeByNamespace(namespace);
1110
- if (ids.length > 0) yield* ctx.tools.unregister(ids);
1111
- yield* bindingStore.removeSource(namespace);
1112
- addedSources.delete(namespace);
1113
- });
1114
- const refreshSource = (namespace) => Effect6.gen(function* () {
1115
- const sd = yield* bindingStore.getSourceConfig(namespace);
1116
- if (!sd)
1117
- return yield* remoteConnectionError(`No stored config for MCP source "${namespace}"`);
1118
- const ci = yield* resolveConnectorInput2(sd);
1119
- const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
1120
- Effect6.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`))
1121
- );
1122
- const oldIds = yield* bindingStore.removeByNamespace(namespace);
1123
- if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
1124
- yield* Effect6.forEach(
1125
- manifest.tools,
1126
- (e) => bindingStore.put(
1127
- ToolId.make(joinToolPath(namespace, e.toolId)),
1128
- namespace,
1129
- toBinding(e)
1130
- ),
1131
- { discard: true }
1132
- );
1133
- yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, namespace)));
1134
- return { toolCount: manifest.tools.length };
1135
- });
1136
- const startOAuth = (input) => Effect6.gen(function* () {
1137
- const endpoint = input.endpoint.trim();
1138
- if (!endpoint) return yield* mcpOAuthError("MCP OAuth requires an endpoint");
1139
- let fullEndpoint = endpoint;
1140
- if (input.queryParams && Object.keys(input.queryParams).length > 0) {
1141
- const u = new URL(endpoint);
1142
- for (const [k, v] of Object.entries(input.queryParams)) u.searchParams.set(k, v);
1143
- fullEndpoint = u.toString();
1144
- }
1145
- const sessionId = `mcp_oauth_${crypto.randomUUID()}`;
1146
- const started = yield* startMcpOAuthAuthorization({
1147
- endpoint: fullEndpoint,
1148
- redirectUrl: input.redirectUrl,
1149
- state: sessionId
1150
- }).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth start failed: ${e.message}`)));
1151
- yield* bindingStore.putOAuthSession(sessionId, {
1152
- endpoint: fullEndpoint,
1153
- redirectUrl: input.redirectUrl,
1154
- codeVerifier: started.codeVerifier,
1155
- resourceMetadataUrl: started.resourceMetadataUrl,
1156
- authorizationServerUrl: started.authorizationServerUrl,
1157
- resourceMetadata: started.resourceMetadata,
1158
- authorizationServerMetadata: started.authorizationServerMetadata,
1159
- clientInformation: started.clientInformation
1160
- });
1161
- return {
1162
- sessionId,
1163
- authorizationUrl: started.authorizationUrl
1164
- };
1165
- });
1166
- const completeOAuth = (input) => Effect6.gen(function* () {
1167
- if (input.error) return yield* mcpOAuthError(`OAuth error: ${input.error}`);
1168
- if (!input.code) return yield* mcpOAuthError("Missing OAuth authorization code");
1169
- const session = yield* bindingStore.getOAuthSession(input.state);
1170
- if (!session) return yield* mcpOAuthError(`OAuth session not found: ${input.state}`);
1171
- const exchanged = yield* exchangeMcpOAuthCode({
1172
- session,
1173
- code: input.code
1174
- }).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth exchange failed: ${e.message}`)));
1175
- const accessTokenRef = yield* ctx.secrets.set({
1176
- id: SecretId.make(`mcp-oauth-access-${input.state}`),
1177
- scopeId: ctx.scope.id,
1178
- name: "MCP OAuth Access Token",
1179
- value: exchanged.tokens.access_token,
1180
- purpose: "oauth_access_token"
1181
- }).pipe(
1182
- Effect6.mapError((e) => mcpOAuthError(`Failed to store access token: ${String(e)}`))
1183
- );
1184
- let refreshTokenSecretId = null;
1185
- if (exchanged.tokens.refresh_token) {
1186
- const ref = yield* ctx.secrets.set({
1187
- id: SecretId.make(`mcp-oauth-refresh-${input.state}`),
1188
- scopeId: ctx.scope.id,
1189
- name: "MCP OAuth Refresh Token",
1190
- value: exchanged.tokens.refresh_token,
1191
- purpose: "oauth_refresh_token"
1192
- }).pipe(
1193
- Effect6.mapError(
1194
- (e) => mcpOAuthError(`Failed to store refresh token: ${String(e)}`)
1195
- )
1196
- );
1197
- refreshTokenSecretId = ref.id;
1198
- }
1199
- yield* bindingStore.deleteOAuthSession(input.state);
1200
- const expiresAt = typeof exchanged.tokens.expires_in === "number" ? Date.now() + exchanged.tokens.expires_in * 1e3 : null;
1201
- return {
1202
- accessTokenSecretId: accessTokenRef.id,
1203
- refreshTokenSecretId,
1204
- tokenType: exchanged.tokens.token_type ?? "Bearer",
1205
- expiresAt,
1206
- scope: exchanged.tokens.scope ?? null
1207
- };
1208
- });
1209
- const updateSource = (namespace, input) => Effect6.gen(function* () {
1210
- const existing = yield* bindingStore.getSource(namespace);
1211
- if (!existing || existing.config.transport !== "remote") return;
1212
- const remote = existing.config;
1213
- const updatedConfig = {
1214
- ...remote,
1215
- ...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
1216
- ...input.headers !== void 0 ? { headers: input.headers } : {},
1217
- ...input.auth !== void 0 ? { auth: input.auth } : {},
1218
- ...input.queryParams !== void 0 ? { queryParams: input.queryParams } : {}
1219
- };
1220
- yield* bindingStore.putSource({
1221
- namespace,
1222
- name: input.name?.trim() || existing.name,
1223
- config: updatedConfig
1224
- });
1225
- });
1226
- const getSource = (namespace) => bindingStore.getSource(namespace);
1227
- return {
1228
- extension: {
1229
- probeEndpoint,
1230
- addSource,
1231
- removeSource,
1232
- refreshSource,
1233
- startOAuth,
1234
- completeOAuth,
1235
- getSource,
1236
- updateSource
1237
- },
1238
- close: () => Effect6.gen(function* () {
1239
- yield* invoker.closeConnections();
1240
- yield* Scope.close(cacheScope, Exit.void);
1241
- for (const sourceId of addedSources.keys()) {
1242
- yield* ctx.tools.unregisterBySource(sourceId);
1243
- }
1244
- addedSources.clear();
1245
- })
1246
- };
1247
- })
1248
- });
1249
- };
1250
-
1251
- export {
1252
- makeKvBindingStore,
1253
- mcpPlugin
1254
- };
1255
- //# sourceMappingURL=chunk-X3JTTDWJ.js.map