@executor-js/plugin-mcp 1.4.32 → 1.5.0

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 (57) hide show
  1. package/dist/AddMcpSource-4LLERUW5.js +602 -0
  2. package/dist/AddMcpSource-4LLERUW5.js.map +1 -0
  3. package/dist/EditMcpSource-GKJRP75X.js +313 -0
  4. package/dist/EditMcpSource-GKJRP75X.js.map +1 -0
  5. package/dist/McpAccountsPanel-UX7MHEIG.js +132 -0
  6. package/dist/McpAccountsPanel-UX7MHEIG.js.map +1 -0
  7. package/dist/api/group.d.ts +79 -143
  8. package/dist/api/index.d.ts +99 -155
  9. package/dist/chunk-2TXHTMKM.js +1298 -0
  10. package/dist/chunk-2TXHTMKM.js.map +1 -0
  11. package/dist/chunk-6OEQZ72N.js +124 -0
  12. package/dist/chunk-6OEQZ72N.js.map +1 -0
  13. package/dist/chunk-7FJ3PUUL.js +21 -0
  14. package/dist/chunk-7FJ3PUUL.js.map +1 -0
  15. package/dist/chunk-N4EAF5CA.js +146 -0
  16. package/dist/chunk-N4EAF5CA.js.map +1 -0
  17. package/dist/client.js +9 -9
  18. package/dist/client.js.map +1 -1
  19. package/dist/core.js +36 -26
  20. package/dist/index.js +2 -2
  21. package/dist/promise.d.ts +1 -1
  22. package/dist/react/AddMcpSource.d.ts +1 -1
  23. package/dist/react/McpAccountsPanel.d.ts +6 -0
  24. package/dist/react/McpRemoteSourceFields.d.ts +4 -2
  25. package/dist/react/McpSignInButton.d.ts +2 -0
  26. package/dist/react/atoms.d.ts +93 -313
  27. package/dist/react/auth-method-config.d.ts +8 -0
  28. package/dist/react/client.d.ts +78 -142
  29. package/dist/react/index.d.ts +3 -3
  30. package/dist/react/source-plugin.d.ts +5 -5
  31. package/dist/sdk/connection.d.ts +4 -4
  32. package/dist/sdk/errors.d.ts +0 -19
  33. package/dist/sdk/index.d.ts +4 -3
  34. package/dist/sdk/invoke.d.ts +9 -16
  35. package/dist/sdk/plugin.d.ts +101 -236
  36. package/dist/sdk/types.d.ts +25 -130
  37. package/package.json +5 -4
  38. package/dist/AddMcpSource-PADMBVX2.js +0 -688
  39. package/dist/AddMcpSource-PADMBVX2.js.map +0 -1
  40. package/dist/EditMcpSource-L5GC2B4J.js +0 -281
  41. package/dist/EditMcpSource-L5GC2B4J.js.map +0 -1
  42. package/dist/McpSourceSummary-LE3WXFUE.js +0 -170
  43. package/dist/McpSourceSummary-LE3WXFUE.js.map +0 -1
  44. package/dist/chunk-6OYEXHU3.js +0 -156
  45. package/dist/chunk-6OYEXHU3.js.map +0 -1
  46. package/dist/chunk-FMTVLO5L.js +0 -179
  47. package/dist/chunk-FMTVLO5L.js.map +0 -1
  48. package/dist/chunk-LEGVPKYH.js +0 -2391
  49. package/dist/chunk-LEGVPKYH.js.map +0 -1
  50. package/dist/chunk-ZIRGIRGP.js +0 -115
  51. package/dist/chunk-ZIRGIRGP.js.map +0 -1
  52. package/dist/react/McpSourceSummary.d.ts +0 -5
  53. package/dist/sdk/binding-store.d.ts +0 -31
  54. package/dist/sdk/stored-source.d.ts +0 -42
  55. /package/dist/{sdk/connection-pool.test.d.ts → react/auth-method-config.test.d.ts} +0 -0
  56. /package/dist/sdk/{cross-user-isolation.test.d.ts → describe-auth-methods.test.d.ts} +0 -0
  57. /package/dist/sdk/{per-user-auth-isolation.test.d.ts → owner-isolation.test.d.ts} +0 -0
@@ -1,2391 +0,0 @@
1
- import {
2
- mcpPresets
3
- } from "./chunk-TW44CBXJ.js";
4
- import {
5
- MCP_OAUTH_CLIENT_ID_SLOT,
6
- MCP_OAUTH_CLIENT_SECRET_SLOT,
7
- MCP_OAUTH_CONNECTION_SLOT,
8
- McpAuthRequiredError,
9
- McpConfiguredValueInput,
10
- McpConnectionAuthInput,
11
- McpConnectionError,
12
- McpCredentialInput,
13
- McpInvocationError,
14
- McpRemoteTransport,
15
- McpStoredSourceData,
16
- McpToolAnnotations,
17
- McpToolBinding,
18
- McpToolDiscoveryError,
19
- SecretBackedValue,
20
- mcpHeaderSlot,
21
- mcpQueryParamSlot
22
- } from "./chunk-6OYEXHU3.js";
23
-
24
- // src/sdk/binding-store.ts
25
- import { Effect, Option, Predicate, Schema } from "effect";
26
- var mcpSchema = {};
27
- var SOURCE_COLLECTION = "source";
28
- var BINDING_COLLECTION = "binding";
29
- var decodeSourceData = Schema.decodeUnknownSync(McpStoredSourceData);
30
- var encodeSourceData = Schema.encodeSync(McpStoredSourceData);
31
- var decodeBinding = Schema.decodeUnknownSync(McpToolBinding);
32
- var encodeBinding = Schema.encodeSync(McpToolBinding);
33
- var decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
34
- var coerceJson = (value) => {
35
- if (typeof value !== "string") return value;
36
- return Option.getOrElse(decodeJson(value), () => value);
37
- };
38
- var sourceData = (source) => ({
39
- namespace: source.namespace,
40
- scope: source.scope,
41
- name: source.name,
42
- config: encodeSourceData(source.config)
43
- });
44
- var bindingData = (namespace, entry) => ({
45
- namespace,
46
- toolId: entry.toolId,
47
- binding: encodeBinding(entry.binding)
48
- });
49
- var rowToSource = (row) => {
50
- const raw = coerceJson(row.data);
51
- if (!raw || typeof raw !== "object") return null;
52
- const record = raw;
53
- if (typeof record.namespace !== "string" || typeof record.scope !== "string" || typeof record.name !== "string") {
54
- return null;
55
- }
56
- return {
57
- namespace: record.namespace,
58
- scope: record.scope,
59
- name: record.name,
60
- config: decodeSourceData(coerceJson(record.config))
61
- };
62
- };
63
- var rowToBinding = (row) => {
64
- const raw = coerceJson(row.data);
65
- if (!raw || typeof raw !== "object") return null;
66
- const record = raw;
67
- if (typeof record.toolId !== "string" || typeof record.namespace !== "string") return null;
68
- return {
69
- toolId: record.toolId,
70
- namespace: record.namespace,
71
- binding: decodeBinding(coerceJson(record.binding))
72
- };
73
- };
74
- var makeMcpStore = ({ pluginStorage }) => {
75
- const listBindingRowsForSourceScope = (namespace, scope) => pluginStorage.list({
76
- collection: BINDING_COLLECTION,
77
- keyPrefix: `${namespace}.`
78
- }).pipe(
79
- Effect.map(
80
- (rows) => rows.filter((row) => {
81
- if (String(row.scopeId) !== scope) return false;
82
- return rowToBinding(row)?.namespace === namespace;
83
- })
84
- )
85
- );
86
- const removeBindingsForSourceScope = (namespace, scope) => Effect.gen(function* () {
87
- const rows = yield* listBindingRowsForSourceScope(namespace, scope);
88
- for (const row of rows) {
89
- yield* pluginStorage.remove({
90
- scope,
91
- collection: BINDING_COLLECTION,
92
- key: row.key
93
- });
94
- }
95
- });
96
- return {
97
- listBindingsBySource: (namespace, scope) => listBindingRowsForSourceScope(namespace, scope).pipe(
98
- Effect.map(
99
- (rows) => rows.map(rowToBinding).filter(Predicate.isNotNull).map((row) => ({ toolId: row.toolId, binding: row.binding }))
100
- )
101
- ),
102
- getBinding: (toolId, scope) => pluginStorage.getAtScope({ scope, collection: BINDING_COLLECTION, key: toolId }).pipe(
103
- Effect.map((row) => {
104
- const binding = row ? rowToBinding(row) : null;
105
- return binding ? { binding: binding.binding, namespace: binding.namespace } : null;
106
- })
107
- ),
108
- putBindings: (namespace, scope, entries) => Effect.gen(function* () {
109
- for (const entry of entries) {
110
- yield* pluginStorage.put({
111
- scope,
112
- collection: BINDING_COLLECTION,
113
- key: entry.toolId,
114
- data: bindingData(namespace, entry)
115
- });
116
- }
117
- }),
118
- removeBindingsByNamespace: (namespace, scope) => removeBindingsForSourceScope(namespace, scope),
119
- getSource: (namespace, scope) => pluginStorage.getAtScope({ scope, collection: SOURCE_COLLECTION, key: namespace }).pipe(Effect.map((row) => row ? rowToSource(row) : null)),
120
- getSourceConfig: (namespace, scope) => pluginStorage.getAtScope({ scope, collection: SOURCE_COLLECTION, key: namespace }).pipe(
121
- Effect.map((row) => {
122
- const source = row ? rowToSource(row) : null;
123
- return source?.config ?? null;
124
- })
125
- ),
126
- putSource: (source) => pluginStorage.put({
127
- scope: source.scope,
128
- collection: SOURCE_COLLECTION,
129
- key: source.namespace,
130
- data: sourceData(source)
131
- }).pipe(Effect.asVoid),
132
- removeSource: (namespace, scope) => Effect.gen(function* () {
133
- yield* removeBindingsForSourceScope(namespace, scope);
134
- yield* pluginStorage.remove({
135
- scope,
136
- collection: SOURCE_COLLECTION,
137
- key: namespace
138
- });
139
- })
140
- };
141
- };
142
-
143
- // src/sdk/plugin.ts
144
- import {
145
- Duration as Duration2,
146
- Effect as Effect6,
147
- Exit as Exit2,
148
- Match,
149
- Option as Option5,
150
- Predicate as Predicate3,
151
- Result,
152
- Scope,
153
- Schema as Schema5,
154
- ScopedCache as ScopedCache2
155
- } from "effect";
156
- import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
157
- import * as z from "zod/v4";
158
- import {
159
- ScopeId,
160
- SourceDetectionResult,
161
- ToolResult,
162
- authToolFailure,
163
- defaultSourceInstallScopeId,
164
- definePlugin,
165
- tool,
166
- resolveSecretBackedMap as resolveSharedSecretBackedMap,
167
- StorageError
168
- } from "@executor-js/sdk/core";
169
- import {
170
- compileHttpNamedCredentialMap,
171
- OAuth2SourceConfig,
172
- httpCredentialInputToBindingValue
173
- } from "@executor-js/sdk/http-source";
174
-
175
- // src/sdk/connection.ts
176
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
177
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
178
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
179
- import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
180
- import { Effect as Effect2 } from "effect";
181
- var buildEndpointUrl = (endpoint, queryParams) => {
182
- const url = new URL(endpoint);
183
- for (const [key, value] of Object.entries(queryParams)) {
184
- url.searchParams.set(key, value);
185
- }
186
- return url;
187
- };
188
- var createClient = () => new Client(
189
- { name: "executor-mcp", version: "0.1.0" },
190
- {
191
- capabilities: { elicitation: { form: {}, url: {} } },
192
- jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
193
- }
194
- );
195
- var connectionFromClient = (client) => ({
196
- client,
197
- close: () => client.close()
198
- });
199
- var connectClient = (input) => Effect2.gen(function* () {
200
- const client = createClient();
201
- const transportInstance = input.createTransport();
202
- yield* Effect2.tryPromise({
203
- try: () => client.connect(transportInstance),
204
- catch: () => new McpConnectionError({
205
- transport: input.transport,
206
- message: `Failed connecting via ${input.transport}`
207
- })
208
- }).pipe(
209
- Effect2.withSpan("plugin.mcp.connection.handshake", {
210
- attributes: { "plugin.mcp.transport": input.transport }
211
- })
212
- );
213
- return connectionFromClient(client);
214
- });
215
- var createMcpConnector = (input) => {
216
- if (input.transport === "stdio") {
217
- const command = input.command.trim();
218
- if (!command) {
219
- return Effect2.fail(
220
- new McpConnectionError({
221
- transport: "stdio",
222
- message: "MCP stdio transport requires a command"
223
- })
224
- );
225
- }
226
- return Effect2.gen(function* () {
227
- const { createStdioTransport } = yield* Effect2.tryPromise({
228
- try: () => import("./stdio-connector-AA5S6UUJ.js"),
229
- catch: () => new McpConnectionError({
230
- transport: "stdio",
231
- message: "Failed to load stdio transport module"
232
- })
233
- });
234
- return yield* connectClient({
235
- transport: "stdio",
236
- createTransport: () => createStdioTransport({
237
- command,
238
- args: input.args,
239
- env: input.env,
240
- cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
241
- })
242
- });
243
- });
244
- }
245
- const headers = input.headers ?? {};
246
- const remoteTransport = input.remoteTransport ?? "auto";
247
- const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
248
- const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
249
- const connectStreamableHttp = connectClient({
250
- transport: "streamable-http",
251
- createTransport: () => new StreamableHTTPClientTransport(endpoint, {
252
- requestInit,
253
- authProvider: input.authProvider
254
- })
255
- });
256
- const connectSse = connectClient({
257
- transport: "sse",
258
- createTransport: () => new SSEClientTransport(endpoint, {
259
- requestInit,
260
- authProvider: input.authProvider
261
- })
262
- });
263
- if (remoteTransport === "streamable-http") return connectStreamableHttp;
264
- if (remoteTransport === "sse") return connectSse;
265
- return connectStreamableHttp.pipe(Effect2.catch(() => connectSse));
266
- };
267
-
268
- // src/sdk/discover.ts
269
- import { Effect as Effect3 } from "effect";
270
-
271
- // src/sdk/manifest.ts
272
- import { Option as Option2, Schema as Schema2 } from "effect";
273
- var ListedTool = Schema2.Struct({
274
- name: Schema2.String,
275
- description: Schema2.optional(Schema2.NullOr(Schema2.String)),
276
- inputSchema: Schema2.optional(Schema2.Unknown),
277
- parameters: Schema2.optional(Schema2.Unknown),
278
- outputSchema: Schema2.optional(Schema2.Unknown),
279
- annotations: Schema2.optional(McpToolAnnotations)
280
- });
281
- var ListToolsResult = Schema2.Struct({
282
- tools: Schema2.Array(ListedTool)
283
- });
284
- var ServerInfo = Schema2.Struct({
285
- name: Schema2.optional(Schema2.String),
286
- version: Schema2.optional(Schema2.String)
287
- });
288
- var decodeListToolsResult = Schema2.decodeUnknownOption(ListToolsResult);
289
- var decodeServerInfo = Schema2.decodeUnknownOption(ServerInfo);
290
- var isListToolsResult = (value) => Option2.isSome(decodeListToolsResult(value));
291
- var sanitize = (value) => {
292
- const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
293
- return s || "tool";
294
- };
295
- var uniqueId = (value, seen) => {
296
- const base = sanitize(value);
297
- const n = (seen.get(base) ?? 0) + 1;
298
- seen.set(base, n);
299
- return n === 1 ? base : `${base}_${n}`;
300
- };
301
- var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
302
- const seen = /* @__PURE__ */ new Map();
303
- const listed = decodeListToolsResult(listToolsResult).pipe(
304
- Option2.map((result) => result.tools),
305
- Option2.getOrElse(() => [])
306
- );
307
- const server = decodeServerInfo(metadata?.serverInfo).pipe(
308
- Option2.map(
309
- (info) => ({
310
- name: info.name ?? null,
311
- version: info.version ?? null
312
- })
313
- ),
314
- Option2.getOrNull
315
- );
316
- const tools = listed.flatMap((tool2) => {
317
- const toolName = tool2.name.trim();
318
- if (!toolName) return [];
319
- return [
320
- {
321
- toolId: uniqueId(toolName, seen),
322
- toolName,
323
- description: tool2.description ?? null,
324
- inputSchema: tool2.inputSchema ?? tool2.parameters,
325
- outputSchema: tool2.outputSchema,
326
- annotations: tool2.annotations
327
- }
328
- ];
329
- });
330
- return { server, tools };
331
- };
332
- var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
333
- var hostnameOf = (url) => {
334
- if (!URL.canParse(url)) return null;
335
- return new URL(url).hostname;
336
- };
337
- var basenameOf = (path) => path.trim().split(/[\\/]/).pop() ?? path.trim();
338
- var deriveMcpNamespace = (input) => {
339
- if (input.name?.trim()) return slugify(input.name) || "mcp";
340
- const fromEndpoint = input.endpoint?.trim() ? hostnameOf(input.endpoint) : null;
341
- if (fromEndpoint) return slugify(fromEndpoint) || "mcp";
342
- if (input.command?.trim()) return slugify(basenameOf(input.command)) || "mcp";
343
- return "mcp";
344
- };
345
-
346
- // src/sdk/discover.ts
347
- var discoverTools = (connector) => Effect3.gen(function* () {
348
- const connection = yield* connector.pipe(
349
- Effect3.mapError(
350
- ({ message }) => new McpToolDiscoveryError({
351
- stage: "connect",
352
- message: `Failed connecting to MCP server: ${message}`
353
- })
354
- )
355
- );
356
- const listResult = yield* Effect3.tryPromise({
357
- try: () => connection.client.listTools(),
358
- catch: () => new McpToolDiscoveryError({
359
- stage: "list_tools",
360
- message: "Failed listing MCP tools"
361
- })
362
- });
363
- if (!isListToolsResult(listResult)) {
364
- yield* closeConnection(connection);
365
- return yield* new McpToolDiscoveryError({
366
- stage: "list_tools",
367
- message: "MCP listTools response did not match the expected schema"
368
- });
369
- }
370
- const manifest = extractManifestFromListToolsResult(listResult, {
371
- serverInfo: connection.client.getServerVersion?.()
372
- });
373
- yield* closeConnection(connection);
374
- return manifest;
375
- });
376
- var closeConnection = (connection) => Effect3.ignore(
377
- Effect3.tryPromise({
378
- try: () => connection.close(),
379
- catch: () => new McpToolDiscoveryError({
380
- stage: "list_tools",
381
- message: "Failed closing MCP connection"
382
- })
383
- })
384
- );
385
-
386
- // src/sdk/invoke.ts
387
- import { Cause, Effect as Effect4, Exit, Option as Option3, Predicate as Predicate2, Schema as Schema3, ScopedCache } from "effect";
388
- import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
389
- import {
390
- FormElicitation,
391
- UrlElicitation
392
- } from "@executor-js/sdk/core";
393
- var ArgsRecord = Schema3.Record(Schema3.String, Schema3.Unknown);
394
- var decodeArgsRecord = Schema3.decodeUnknownOption(ArgsRecord);
395
- var argsRecord = (value) => Option3.getOrElse(decodeArgsRecord(value), () => ({}));
396
- var stableJson = (value) => {
397
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
398
- if (value && typeof value === "object") {
399
- return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
400
- }
401
- return JSON.stringify(value);
402
- };
403
- var fingerprint = (value) => {
404
- const input = stableJson(value);
405
- let hash = 2166136261;
406
- for (let i = 0; i < input.length; i++) {
407
- hash ^= input.charCodeAt(i);
408
- hash = Math.imul(hash, 16777619);
409
- }
410
- return (hash >>> 0).toString(16).padStart(8, "0");
411
- };
412
- var connectionCacheKey = (input) => {
413
- const sd = input.sourceData;
414
- return sd.transport === "stdio" ? `stdio:${fingerprint({
415
- sourceId: input.sourceId,
416
- sourceScope: input.sourceScope,
417
- command: sd.command,
418
- args: sd.args ?? [],
419
- env: sd.env ?? {},
420
- cwd: sd.cwd ?? null
421
- })}` : `remote:${fingerprint({
422
- sourceId: input.sourceId,
423
- sourceScope: input.sourceScope,
424
- invokerScope: input.invokerScope,
425
- endpoint: sd.endpoint,
426
- remoteTransport: sd.remoteTransport ?? "auto",
427
- headers: sd.headers ?? {},
428
- queryParams: sd.queryParams ?? {},
429
- auth: sd.auth
430
- })}`;
431
- };
432
- var McpElicitParams = Schema3.Union([
433
- Schema3.Struct({
434
- mode: Schema3.Literal("url"),
435
- message: Schema3.String,
436
- url: Schema3.String,
437
- elicitationId: Schema3.optional(Schema3.String),
438
- id: Schema3.optional(Schema3.String)
439
- }),
440
- Schema3.Struct({
441
- mode: Schema3.optional(Schema3.Literal("form")),
442
- message: Schema3.String,
443
- requestedSchema: Schema3.Record(Schema3.String, Schema3.Unknown)
444
- })
445
- ]);
446
- var decodeElicitParams = Schema3.decodeUnknownSync(McpElicitParams);
447
- var toElicitationRequest = (params) => params.mode === "url" ? UrlElicitation.make({
448
- message: params.message,
449
- url: params.url,
450
- elicitationId: params.elicitationId ?? params.id ?? ""
451
- }) : FormElicitation.make({
452
- message: params.message,
453
- requestedSchema: params.requestedSchema
454
- });
455
- var installElicitationHandler = (client, elicit) => {
456
- client.setRequestHandler(ElicitRequestSchema, async (request) => {
457
- const params = decodeElicitParams(request.params);
458
- const req = toElicitationRequest(params);
459
- const exit = await Effect4.runPromiseExit(elicit(req));
460
- if (Exit.isSuccess(exit)) {
461
- const response = exit.value;
462
- return {
463
- action: response.action,
464
- ...response.action === "accept" && response.content ? { content: response.content } : {}
465
- };
466
- }
467
- const failure = exit.cause.reasons.find(Cause.isFailReason);
468
- if (failure) {
469
- const err = failure.error;
470
- if (Predicate2.isTagged(err, "ElicitationDeclinedError")) {
471
- const action = Predicate2.hasProperty(err, "action") && err.action === "cancel" ? "cancel" : "decline";
472
- return { action };
473
- }
474
- }
475
- throw Cause.squash(exit.cause);
476
- });
477
- };
478
- var useConnection = (connection, toolName, args, elicit) => Effect4.gen(function* () {
479
- installElicitationHandler(connection.client, elicit);
480
- return yield* Effect4.tryPromise({
481
- try: () => connection.client.callTool({ name: toolName, arguments: args }),
482
- catch: () => new McpInvocationError({
483
- toolName,
484
- message: `MCP tool call failed for ${toolName}`
485
- })
486
- }).pipe(
487
- Effect4.withSpan("plugin.mcp.client.call_tool", {
488
- attributes: { "mcp.tool.name": toolName }
489
- })
490
- );
491
- });
492
- var invokeMcpTool = (input) => {
493
- const transport = input.sourceData.transport === "stdio" ? "stdio" : input.sourceData.remoteTransport ?? "auto";
494
- return Effect4.gen(function* () {
495
- const cacheKey = connectionCacheKey(input);
496
- const args = argsRecord(input.args);
497
- const connector = input.resolveConnector();
498
- input.pendingConnectors.set(cacheKey, connector);
499
- const cacheHit = yield* ScopedCache.has(input.connectionCache, cacheKey);
500
- const firstConnection = yield* ScopedCache.get(input.connectionCache, cacheKey).pipe(
501
- Effect4.withSpan("plugin.mcp.connection.acquire", {
502
- attributes: {
503
- "plugin.mcp.transport": transport,
504
- "plugin.mcp.cache_key": cacheKey,
505
- "plugin.mcp.attempt": 1,
506
- "plugin.mcp.cache_hit": cacheHit
507
- }
508
- })
509
- );
510
- return yield* useConnection(firstConnection, input.toolName, args, input.elicit).pipe(
511
- // On failure, invalidate the cache and retry once with a fresh
512
- // connection. Matches the old invoker's retry-once semantics.
513
- Effect4.catch(
514
- () => Effect4.gen(function* () {
515
- yield* ScopedCache.invalidate(input.connectionCache, cacheKey);
516
- input.pendingConnectors.set(cacheKey, connector);
517
- const fresh = yield* ScopedCache.get(input.connectionCache, cacheKey);
518
- return yield* useConnection(fresh, input.toolName, args, input.elicit);
519
- }).pipe(
520
- Effect4.withSpan("plugin.mcp.invoke.retry", {
521
- attributes: {
522
- "plugin.mcp.transport": transport,
523
- "plugin.mcp.cache_key": cacheKey,
524
- "mcp.tool.name": input.toolName
525
- }
526
- })
527
- )
528
- )
529
- );
530
- }).pipe(
531
- Effect4.scoped,
532
- Effect4.withSpan("plugin.mcp.invoke", {
533
- attributes: {
534
- "mcp.tool.name": input.toolName,
535
- "plugin.mcp.tool_id": input.toolId,
536
- "plugin.mcp.transport": transport
537
- }
538
- })
539
- );
540
- };
541
-
542
- // src/sdk/probe-shape.ts
543
- import { Data, Duration, Effect as Effect5, Option as Option4, Schema as Schema4 } from "effect";
544
- import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
545
- var INITIALIZE_BODY = JSON.stringify({
546
- jsonrpc: "2.0",
547
- id: 1,
548
- method: "initialize",
549
- params: {
550
- protocolVersion: "2025-06-18",
551
- capabilities: {},
552
- clientInfo: { name: "executor-probe", version: "0" }
553
- }
554
- });
555
- var readHeader = (headers, name) => {
556
- const direct = headers[name];
557
- if (direct !== void 0) return direct;
558
- const lower = name.toLowerCase();
559
- for (const [k, v] of Object.entries(headers)) {
560
- if (k.toLowerCase() === lower) return v;
561
- }
562
- return null;
563
- };
564
- var ProbeTransportError = class extends Data.TaggedError("ProbeTransportError") {
565
- };
566
- var decodeJsonString = Schema4.decodeUnknownOption(Schema4.fromJsonString(Schema4.Unknown));
567
- var asObject = (body) => {
568
- if (!body) return null;
569
- const parsed = decodeJsonString(body);
570
- if (Option4.isNone(parsed)) return null;
571
- const value = parsed.value;
572
- if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
573
- return value;
574
- };
575
- var isJsonRpcEnvelope = (body) => {
576
- const obj = asObject(body);
577
- if (!obj) return false;
578
- if (obj.jsonrpc !== "2.0") return false;
579
- return "result" in obj || "error" in obj || "method" in obj;
580
- };
581
- var isOAuthErrorBody = (body) => {
582
- const obj = asObject(body);
583
- if (!obj) return false;
584
- if (Array.isArray(obj.errors)) return false;
585
- return typeof obj.error === "string";
586
- };
587
- var ProtectedResourceMetadata = Schema4.Struct({
588
- resource: Schema4.String,
589
- authorization_servers: Schema4.Array(Schema4.String)
590
- });
591
- var decodeProtectedResourceMetadata = Schema4.decodeUnknownOption(
592
- Schema4.fromJsonString(ProtectedResourceMetadata)
593
- );
594
- var protectedResourceMetadataUrl = (endpoint) => {
595
- const path = endpoint.pathname === "/" ? "" : endpoint.pathname;
596
- return `${endpoint.origin}/.well-known/oauth-protected-resource${path}`;
597
- };
598
- var resourceMatchesEndpoint = (resource, endpoint) => {
599
- if (!URL.canParse(resource)) return false;
600
- const parsed = new URL(resource);
601
- if (parsed.origin !== endpoint.origin) return false;
602
- const resourcePath = parsed.pathname.replace(/\/+$/, "");
603
- const endpointPath = endpoint.pathname.replace(/\/+$/, "");
604
- return endpointPath === resourcePath || endpointPath.startsWith(`${resourcePath}/`);
605
- };
606
- var probeProtectedResourceMetadata = (client, endpoint, timeoutMs) => Effect5.gen(function* () {
607
- const response = yield* client.execute(
608
- HttpClientRequest.get(protectedResourceMetadataUrl(endpoint)).pipe(
609
- HttpClientRequest.setHeader("accept", "application/json")
610
- )
611
- ).pipe(Effect5.timeout(Duration.millis(timeoutMs)));
612
- if (response.status < 200 || response.status >= 300) return false;
613
- const body = yield* response.text.pipe(
614
- Effect5.timeout(Duration.millis(timeoutMs)),
615
- Effect5.catch(() => Effect5.succeed(""))
616
- );
617
- const metadata = decodeProtectedResourceMetadata(body);
618
- if (Option4.isNone(metadata)) return false;
619
- if (metadata.value.authorization_servers.length === 0) return false;
620
- return resourceMatchesEndpoint(metadata.value.resource, endpoint);
621
- }).pipe(Effect5.catch(() => Effect5.succeed(false)));
622
- var ErrorMessageShape = Schema4.Struct({ message: Schema4.String });
623
- var decodeErrorMessageShape = Schema4.decodeUnknownOption(ErrorMessageShape);
624
- var reasonFromBoundaryCause = (cause) => {
625
- const messageShape = decodeErrorMessageShape(cause);
626
- if (Option4.isSome(messageShape)) return messageShape.value.message;
627
- if (typeof cause === "string") return cause;
628
- if (typeof cause === "number" || typeof cause === "boolean" || typeof cause === "bigint") {
629
- return `${cause}`;
630
- }
631
- if (typeof cause === "symbol") return cause.description ?? "symbol";
632
- if (cause === null) return "null";
633
- if (typeof cause === "undefined") return "undefined";
634
- return "fetch failed";
635
- };
636
- var probeMcpEndpointShape = (endpoint, options = {}) => Effect5.gen(function* () {
637
- const timeoutMs = options.timeoutMs ?? 8e3;
638
- const outcome = yield* Effect5.gen(function* () {
639
- const client = yield* HttpClient.HttpClient;
640
- const readBody = (response) => response.text.pipe(
641
- Effect5.timeout(Duration.millis(timeoutMs)),
642
- Effect5.catch(() => Effect5.succeed(""))
643
- );
644
- const classify = (response, method) => Effect5.gen(function* () {
645
- const contentType = readHeader(response.headers, "content-type") ?? "";
646
- const isSse = /^\s*text\/event-stream\b/i.test(contentType);
647
- if (response.status === 401) {
648
- const wwwAuth = readHeader(response.headers, "www-authenticate");
649
- if (!wwwAuth || !/^\s*bearer\b/i.test(wwwAuth)) {
650
- if (yield* probeProtectedResourceMetadata(client, url, timeoutMs)) {
651
- return { kind: "mcp", requiresAuth: true };
652
- }
653
- return {
654
- kind: "not-mcp",
655
- category: "auth-required",
656
- reason: "401 without Bearer WWW-Authenticate \u2014 not an MCP auth challenge"
657
- };
658
- }
659
- if (/(?:^|[\s,])resource_metadata\s*=/i.test(wwwAuth)) {
660
- return { kind: "mcp", requiresAuth: true };
661
- }
662
- if (/(?:^|[\s,])error\s*=/i.test(wwwAuth)) {
663
- return { kind: "mcp", requiresAuth: true };
664
- }
665
- if (isSse) return { kind: "mcp", requiresAuth: true };
666
- const body = yield* readBody(response);
667
- if (!isJsonRpcEnvelope(body) && !isOAuthErrorBody(body)) {
668
- if (yield* probeProtectedResourceMetadata(client, url, timeoutMs)) {
669
- return { kind: "mcp", requiresAuth: true };
670
- }
671
- return {
672
- kind: "not-mcp",
673
- category: "auth-required",
674
- reason: "401 + Bearer without resource_metadata, JSON-RPC body, or OAuth error body"
675
- };
676
- }
677
- return { kind: "mcp", requiresAuth: true };
678
- }
679
- if (response.status >= 200 && response.status < 300) {
680
- if (method === "GET") {
681
- if (!isSse) {
682
- return {
683
- kind: "not-mcp",
684
- category: "wrong-shape",
685
- reason: "GET response is not an SSE stream"
686
- };
687
- }
688
- return { kind: "mcp", requiresAuth: false };
689
- }
690
- if (isSse) return { kind: "mcp", requiresAuth: false };
691
- const body = yield* readBody(response);
692
- if (!isJsonRpcEnvelope(body)) {
693
- return {
694
- kind: "not-mcp",
695
- category: "wrong-shape",
696
- reason: "2xx POST body is not a JSON-RPC envelope"
697
- };
698
- }
699
- return { kind: "mcp", requiresAuth: false };
700
- }
701
- return null;
702
- });
703
- const url = new URL(endpoint);
704
- for (const [key, value] of Object.entries(options.queryParams ?? {})) {
705
- url.searchParams.set(key, value);
706
- }
707
- let postRequest = HttpClientRequest.post(url.toString()).pipe(
708
- HttpClientRequest.setHeader("content-type", "application/json"),
709
- HttpClientRequest.setHeader("accept", "application/json, text/event-stream"),
710
- HttpClientRequest.bodyText(INITIALIZE_BODY, "application/json")
711
- );
712
- for (const [name, value] of Object.entries(options.headers ?? {})) {
713
- postRequest = HttpClientRequest.setHeader(postRequest, name, value);
714
- }
715
- const postResponse = yield* client.execute(postRequest).pipe(Effect5.timeout(Duration.millis(timeoutMs)));
716
- const postResult = yield* classify(postResponse, "POST");
717
- if (postResult) return postResult;
718
- if ([404, 405, 406, 415].includes(postResponse.status)) {
719
- let getRequest = HttpClientRequest.get(url.toString()).pipe(
720
- HttpClientRequest.setHeader("accept", "text/event-stream")
721
- );
722
- for (const [name, value] of Object.entries(options.headers ?? {})) {
723
- getRequest = HttpClientRequest.setHeader(getRequest, name, value);
724
- }
725
- const getResponse = yield* client.execute(getRequest).pipe(Effect5.timeout(Duration.millis(timeoutMs)));
726
- const getResult = yield* classify(getResponse, "GET");
727
- if (getResult) return getResult;
728
- }
729
- return {
730
- kind: "not-mcp",
731
- category: "wrong-shape",
732
- reason: `unexpected status ${postResponse.status} for initialize`
733
- };
734
- }).pipe(
735
- Effect5.provide(options.httpClientLayer ?? FetchHttpClient.layer),
736
- Effect5.mapError(
737
- (cause) => new ProbeTransportError({
738
- reason: reasonFromBoundaryCause(cause),
739
- cause
740
- })
741
- ),
742
- Effect5.catch(
743
- (cause) => Effect5.succeed({
744
- kind: "unreachable",
745
- reason: cause.reason
746
- })
747
- )
748
- );
749
- return outcome;
750
- }).pipe(Effect5.withSpan("mcp.plugin.probe_shape"));
751
-
752
- // src/sdk/plugin.ts
753
- import {
754
- headerToConfigValue
755
- } from "@executor-js/config";
756
- var McpConfigureSourcePayloadSchema = Schema5.Struct({
757
- name: Schema5.optional(Schema5.String),
758
- endpoint: Schema5.optional(Schema5.String),
759
- headers: Schema5.optional(Schema5.Record(Schema5.String, McpCredentialInput)),
760
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, McpCredentialInput)),
761
- auth: Schema5.optional(McpConnectionAuthInput)
762
- });
763
- var McpConfigureSourceInputSchema = Schema5.Struct({
764
- scope: Schema5.String,
765
- ...McpConfigureSourcePayloadSchema.fields
766
- });
767
- var McpInitialCredentialsInputSchema = Schema5.Struct({
768
- scope: Schema5.String,
769
- headers: Schema5.optional(Schema5.Record(Schema5.String, McpCredentialInput)),
770
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, McpCredentialInput)),
771
- auth: Schema5.optional(McpConnectionAuthInput)
772
- });
773
- var McpRemoteAddSourceInputSchema = Schema5.Struct({
774
- transport: Schema5.Literal("remote"),
775
- name: Schema5.String,
776
- endpoint: Schema5.String,
777
- remoteTransport: Schema5.optional(McpRemoteTransport),
778
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, McpConfiguredValueInput)),
779
- headers: Schema5.optional(Schema5.Record(Schema5.String, McpConfiguredValueInput)),
780
- namespace: Schema5.optional(Schema5.String),
781
- oauth2: Schema5.optional(OAuth2SourceConfig),
782
- credentials: Schema5.optional(McpInitialCredentialsInputSchema)
783
- });
784
- var McpStdioAddSourceInputSchema = Schema5.Struct({
785
- transport: Schema5.Literal("stdio"),
786
- name: Schema5.String,
787
- command: Schema5.String,
788
- args: Schema5.optional(Schema5.Array(Schema5.String)),
789
- env: Schema5.optional(Schema5.Record(Schema5.String, Schema5.String)),
790
- cwd: Schema5.optional(Schema5.String),
791
- namespace: Schema5.optional(Schema5.String)
792
- });
793
- var McpAddSourceInputSchema = Schema5.Union([
794
- McpRemoteAddSourceInputSchema,
795
- McpStdioAddSourceInputSchema
796
- ]);
797
- var McpAddSourceOutputSchema = Schema5.Struct({
798
- namespace: Schema5.String,
799
- source: Schema5.Struct({
800
- id: Schema5.String,
801
- scope: Schema5.String
802
- }),
803
- toolCount: Schema5.Number,
804
- discovery: Schema5.optional(
805
- Schema5.Struct({
806
- status: Schema5.Literals(["ok", "failed"]),
807
- message: Schema5.optional(Schema5.String),
808
- stage: Schema5.optional(Schema5.String)
809
- })
810
- )
811
- });
812
- var McpProbeEndpointInputSchema = Schema5.Struct({
813
- endpoint: Schema5.String,
814
- headers: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue)),
815
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue))
816
- });
817
- var McpProbeEndpointOutputSchema = Schema5.Struct({
818
- connected: Schema5.Boolean,
819
- requiresOAuth: Schema5.Boolean,
820
- supportsDynamicRegistration: Schema5.Boolean,
821
- name: Schema5.String,
822
- namespace: Schema5.String,
823
- toolCount: Schema5.NullOr(Schema5.Number),
824
- serverName: Schema5.NullOr(Schema5.String)
825
- });
826
- var McpGetSourceInputSchema = Schema5.Struct({
827
- namespace: Schema5.String,
828
- scope: Schema5.String
829
- });
830
- var McpGetSourceOutputSchema = Schema5.Struct({
831
- source: Schema5.NullOr(Schema5.Unknown)
832
- });
833
- var McpStaticConfigureSourceInputSchema = Schema5.Struct({
834
- source: Schema5.Struct({
835
- id: Schema5.String,
836
- scope: Schema5.String
837
- }),
838
- scope: Schema5.String,
839
- ...McpConfigureSourcePayloadSchema.fields
840
- });
841
- var McpStaticConfigureSourceOutputSchema = Schema5.Struct({
842
- configured: Schema5.Boolean
843
- });
844
- var schemaToStaticToolSchema = (schema) => Schema5.toStandardSchemaV1(Schema5.toStandardJSONSchemaV1(schema));
845
- var mcpToolFailure = (code, message, details) => ToolResult.fail({
846
- code,
847
- message,
848
- ...details === void 0 ? {} : { details }
849
- });
850
- var mcpAuthToolFailure = (failure) => authToolFailure({
851
- code: failure.code,
852
- message: failure.message,
853
- source: { id: failure.sourceId, scope: failure.sourceScope },
854
- credential: {
855
- kind: failure.credentialKind,
856
- ...failure.credentialLabel ? { label: failure.credentialLabel } : {},
857
- ...failure.slotKey ? { slotKey: failure.slotKey } : {},
858
- ...failure.secretId ? { secretId: failure.secretId } : {},
859
- ...failure.connectionId ? { connectionId: failure.connectionId } : {}
860
- },
861
- ...failure.status !== void 0 ? { status: failure.status } : {},
862
- ...failure.details !== void 0 ? {
863
- upstream: {
864
- ...failure.status !== void 0 ? { status: failure.status } : {},
865
- details: failure.details
866
- }
867
- } : {},
868
- recovery: { configureSourceTool: "executor.mcp.configureSource" }
869
- });
870
- var McpAddSourceInputStandardSchema = schemaToStaticToolSchema(McpAddSourceInputSchema);
871
- var McpAddSourceOutputStandardSchema = schemaToStaticToolSchema(McpAddSourceOutputSchema);
872
- var McpProbeEndpointInputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointInputSchema);
873
- var McpProbeEndpointOutputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointOutputSchema);
874
- var McpGetSourceInputStandardSchema = schemaToStaticToolSchema(McpGetSourceInputSchema);
875
- var McpGetSourceOutputStandardSchema = schemaToStaticToolSchema(McpGetSourceOutputSchema);
876
- var McpStaticConfigureSourceInputStandardSchema = schemaToStaticToolSchema(
877
- McpStaticConfigureSourceInputSchema
878
- );
879
- var McpStaticConfigureSourceOutputStandardSchema = schemaToStaticToolSchema(
880
- McpStaticConfigureSourceOutputSchema
881
- );
882
- var resolveStaticScopeInput = (ctx, value) => String(
883
- ctx.scopes.find((scope) => scope.name === value || String(scope.id) === value)?.id ?? value
884
- );
885
- var toStoredSourceData = (config, remoteCredentials) => {
886
- if (config.transport === "stdio") {
887
- return {
888
- transport: "stdio",
889
- command: config.command,
890
- args: config.args,
891
- env: config.env,
892
- cwd: config.cwd
893
- };
894
- }
895
- return {
896
- transport: "remote",
897
- endpoint: config.endpoint,
898
- remoteTransport: config.remoteTransport ?? "auto",
899
- queryParams: remoteCredentials?.queryParams,
900
- headers: remoteCredentials?.headers,
901
- auth: remoteCredentials?.auth ?? { kind: "none" }
902
- };
903
- };
904
- var normalizeNamespace = (config) => config.namespace ?? deriveMcpNamespace({
905
- name: config.name,
906
- endpoint: config.transport === "remote" ? config.endpoint : void 0,
907
- command: config.transport === "stdio" ? config.command : void 0
908
- });
909
- var McpCallToolResultJsonSchema = z.toJSONSchema(CallToolResultSchema);
910
- var mcpCallToolResultOutputSchema = (structuredContentSchema) => {
911
- const defaultStructuredContentSchema = McpCallToolResultJsonSchema.properties?.structuredContent ?? {};
912
- return {
913
- ...McpCallToolResultJsonSchema,
914
- properties: {
915
- ...McpCallToolResultJsonSchema.properties,
916
- structuredContent: structuredContentSchema === void 0 ? defaultStructuredContentSchema : structuredContentSchema,
917
- isError: { const: false }
918
- },
919
- required: structuredContentSchema === void 0 ? ["content"] : ["content", "structuredContent"]
920
- };
921
- };
922
- var toBinding = (entry) => McpToolBinding.make({
923
- toolId: entry.toolId,
924
- toolName: entry.toolName,
925
- description: entry.description,
926
- inputSchema: entry.inputSchema,
927
- outputSchema: entry.outputSchema,
928
- annotations: entry.annotations
929
- });
930
- var MCP_PLUGIN_ID = "mcp";
931
- var McpTextContent = Schema5.Struct({ type: Schema5.Literal("text"), text: Schema5.String });
932
- var McpToolCallEnvelope = Schema5.Struct({
933
- isError: Schema5.optional(Schema5.Boolean),
934
- content: Schema5.optional(Schema5.Array(Schema5.Unknown))
935
- });
936
- var decodeMcpTextContent = Schema5.decodeUnknownOption(McpTextContent);
937
- var decodeMcpToolCallEnvelope = Schema5.decodeUnknownOption(McpToolCallEnvelope);
938
- var extractMcpErrorMessage = (content) => {
939
- if (Array.isArray(content)) {
940
- for (const item of content) {
941
- const decoded = Option5.getOrUndefined(decodeMcpTextContent(item));
942
- if (decoded !== void 0 && decoded.text.length > 0) return decoded.text;
943
- }
944
- }
945
- return "MCP tool returned an error";
946
- };
947
- var urlMatchesToken = (url, token) => {
948
- const re = new RegExp(`(?:^|[^a-z0-9])${token}(?:$|[^a-z0-9])`, "i");
949
- return re.test(url.hostname) || re.test(url.pathname);
950
- };
951
- var userFacingProbeMessage = (shape) => {
952
- if (shape.kind === "unreachable") {
953
- return "Couldn't reach this URL. Check the address, your network, and that the server is running.";
954
- }
955
- return Match.value(shape.category).pipe(
956
- Match.when(
957
- "auth-required",
958
- () => "This server requires authentication. Add credentials (Authorization header, query parameter, or API key) below and retry."
959
- ),
960
- Match.when(
961
- "wrong-shape",
962
- () => "This URL doesn't appear to host an MCP server. Double-check the address, including the path."
963
- ),
964
- Match.exhaustive
965
- );
966
- };
967
- var scopeRanks = (ctx) => new Map(ctx.scopes.map((scope, index) => [String(scope.id), index]));
968
- var scopeRank = (ranks, scopeId) => ranks.get(scopeId) ?? Infinity;
969
- var resolveMcpSourceBinding = (ctx, sourceId, sourceScope, slot) => Effect6.gen(function* () {
970
- const ranks = scopeRanks(ctx);
971
- const sourceSourceRank = scopeRank(ranks, sourceScope);
972
- if (sourceSourceRank === Infinity) return null;
973
- const bindings = yield* ctx.credentialBindings.listForSource({
974
- pluginId: MCP_PLUGIN_ID,
975
- sourceId,
976
- sourceScope: ScopeId.make(sourceScope)
977
- });
978
- const binding = bindings.filter(
979
- (candidate) => candidate.slotKey === slot && scopeRank(ranks, candidate.scopeId) <= sourceSourceRank
980
- ).sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0];
981
- return binding ?? null;
982
- });
983
- var validateMcpBindingTarget = (ctx, input) => Effect6.gen(function* () {
984
- const ranks = scopeRanks(ctx);
985
- const sourceSourceRank = scopeRank(ranks, input.sourceScope);
986
- const targetRank = scopeRank(ranks, input.targetScope);
987
- const scopeList = `[${ctx.scopes.map((s) => s.id).join(", ")}]`;
988
- if (sourceSourceRank === Infinity) {
989
- return yield* new StorageError({
990
- message: `MCP source binding references source scope "${input.sourceScope}" which is not in the executor's scope stack ${scopeList}.`,
991
- cause: void 0
992
- });
993
- }
994
- if (targetRank === Infinity) {
995
- return yield* new StorageError({
996
- message: `MCP source binding targets scope "${input.targetScope}" which is not in the executor's scope stack ${scopeList}.`,
997
- cause: void 0
998
- });
999
- }
1000
- if (targetRank > sourceSourceRank) {
1001
- return yield* new StorageError({
1002
- message: `MCP source bindings for "${input.sourceId}" cannot be written at outer scope "${input.targetScope}" because the base source lives at "${input.sourceScope}"`,
1003
- cause: void 0
1004
- });
1005
- }
1006
- });
1007
- var canonicalizeCredentialMap = compileHttpNamedCredentialMap;
1008
- var canonicalizeConfiguredValueMap = (values, slotForName) => {
1009
- const next = {};
1010
- for (const [name, value] of Object.entries(values ?? {})) {
1011
- if (typeof value === "string") {
1012
- next[name] = value;
1013
- continue;
1014
- }
1015
- next[name] = {
1016
- kind: "binding",
1017
- slot: slotForName(name),
1018
- prefix: value.prefix
1019
- };
1020
- }
1021
- return next;
1022
- };
1023
- var resolveConfiguredValueMap = (values) => {
1024
- if (!values) return void 0;
1025
- const resolved = {};
1026
- for (const [name, value] of Object.entries(values)) {
1027
- if (typeof value === "string") resolved[name] = value;
1028
- }
1029
- return Object.keys(resolved).length > 0 ? resolved : void 0;
1030
- };
1031
- var authFromOAuth2Source = (oauth2) => oauth2 ? {
1032
- kind: "oauth2",
1033
- connectionSlot: oauth2.connectionSlot,
1034
- clientIdSlot: oauth2.clientIdSlot,
1035
- ...oauth2.clientSecretSlot ? { clientSecretSlot: oauth2.clientSecretSlot } : {}
1036
- } : { kind: "none" };
1037
- var canonicalizeAuth = (auth) => {
1038
- if (!auth || "kind" in auth || !auth.oauth2) return { auth: { kind: "none" }, bindings: [] };
1039
- const oauth = auth.oauth2;
1040
- const bindings = [];
1041
- if (oauth.connection) {
1042
- bindings.push({
1043
- slot: MCP_OAUTH_CONNECTION_SLOT,
1044
- value: httpCredentialInputToBindingValue(oauth.connection)
1045
- });
1046
- }
1047
- if (oauth.clientId) {
1048
- bindings.push({
1049
- slot: MCP_OAUTH_CLIENT_ID_SLOT,
1050
- value: httpCredentialInputToBindingValue(oauth.clientId)
1051
- });
1052
- }
1053
- if (oauth.clientSecret) {
1054
- bindings.push({
1055
- slot: MCP_OAUTH_CLIENT_SECRET_SLOT,
1056
- value: httpCredentialInputToBindingValue(oauth.clientSecret)
1057
- });
1058
- }
1059
- return {
1060
- auth: {
1061
- kind: "oauth2",
1062
- connectionSlot: MCP_OAUTH_CONNECTION_SLOT,
1063
- ...oauth.clientId ? { clientIdSlot: MCP_OAUTH_CLIENT_ID_SLOT } : {},
1064
- ...oauth.clientSecret ? { clientSecretSlot: MCP_OAUTH_CLIENT_SECRET_SLOT } : {}
1065
- },
1066
- bindings
1067
- };
1068
- };
1069
- var makeOAuthProvider = (accessToken) => ({
1070
- get redirectUrl() {
1071
- return "http://localhost/oauth/callback";
1072
- },
1073
- get clientMetadata() {
1074
- return {
1075
- redirect_uris: ["http://localhost/oauth/callback"],
1076
- grant_types: ["authorization_code", "refresh_token"],
1077
- response_types: ["code"],
1078
- token_endpoint_auth_method: "none",
1079
- client_name: "Executor"
1080
- };
1081
- },
1082
- clientInformation: () => void 0,
1083
- saveClientInformation: () => void 0,
1084
- tokens: () => ({ access_token: accessToken, token_type: "Bearer" }),
1085
- saveTokens: () => void 0,
1086
- redirectToAuthorization: async () => {
1087
- throw new Error("MCP OAuth re-authorization required");
1088
- },
1089
- saveCodeVerifier: () => void 0,
1090
- codeVerifier: () => {
1091
- throw new Error("No active PKCE verifier");
1092
- },
1093
- saveDiscoveryState: () => void 0,
1094
- discoveryState: () => void 0
1095
- });
1096
- var resolveSecretBackedMap = (values, ctx) => resolveSharedSecretBackedMap({
1097
- values,
1098
- getSecret: ctx.secrets.get,
1099
- onMissing: (_name, value) => new McpConnectionError({
1100
- transport: "remote",
1101
- message: `Failed to resolve secret "${value.secretId}"`
1102
- }),
1103
- onError: (err, _name, value) => Predicate3.isTagged("SecretOwnedByConnectionError")(err) ? new McpConnectionError({
1104
- transport: "remote",
1105
- message: `Failed to resolve secret "${value.secretId}"`
1106
- }) : err
1107
- }).pipe(
1108
- Effect6.mapError(
1109
- (err) => Predicate3.isTagged("SecretOwnedByConnectionError")(err) ? new McpConnectionError({ transport: "remote", message: "Failed to resolve secret" }) : err
1110
- )
1111
- );
1112
- var credentialInputMapToConfigValues = (values) => {
1113
- if (!values) return void 0;
1114
- const out = {};
1115
- for (const [name, value] of Object.entries(values)) {
1116
- if (typeof value === "string") {
1117
- out[name] = value;
1118
- continue;
1119
- }
1120
- if (value.kind === "secret" && "secretId" in value) {
1121
- out[name] = headerToConfigValue({ secretId: value.secretId, prefix: value.prefix });
1122
- continue;
1123
- }
1124
- if (value.kind === "text") {
1125
- out[name] = value.prefix ? `${value.prefix}${value.text}` : value.text;
1126
- }
1127
- }
1128
- return Object.keys(out).length > 0 ? out : void 0;
1129
- };
1130
- var resolveMcpBindingValueMap = (ctx, values, params) => Effect6.gen(function* () {
1131
- if (!values) return void 0;
1132
- const resolved = {};
1133
- for (const [name, value] of Object.entries(values)) {
1134
- if (typeof value === "string") {
1135
- resolved[name] = value;
1136
- continue;
1137
- }
1138
- const binding = yield* resolveMcpSourceBinding(
1139
- ctx,
1140
- params.sourceId,
1141
- params.sourceScope,
1142
- value.slot
1143
- );
1144
- if (binding?.value.kind === "secret") {
1145
- const secretBinding = binding.value;
1146
- const secret = yield* ctx.secrets.getAtScope(secretBinding.secretId, binding.scopeId).pipe(
1147
- Effect6.catchTag(
1148
- "SecretOwnedByConnectionError",
1149
- () => Effect6.fail(
1150
- new McpAuthRequiredError({
1151
- code: "credential_secret_missing",
1152
- sourceId: params.sourceId,
1153
- sourceScope: params.sourceScope,
1154
- credentialKind: "secret",
1155
- credentialLabel: name,
1156
- slotKey: value.slot,
1157
- secretId: String(secretBinding.secretId),
1158
- message: `Failed to resolve secret for ${params.missingLabel} "${name}"`
1159
- })
1160
- )
1161
- )
1162
- );
1163
- if (secret === null) {
1164
- return yield* new McpAuthRequiredError({
1165
- code: "credential_secret_missing",
1166
- sourceId: params.sourceId,
1167
- sourceScope: params.sourceScope,
1168
- credentialKind: "secret",
1169
- credentialLabel: name,
1170
- slotKey: value.slot,
1171
- secretId: String(secretBinding.secretId),
1172
- message: `Missing secret "${secretBinding.secretId}" for ${params.missingLabel} "${name}"`
1173
- });
1174
- }
1175
- resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
1176
- continue;
1177
- }
1178
- if (binding?.value.kind === "text") {
1179
- resolved[name] = value.prefix ? `${value.prefix}${binding.value.text}` : binding.value.text;
1180
- continue;
1181
- }
1182
- return yield* new McpAuthRequiredError({
1183
- code: "credential_binding_missing",
1184
- sourceId: params.sourceId,
1185
- sourceScope: params.sourceScope,
1186
- credentialKind: "secret",
1187
- credentialLabel: name,
1188
- slotKey: value.slot,
1189
- message: `Missing binding for ${params.missingLabel} "${name}"`
1190
- });
1191
- }
1192
- return Object.keys(resolved).length > 0 ? resolved : void 0;
1193
- });
1194
- var resolveInitialMcpCredentialValueMap = (ctx, values, bindings, targetScope, missingLabel) => Effect6.gen(function* () {
1195
- const bySlot = new Map(bindings.map((binding) => [binding.slot, binding.value]));
1196
- const resolved = {};
1197
- for (const [name, value] of Object.entries(values)) {
1198
- if (typeof value === "string") {
1199
- resolved[name] = value;
1200
- continue;
1201
- }
1202
- const binding = bySlot.get(value.slot);
1203
- if (binding?.kind === "secret") {
1204
- const secret = yield* ctx.secrets.getAtScope(binding.secretId, binding.secretScopeId ?? ScopeId.make(targetScope)).pipe(
1205
- Effect6.catchTag(
1206
- "SecretOwnedByConnectionError",
1207
- () => Effect6.fail(
1208
- new McpConnectionError({
1209
- transport: "remote",
1210
- message: `Failed to resolve secret for ${missingLabel} "${name}"`
1211
- })
1212
- )
1213
- )
1214
- );
1215
- if (secret === null) {
1216
- return yield* new McpConnectionError({
1217
- transport: "remote",
1218
- message: `Missing secret "${binding.secretId}" for ${missingLabel} "${name}"`
1219
- });
1220
- }
1221
- resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
1222
- continue;
1223
- }
1224
- if (binding?.kind === "text") {
1225
- resolved[name] = value.prefix ? `${value.prefix}${binding.text}` : binding.text;
1226
- }
1227
- }
1228
- return Object.keys(resolved).length > 0 ? resolved : void 0;
1229
- });
1230
- var resolveInitialMcpOauthProvider = (ctx, bindings, targetScope) => Effect6.gen(function* () {
1231
- const connection = bindings.find(
1232
- (binding) => binding.slot === MCP_OAUTH_CONNECTION_SLOT && binding.value.kind === "connection"
1233
- );
1234
- if (!connection || connection.value.kind !== "connection") return void 0;
1235
- const connectionId = connection.value.connectionId;
1236
- const accessToken = yield* ctx.connections.accessTokenAtScope(connectionId, ScopeId.make(targetScope)).pipe(
1237
- Effect6.mapError(
1238
- ({ message }) => new McpConnectionError({
1239
- transport: "remote",
1240
- message: `Failed to resolve OAuth connection "${connectionId}": ${message}`
1241
- })
1242
- )
1243
- );
1244
- return makeOAuthProvider(accessToken);
1245
- });
1246
- var resolveMcpHeaderAuth = (ctx, sourceId, sourceScope, auth) => Effect6.gen(function* () {
1247
- if (auth.kind !== "header") return {};
1248
- const binding = yield* resolveMcpSourceBinding(ctx, sourceId, sourceScope, auth.secretSlot);
1249
- if (binding?.value.kind === "secret") {
1250
- const secretBinding = binding.value;
1251
- const secret = yield* ctx.secrets.getAtScope(secretBinding.secretId, binding.scopeId).pipe(
1252
- Effect6.catchTag(
1253
- "SecretOwnedByConnectionError",
1254
- () => Effect6.fail(
1255
- new McpAuthRequiredError({
1256
- code: "credential_secret_missing",
1257
- sourceId,
1258
- sourceScope,
1259
- credentialKind: "secret",
1260
- credentialLabel: auth.headerName,
1261
- slotKey: auth.secretSlot,
1262
- secretId: String(secretBinding.secretId),
1263
- message: `Failed to resolve header auth binding "${auth.secretSlot}"`
1264
- })
1265
- )
1266
- )
1267
- );
1268
- if (secret === null) {
1269
- return yield* new McpAuthRequiredError({
1270
- code: "credential_secret_missing",
1271
- sourceId,
1272
- sourceScope,
1273
- credentialKind: "secret",
1274
- credentialLabel: auth.headerName,
1275
- slotKey: auth.secretSlot,
1276
- secretId: String(secretBinding.secretId),
1277
- message: `Missing secret for header auth binding "${auth.secretSlot}"`
1278
- });
1279
- }
1280
- return { [auth.headerName]: auth.prefix ? `${auth.prefix}${secret}` : secret };
1281
- }
1282
- if (binding?.value.kind === "text") {
1283
- return {
1284
- [auth.headerName]: auth.prefix ? `${auth.prefix}${binding.value.text}` : binding.value.text
1285
- };
1286
- }
1287
- return yield* new McpAuthRequiredError({
1288
- code: "credential_binding_missing",
1289
- sourceId,
1290
- sourceScope,
1291
- credentialKind: "secret",
1292
- credentialLabel: auth.headerName,
1293
- slotKey: auth.secretSlot,
1294
- message: `Missing header auth binding "${auth.secretSlot}"`
1295
- });
1296
- });
1297
- var resolveMcpStoredOauthProvider = (ctx, sourceId, sourceScope, auth) => Effect6.gen(function* () {
1298
- if (auth.kind !== "oauth2") return void 0;
1299
- const binding = yield* resolveMcpSourceBinding(ctx, sourceId, sourceScope, auth.connectionSlot);
1300
- if (binding?.value.kind !== "connection") {
1301
- return yield* new McpAuthRequiredError({
1302
- code: "oauth_connection_missing",
1303
- sourceId,
1304
- sourceScope,
1305
- credentialKind: "connection",
1306
- credentialLabel: "OAuth sign-in",
1307
- slotKey: auth.connectionSlot,
1308
- message: `Missing OAuth connection binding for MCP source "${sourceId}"`
1309
- });
1310
- }
1311
- const connectionId = binding.value.connectionId;
1312
- const accessToken = yield* ctx.connections.accessTokenAtScope(connectionId, binding.scopeId).pipe(
1313
- Effect6.catchTags({
1314
- ConnectionReauthRequiredError: ({ message, connectionId: failedConnectionId }) => Effect6.fail(
1315
- new McpAuthRequiredError({
1316
- code: "oauth_reauth_required",
1317
- sourceId,
1318
- sourceScope,
1319
- credentialKind: "oauth",
1320
- credentialLabel: "OAuth sign-in",
1321
- slotKey: auth.connectionSlot,
1322
- connectionId: String(failedConnectionId),
1323
- message: `OAuth connection "${failedConnectionId}" needs re-authentication: ${message}`
1324
- })
1325
- ),
1326
- ConnectionNotFoundError: ({ connectionId: failedConnectionId }) => Effect6.fail(
1327
- new McpAuthRequiredError({
1328
- code: "oauth_connection_missing",
1329
- sourceId,
1330
- sourceScope,
1331
- credentialKind: "connection",
1332
- credentialLabel: "OAuth sign-in",
1333
- slotKey: auth.connectionSlot,
1334
- connectionId: String(failedConnectionId),
1335
- message: `OAuth connection "${failedConnectionId}" was not found for MCP source "${sourceId}"`
1336
- })
1337
- ),
1338
- ConnectionProviderNotRegisteredError: ({ provider }) => Effect6.fail(
1339
- new McpAuthRequiredError({
1340
- code: "oauth_connection_failed",
1341
- sourceId,
1342
- sourceScope,
1343
- credentialKind: "oauth",
1344
- credentialLabel: "OAuth sign-in",
1345
- slotKey: auth.connectionSlot,
1346
- connectionId: String(connectionId),
1347
- message: `OAuth provider "${provider}" is not registered`
1348
- })
1349
- ),
1350
- ConnectionRefreshNotSupportedError: ({ provider, connectionId: failedConnectionId }) => Effect6.fail(
1351
- new McpAuthRequiredError({
1352
- code: "oauth_connection_failed",
1353
- sourceId,
1354
- sourceScope,
1355
- credentialKind: "oauth",
1356
- credentialLabel: "OAuth sign-in",
1357
- slotKey: auth.connectionSlot,
1358
- connectionId: String(failedConnectionId),
1359
- message: `OAuth provider "${provider}" cannot refresh connection "${failedConnectionId}"`
1360
- })
1361
- ),
1362
- ConnectionRefreshError: ({ message, connectionId: failedConnectionId }) => Effect6.fail(
1363
- new McpAuthRequiredError({
1364
- code: "oauth_connection_failed",
1365
- sourceId,
1366
- sourceScope,
1367
- credentialKind: "oauth",
1368
- credentialLabel: "OAuth sign-in",
1369
- slotKey: auth.connectionSlot,
1370
- connectionId: String(failedConnectionId),
1371
- message: `OAuth connection "${failedConnectionId}" refresh failed: ${message}`
1372
- })
1373
- )
1374
- })
1375
- );
1376
- return makeOAuthProvider(accessToken);
1377
- });
1378
- var resolveConnectorInput = (sourceId, sourceScope, sd, ctx, allowStdio) => {
1379
- if (sd.transport === "stdio") {
1380
- if (!allowStdio) {
1381
- return Effect6.fail(
1382
- new McpConnectionError({
1383
- transport: "stdio",
1384
- message: "MCP stdio transport is disabled. Enable it by passing `dangerouslyAllowStdioMCP: true` to mcpPlugin() \u2014 only safe for trusted local contexts."
1385
- })
1386
- );
1387
- }
1388
- return Effect6.succeed({
1389
- transport: "stdio",
1390
- command: sd.command,
1391
- args: sd.args,
1392
- env: sd.env,
1393
- cwd: sd.cwd
1394
- });
1395
- }
1396
- return Effect6.gen(function* () {
1397
- const resolvedHeaders = yield* resolveMcpBindingValueMap(ctx, sd.headers, {
1398
- sourceId,
1399
- sourceScope,
1400
- missingLabel: "header"
1401
- });
1402
- const resolvedQueryParams = yield* resolveMcpBindingValueMap(ctx, sd.queryParams, {
1403
- sourceId,
1404
- sourceScope,
1405
- missingLabel: "query parameter"
1406
- });
1407
- const headers = { ...resolvedHeaders ?? {} };
1408
- const auth = sd.auth;
1409
- if (auth.kind === "header") {
1410
- Object.assign(headers, yield* resolveMcpHeaderAuth(ctx, sourceId, sourceScope, auth));
1411
- }
1412
- const authProvider = yield* resolveMcpStoredOauthProvider(ctx, sourceId, sourceScope, auth);
1413
- return {
1414
- transport: "remote",
1415
- endpoint: sd.endpoint,
1416
- remoteTransport: sd.remoteTransport,
1417
- queryParams: resolvedQueryParams,
1418
- headers: Object.keys(headers).length > 0 ? headers : void 0,
1419
- authProvider
1420
- };
1421
- });
1422
- };
1423
- var makeRuntime = () => Effect6.gen(function* () {
1424
- const cacheScope = yield* Scope.make();
1425
- const pendingConnectors = /* @__PURE__ */ new Map();
1426
- const connectionCache = yield* ScopedCache2.make({
1427
- lookup: (key) => Effect6.acquireRelease(
1428
- Effect6.suspend(() => {
1429
- const connector = pendingConnectors.get(key);
1430
- if (!connector) {
1431
- return Effect6.fail(
1432
- new McpConnectionError({
1433
- transport: "auto",
1434
- message: `No pending connector for key: ${key}`
1435
- })
1436
- );
1437
- }
1438
- return connector;
1439
- }),
1440
- (connection) => Effect6.ignore(
1441
- Effect6.tryPromise({
1442
- try: () => connection.close(),
1443
- catch: () => new McpConnectionError({
1444
- transport: "auto",
1445
- message: "Failed to close MCP connection"
1446
- })
1447
- })
1448
- )
1449
- ),
1450
- capacity: 64,
1451
- timeToLive: Duration2.minutes(5)
1452
- }).pipe(Scope.provide(cacheScope));
1453
- return { connectionCache, pendingConnectors, cacheScope };
1454
- });
1455
- var authToConfig = (auth) => {
1456
- if (!auth) return void 0;
1457
- if ("kind" in auth) return { kind: "none" };
1458
- const connection = auth.oauth2?.connection;
1459
- if (!connection || typeof connection === "string" || connection.kind !== "connection") {
1460
- return void 0;
1461
- }
1462
- return {
1463
- kind: "oauth2",
1464
- connectionId: connection.connectionId
1465
- };
1466
- };
1467
- var toCredentialInput = (bySlot, configured) => {
1468
- if (typeof configured === "string") return configured;
1469
- const value = bySlot.get(configured.slot);
1470
- if (!value) return void 0;
1471
- if (value.kind === "secret") {
1472
- return {
1473
- kind: "secret",
1474
- secretId: value.secretId,
1475
- ...value.secretScopeId ? { secretScope: value.secretScopeId } : {},
1476
- ...configured.prefix ? { prefix: configured.prefix } : {}
1477
- };
1478
- }
1479
- if (value.kind === "text") return value.text;
1480
- return void 0;
1481
- };
1482
- var toCredentialInputMap = (bySlot, values) => {
1483
- if (!values) return void 0;
1484
- const out = {};
1485
- for (const [name, configured] of Object.entries(values)) {
1486
- const input = toCredentialInput(bySlot, configured);
1487
- if (input !== void 0) out[name] = input;
1488
- }
1489
- return Object.keys(out).length > 0 ? out : void 0;
1490
- };
1491
- var toAuthInput = (bySlot, auth) => {
1492
- if (auth.kind === "none") return { kind: "none" };
1493
- if (auth.kind === "header") {
1494
- const value = bySlot.get(auth.secretSlot);
1495
- if (value?.kind !== "secret") return void 0;
1496
- return {
1497
- kind: "none"
1498
- };
1499
- }
1500
- const connection = bySlot.get(auth.connectionSlot);
1501
- return {
1502
- oauth2: {
1503
- ...connection?.kind === "connection" ? { connection: { kind: "connection", connectionId: connection.connectionId } } : {}
1504
- }
1505
- };
1506
- };
1507
- var inputFormFromStored = (bindings, stored, scope, sourceName, namespace) => {
1508
- if (stored.transport === "stdio") {
1509
- return {
1510
- transport: "stdio",
1511
- scope,
1512
- name: sourceName,
1513
- namespace,
1514
- command: stored.command,
1515
- args: stored.args ? [...stored.args] : void 0,
1516
- env: stored.env,
1517
- cwd: stored.cwd
1518
- };
1519
- }
1520
- const bySlot = new Map(bindings.map((b) => [b.slotKey, b.value]));
1521
- return {
1522
- transport: "remote",
1523
- scope,
1524
- name: sourceName,
1525
- namespace,
1526
- endpoint: stored.endpoint,
1527
- remoteTransport: stored.remoteTransport,
1528
- headers: toCredentialInputMap(bySlot, stored.headers),
1529
- queryParams: toCredentialInputMap(bySlot, stored.queryParams),
1530
- auth: toAuthInput(bySlot, stored.auth)
1531
- };
1532
- };
1533
- var toMcpConfigEntry = (namespace, sourceName, config) => {
1534
- if (config.transport === "stdio") {
1535
- const entry2 = {
1536
- kind: "mcp",
1537
- transport: "stdio",
1538
- name: sourceName,
1539
- command: config.command,
1540
- args: config.args,
1541
- env: config.env,
1542
- cwd: config.cwd,
1543
- namespace
1544
- };
1545
- return entry2;
1546
- }
1547
- const entry = {
1548
- kind: "mcp",
1549
- transport: "remote",
1550
- name: sourceName,
1551
- endpoint: config.endpoint,
1552
- remoteTransport: config.remoteTransport,
1553
- queryParams: credentialInputMapToConfigValues(config.queryParams),
1554
- headers: credentialInputMapToConfigValues(config.headers),
1555
- namespace,
1556
- auth: authToConfig(config.auth)
1557
- };
1558
- return entry;
1559
- };
1560
- var mcpPlugin = definePlugin((options) => {
1561
- const allowStdio = options?.dangerouslyAllowStdioMCP ?? false;
1562
- const runtimeRef = { current: null };
1563
- const ensureRuntime = () => runtimeRef.current ? Effect6.succeed(runtimeRef.current) : makeRuntime().pipe(
1564
- Effect6.tap(
1565
- (rt) => Effect6.sync(() => {
1566
- runtimeRef.current = rt;
1567
- })
1568
- )
1569
- );
1570
- return {
1571
- id: "mcp",
1572
- packageName: "@executor-js/plugin-mcp",
1573
- sourcePresets: allowStdio ? mcpPresets.map((preset) => ({
1574
- ...preset,
1575
- transport: "transport" in preset ? preset.transport : "remote"
1576
- })) : mcpPresets.filter((preset) => !("transport" in preset && preset.transport === "stdio")).map((preset) => ({
1577
- ...preset,
1578
- transport: "remote"
1579
- })),
1580
- // Surfaced to the client bundle via the Vite plugin (see
1581
- // `@executor-js/vite-plugin`). The MCP `./client` factory reads
1582
- // `allowStdio` and gates the stdio tab + presets in AddMcpSource —
1583
- // so the server's `dangerouslyAllowStdioMCP` flag is the single
1584
- // source of truth for both runtime and UI.
1585
- clientConfig: { allowStdio },
1586
- schema: mcpSchema,
1587
- storage: (deps) => makeMcpStore(deps),
1588
- extension: (ctx) => {
1589
- const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
1590
- const probeEndpoint = (input) => Effect6.gen(function* () {
1591
- const endpoint = typeof input === "string" ? input : input.endpoint;
1592
- const trimmed = endpoint.trim();
1593
- if (!trimmed) {
1594
- return yield* new McpConnectionError({
1595
- transport: "remote",
1596
- message: "Endpoint URL is required"
1597
- });
1598
- }
1599
- const name = yield* Effect6.try({
1600
- try: () => new URL(trimmed).hostname,
1601
- catch: () => "mcp"
1602
- }).pipe(Effect6.orElseSucceed(() => "mcp"));
1603
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
1604
- const probeHeaders = typeof input === "string" ? void 0 : yield* resolveSecretBackedMap(input.headers, ctx);
1605
- const probeQueryParams = typeof input === "string" ? void 0 : yield* resolveSecretBackedMap(input.queryParams, ctx);
1606
- const connector = createMcpConnector({
1607
- transport: "remote",
1608
- endpoint: trimmed,
1609
- headers: probeHeaders,
1610
- queryParams: probeQueryParams
1611
- });
1612
- const result = yield* discoverTools(connector).pipe(
1613
- Effect6.map((m) => ({ ok: true, manifest: m })),
1614
- Effect6.catch(() => Effect6.succeed({ ok: false, manifest: null })),
1615
- Effect6.withSpan("mcp.plugin.discover_tools")
1616
- );
1617
- if (result.ok && result.manifest) {
1618
- return {
1619
- connected: true,
1620
- requiresOAuth: false,
1621
- supportsDynamicRegistration: false,
1622
- name: result.manifest.server?.name ?? name,
1623
- namespace,
1624
- toolCount: result.manifest.tools.length,
1625
- serverName: result.manifest.server?.name ?? null
1626
- };
1627
- }
1628
- const shape = yield* probeMcpEndpointShape(trimmed, {
1629
- httpClientLayer,
1630
- headers: probeHeaders,
1631
- queryParams: probeQueryParams
1632
- });
1633
- if (shape.kind !== "mcp") {
1634
- return yield* new McpConnectionError({
1635
- transport: "remote",
1636
- message: userFacingProbeMessage(shape)
1637
- });
1638
- }
1639
- const probeResult = yield* ctx.oauth.probe({
1640
- endpoint: trimmed,
1641
- headers: probeHeaders,
1642
- queryParams: probeQueryParams
1643
- }).pipe(
1644
- Effect6.map((oauth) => ({ ok: true, oauth })),
1645
- Effect6.catch(() => Effect6.succeed({ ok: false, oauth: null })),
1646
- Effect6.withSpan("mcp.plugin.probe_oauth")
1647
- );
1648
- if (probeResult.ok) {
1649
- return {
1650
- connected: false,
1651
- requiresOAuth: true,
1652
- supportsDynamicRegistration: probeResult.oauth.supportsDynamicRegistration,
1653
- name,
1654
- namespace,
1655
- toolCount: null,
1656
- serverName: null
1657
- };
1658
- }
1659
- return yield* new McpConnectionError({
1660
- transport: "remote",
1661
- message: "This server requires authentication, but OAuth metadata wasn't found. Add credentials (Authorization header, query parameter, or API key) below and retry."
1662
- });
1663
- }).pipe(
1664
- Effect6.withSpan("mcp.plugin.probe_endpoint", {
1665
- attributes: { "mcp.endpoint": typeof input === "string" ? input : input.endpoint }
1666
- })
1667
- );
1668
- const configFile = options?.configFile;
1669
- const addSource = (config) => Effect6.gen(function* () {
1670
- const namespace = normalizeNamespace(config);
1671
- const canonicalRemote = config.transport === "remote" ? {
1672
- headers: canonicalizeConfiguredValueMap(config.headers, mcpHeaderSlot),
1673
- queryParams: canonicalizeConfiguredValueMap(
1674
- config.queryParams,
1675
- mcpQueryParamSlot
1676
- )
1677
- } : null;
1678
- const initialRemote = config.transport === "remote" && config.credentials ? {
1679
- scope: config.credentials.scope,
1680
- headers: config.credentials.headers !== void 0 ? canonicalizeCredentialMap(config.credentials.headers, mcpHeaderSlot) : null,
1681
- queryParams: config.credentials.queryParams !== void 0 ? canonicalizeCredentialMap(config.credentials.queryParams, mcpQueryParamSlot) : null,
1682
- auth: config.credentials.auth !== void 0 ? canonicalizeAuth(config.credentials.auth) : null
1683
- } : null;
1684
- const remoteAuth = config.transport === "remote" ? config.oauth2 ? authFromOAuth2Source(config.oauth2) : initialRemote?.auth?.auth ?? { kind: "none" } : null;
1685
- const remoteCredentials = canonicalRemote && remoteAuth ? {
1686
- headers: canonicalRemote.headers,
1687
- queryParams: canonicalRemote.queryParams,
1688
- auth: remoteAuth
1689
- } : void 0;
1690
- const initialBindings = [
1691
- ...initialRemote?.headers?.bindings ?? [],
1692
- ...initialRemote?.queryParams?.bindings ?? [],
1693
- ...initialRemote?.auth?.bindings ?? []
1694
- ];
1695
- if (initialRemote && initialBindings.length > 0) {
1696
- yield* validateMcpBindingTarget(ctx, {
1697
- sourceId: namespace,
1698
- sourceScope: config.scope,
1699
- targetScope: initialRemote.scope
1700
- });
1701
- }
1702
- const sd = toStoredSourceData(config, remoteCredentials);
1703
- const initialQueryParams = initialRemote?.queryParams && (yield* resolveInitialMcpCredentialValueMap(
1704
- ctx,
1705
- canonicalRemote?.queryParams ?? initialRemote.queryParams.values,
1706
- initialRemote.queryParams.bindings,
1707
- initialRemote.scope,
1708
- "query parameter"
1709
- ));
1710
- const initialHeaders = initialRemote?.headers && (yield* resolveInitialMcpCredentialValueMap(
1711
- ctx,
1712
- canonicalRemote?.headers ?? initialRemote.headers.values,
1713
- initialRemote.headers.bindings,
1714
- initialRemote.scope,
1715
- "header"
1716
- ));
1717
- const remoteQueryParams = {
1718
- ...config.transport === "remote" ? resolveConfiguredValueMap(config.queryParams) ?? {} : {},
1719
- ...initialQueryParams || {}
1720
- };
1721
- const remoteHeaders = {
1722
- ...config.transport === "remote" ? resolveConfiguredValueMap(config.headers) ?? {} : {},
1723
- ...initialHeaders || {}
1724
- };
1725
- const initialAuthProvider = initialRemote?.auth !== null && initialRemote?.auth !== void 0 ? yield* resolveInitialMcpOauthProvider(
1726
- ctx,
1727
- initialRemote.auth.bindings,
1728
- initialRemote.scope
1729
- ) : void 0;
1730
- const resolved = config.transport === "remote" ? Result.succeed({
1731
- transport: "remote",
1732
- endpoint: config.endpoint,
1733
- remoteTransport: config.remoteTransport ?? "auto",
1734
- queryParams: Object.keys(remoteQueryParams).length > 0 ? remoteQueryParams : void 0,
1735
- headers: Object.keys(remoteHeaders).length > 0 ? remoteHeaders : void 0,
1736
- authProvider: initialAuthProvider
1737
- }) : yield* resolveConnectorInput(namespace, config.scope, sd, ctx, allowStdio).pipe(
1738
- Effect6.result,
1739
- Effect6.withSpan("mcp.plugin.resolve_connector", {
1740
- attributes: {
1741
- "mcp.source.namespace": namespace,
1742
- "mcp.source.transport": sd.transport
1743
- }
1744
- })
1745
- );
1746
- if (Result.isFailure(resolved) && sd.transport === "stdio") {
1747
- if (Predicate3.isTagged(resolved.failure, "McpAuthRequiredError")) {
1748
- return yield* new McpConnectionError({
1749
- transport: sd.transport,
1750
- message: resolved.failure.message
1751
- });
1752
- }
1753
- return yield* Effect6.fail(resolved.failure);
1754
- }
1755
- const discovery = Result.isSuccess(resolved) ? yield* discoverTools(createMcpConnector(resolved.success)).pipe(
1756
- Effect6.mapError(
1757
- ({ message }) => new McpToolDiscoveryError({
1758
- stage: "list_tools",
1759
- message: `MCP discovery failed: ${message}`
1760
- })
1761
- ),
1762
- Effect6.result,
1763
- Effect6.withSpan("mcp.plugin.discover_tools", {
1764
- attributes: { "mcp.source.namespace": namespace }
1765
- })
1766
- ) : Result.fail(resolved.failure);
1767
- const manifest = Result.isSuccess(discovery) ? discovery.success : { server: void 0, tools: [] };
1768
- const sourceName = config.name ?? manifest.server?.name ?? namespace;
1769
- yield* ctx.transaction(
1770
- Effect6.gen(function* () {
1771
- yield* ctx.storage.removeBindingsByNamespace(namespace, config.scope);
1772
- yield* ctx.storage.removeSource(namespace, config.scope);
1773
- yield* ctx.storage.putSource({
1774
- namespace,
1775
- scope: config.scope,
1776
- name: sourceName,
1777
- config: sd
1778
- });
1779
- yield* ctx.storage.putBindings(
1780
- namespace,
1781
- config.scope,
1782
- manifest.tools.map((e) => ({
1783
- toolId: `${namespace}.${e.toolId}`,
1784
- binding: toBinding(e)
1785
- }))
1786
- );
1787
- yield* ctx.core.sources.register({
1788
- id: namespace,
1789
- scope: config.scope,
1790
- kind: "mcp",
1791
- name: sourceName,
1792
- url: sd.transport === "remote" ? sd.endpoint : void 0,
1793
- canRemove: true,
1794
- canRefresh: true,
1795
- canEdit: sd.transport === "remote",
1796
- tools: manifest.tools.map((e) => ({
1797
- name: e.toolId,
1798
- description: e.description ?? `MCP tool: ${e.toolName}`,
1799
- inputSchema: e.inputSchema,
1800
- outputSchema: mcpCallToolResultOutputSchema(e.outputSchema)
1801
- }))
1802
- });
1803
- if (initialRemote && initialBindings.length > 0) {
1804
- yield* ctx.credentialBindings.replaceForSource({
1805
- targetScope: ScopeId.make(initialRemote.scope),
1806
- pluginId: MCP_PLUGIN_ID,
1807
- sourceId: namespace,
1808
- sourceScope: ScopeId.make(config.scope),
1809
- slotPrefixes: [
1810
- ...initialRemote.headers !== null ? ["header:"] : [],
1811
- ...initialRemote.queryParams !== null ? ["query_param:"] : [],
1812
- ...initialRemote.auth !== null ? ["auth:"] : []
1813
- ],
1814
- bindings: initialBindings.map((binding) => ({
1815
- slotKey: binding.slot,
1816
- value: binding.value
1817
- }))
1818
- });
1819
- }
1820
- })
1821
- ).pipe(
1822
- Effect6.withSpan("mcp.plugin.persist_source", {
1823
- attributes: {
1824
- "mcp.source.namespace": namespace,
1825
- "mcp.source.tool_count": manifest.tools.length
1826
- }
1827
- })
1828
- );
1829
- if (configFile) {
1830
- yield* configFile.upsertSource(toMcpConfigEntry(namespace, sourceName, config)).pipe(Effect6.withSpan("mcp.plugin.config_file.upsert"));
1831
- }
1832
- if (Result.isFailure(discovery)) {
1833
- if (Predicate3.isTagged(discovery.failure, "McpAuthRequiredError")) {
1834
- return yield* new McpConnectionError({
1835
- transport: sd.transport,
1836
- message: discovery.failure.message
1837
- });
1838
- }
1839
- return yield* Effect6.fail(discovery.failure);
1840
- }
1841
- return { toolCount: manifest.tools.length, namespace };
1842
- }).pipe(
1843
- Effect6.withSpan("mcp.plugin.add_source", {
1844
- attributes: {
1845
- "mcp.source.transport": config.transport,
1846
- "mcp.source.name": config.name
1847
- }
1848
- })
1849
- );
1850
- const removeSource = (namespace, scope) => Effect6.gen(function* () {
1851
- yield* ctx.transaction(
1852
- Effect6.gen(function* () {
1853
- yield* ctx.credentialBindings.removeForSource({
1854
- pluginId: MCP_PLUGIN_ID,
1855
- sourceId: namespace,
1856
- sourceScope: ScopeId.make(scope)
1857
- });
1858
- yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1859
- yield* ctx.storage.removeSource(namespace, scope);
1860
- yield* ctx.core.sources.unregister({ id: namespace, targetScope: scope });
1861
- })
1862
- ).pipe(Effect6.withSpan("mcp.plugin.persist_remove"));
1863
- if (configFile) {
1864
- yield* configFile.removeSource(namespace).pipe(Effect6.withSpan("mcp.plugin.config_file.remove"));
1865
- }
1866
- }).pipe(
1867
- Effect6.withSpan("mcp.plugin.remove_source", {
1868
- attributes: { "mcp.source.namespace": namespace }
1869
- })
1870
- );
1871
- const refreshSource = (namespace, scope) => Effect6.gen(function* () {
1872
- const sd = yield* ctx.storage.getSourceConfig(namespace, scope).pipe(
1873
- Effect6.withSpan("mcp.plugin.load_source_config", {
1874
- attributes: { "mcp.source.namespace": namespace }
1875
- })
1876
- );
1877
- if (!sd) {
1878
- return yield* new McpConnectionError({
1879
- transport: "remote",
1880
- message: `No stored config for MCP source "${namespace}"`
1881
- });
1882
- }
1883
- const ci = yield* resolveConnectorInput(namespace, scope, sd, ctx, allowStdio).pipe(
1884
- Effect6.catchTag(
1885
- "McpAuthRequiredError",
1886
- ({ message }) => Effect6.fail(new McpConnectionError({ transport: sd.transport, message }))
1887
- ),
1888
- Effect6.withSpan("mcp.plugin.resolve_connector", {
1889
- attributes: {
1890
- "mcp.source.namespace": namespace,
1891
- "mcp.source.transport": sd.transport
1892
- }
1893
- })
1894
- );
1895
- const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
1896
- Effect6.mapError(
1897
- ({ message }) => new McpToolDiscoveryError({
1898
- stage: "list_tools",
1899
- message: `MCP refresh failed: ${message}`
1900
- })
1901
- ),
1902
- Effect6.withSpan("mcp.plugin.discover_tools", {
1903
- attributes: { "mcp.source.namespace": namespace }
1904
- })
1905
- );
1906
- const existing = yield* ctx.storage.getSource(namespace, scope);
1907
- const sourceName = manifest.server?.name ?? existing?.name ?? namespace;
1908
- yield* ctx.transaction(
1909
- Effect6.gen(function* () {
1910
- yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1911
- yield* ctx.core.sources.unregister({ id: namespace, targetScope: scope });
1912
- yield* ctx.storage.putBindings(
1913
- namespace,
1914
- scope,
1915
- manifest.tools.map((e) => ({
1916
- toolId: `${namespace}.${e.toolId}`,
1917
- binding: toBinding(e)
1918
- }))
1919
- );
1920
- yield* ctx.core.sources.register({
1921
- id: namespace,
1922
- scope,
1923
- kind: "mcp",
1924
- name: sourceName,
1925
- url: sd.transport === "remote" ? sd.endpoint : void 0,
1926
- canRemove: true,
1927
- canRefresh: true,
1928
- canEdit: sd.transport === "remote",
1929
- tools: manifest.tools.map((e) => ({
1930
- name: e.toolId,
1931
- description: e.description ?? `MCP tool: ${e.toolName}`,
1932
- inputSchema: e.inputSchema,
1933
- outputSchema: mcpCallToolResultOutputSchema(e.outputSchema)
1934
- }))
1935
- });
1936
- })
1937
- ).pipe(
1938
- Effect6.withSpan("mcp.plugin.persist_source", {
1939
- attributes: {
1940
- "mcp.source.namespace": namespace,
1941
- "mcp.source.tool_count": manifest.tools.length
1942
- }
1943
- })
1944
- );
1945
- return { toolCount: manifest.tools.length };
1946
- }).pipe(
1947
- Effect6.withSpan("mcp.plugin.refresh_source", {
1948
- attributes: { "mcp.source.namespace": namespace }
1949
- })
1950
- );
1951
- const getSource = (namespace, scope) => ctx.storage.getSource(namespace, scope).pipe(
1952
- Effect6.withSpan("mcp.plugin.get_source", {
1953
- attributes: { "mcp.source.namespace": namespace }
1954
- })
1955
- );
1956
- return {
1957
- probeEndpoint,
1958
- addSource,
1959
- removeSource,
1960
- refreshSource,
1961
- getSource
1962
- };
1963
- },
1964
- sourceConfigure: {
1965
- type: "mcp",
1966
- schema: McpConfigureSourcePayloadSchema,
1967
- configure: ({ ctx, sourceId, sourceScope, targetScope, config }) => Effect6.gen(function* () {
1968
- const input = config;
1969
- const existing = yield* ctx.storage.getSource(sourceId, sourceScope);
1970
- if (!existing || existing.config.transport !== "remote") return;
1971
- const canonicalHeaders = input.headers !== void 0 ? canonicalizeCredentialMap(input.headers, mcpHeaderSlot) : null;
1972
- const canonicalQueryParams = input.queryParams !== void 0 ? canonicalizeCredentialMap(input.queryParams, mcpQueryParamSlot) : null;
1973
- const canonicalAuth = input.auth !== void 0 ? canonicalizeAuth(input.auth) : null;
1974
- const directBindings = [
1975
- ...canonicalHeaders?.bindings ?? [],
1976
- ...canonicalQueryParams?.bindings ?? [],
1977
- ...canonicalAuth?.bindings ?? []
1978
- ];
1979
- if (directBindings.length > 0) {
1980
- yield* validateMcpBindingTarget(ctx, {
1981
- sourceId,
1982
- sourceScope,
1983
- targetScope
1984
- });
1985
- }
1986
- const updatedConfig = {
1987
- ...existing.config,
1988
- ...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
1989
- ...canonicalHeaders ? { headers: canonicalHeaders.values } : {},
1990
- ...canonicalAuth ? { auth: canonicalAuth.auth } : {},
1991
- ...canonicalQueryParams ? { queryParams: canonicalQueryParams.values } : {}
1992
- };
1993
- const affectedPrefixes = [
1994
- ...input.headers !== void 0 ? ["header:"] : [],
1995
- ...input.queryParams !== void 0 ? ["query_param:"] : [],
1996
- ...input.auth !== void 0 ? ["auth:"] : []
1997
- ];
1998
- const sourceName = input.name?.trim() || existing.name;
1999
- yield* ctx.transaction(
2000
- Effect6.gen(function* () {
2001
- yield* ctx.storage.putSource({
2002
- namespace: sourceId,
2003
- scope: sourceScope,
2004
- name: sourceName,
2005
- config: updatedConfig
2006
- });
2007
- if (affectedPrefixes.length > 0 || directBindings.length > 0) {
2008
- yield* ctx.credentialBindings.replaceForSource({
2009
- targetScope: ScopeId.make(targetScope),
2010
- pluginId: MCP_PLUGIN_ID,
2011
- sourceId,
2012
- sourceScope: ScopeId.make(sourceScope),
2013
- slotPrefixes: affectedPrefixes,
2014
- bindings: directBindings.map((binding) => ({
2015
- slotKey: binding.slot,
2016
- value: binding.value
2017
- }))
2018
- });
2019
- }
2020
- })
2021
- );
2022
- if (options?.configFile) {
2023
- const bindings = yield* ctx.credentialBindings.listForSource({
2024
- pluginId: MCP_PLUGIN_ID,
2025
- sourceId,
2026
- sourceScope: ScopeId.make(sourceScope)
2027
- });
2028
- const inputForm = inputFormFromStored(
2029
- bindings,
2030
- updatedConfig,
2031
- sourceScope,
2032
- sourceName,
2033
- sourceId
2034
- );
2035
- yield* options.configFile.upsertSource(toMcpConfigEntry(sourceId, sourceName, inputForm)).pipe(Effect6.withSpan("mcp.plugin.config_file.upsert"));
2036
- }
2037
- })
2038
- },
2039
- staticSources: (self) => [
2040
- {
2041
- id: "mcp",
2042
- kind: "executor",
2043
- name: "MCP",
2044
- tools: [
2045
- tool({
2046
- name: "probeEndpoint",
2047
- description: "Probe a remote MCP endpoint before adding it. If the result requires OAuth, call `executor.coreTools.oauth.probe` and `executor.coreTools.oauth.start` with `credentialScope` set to the user's chosen personal or organization credential scope first, then pass the resulting connection through `addSource` credentials or `mcp.configureSource`.",
2048
- inputSchema: McpProbeEndpointInputStandardSchema,
2049
- outputSchema: McpProbeEndpointOutputStandardSchema,
2050
- execute: (input) => self.probeEndpoint(input).pipe(
2051
- Effect6.map(ToolResult.ok),
2052
- Effect6.catchTag(
2053
- "McpConnectionError",
2054
- ({ message, transport }) => Effect6.succeed(mcpToolFailure("mcp_connection_failed", message, { transport }))
2055
- )
2056
- )
2057
- }),
2058
- tool({
2059
- name: "getSource",
2060
- description: "Inspect an existing MCP source, including transport, endpoint/command, auth mode, configured headers/query params, and credential slots. Use this before repairing an existing source with `mcp.configureSource`, `secrets.create`, or `oauth.start`.",
2061
- inputSchema: McpGetSourceInputStandardSchema,
2062
- outputSchema: McpGetSourceOutputStandardSchema,
2063
- execute: (input, { ctx }) => {
2064
- const args = input;
2065
- return Effect6.map(
2066
- self.getSource(args.namespace, resolveStaticScopeInput(ctx, args.scope)),
2067
- (source) => ToolResult.ok({ source })
2068
- );
2069
- }
2070
- }),
2071
- tool({
2072
- name: "addSource",
2073
- description: "Add an MCP source and register its tools. Executor chooses the source install scope (local scope locally, organization scope in cloud) and returns it as `source`. For remote OAuth-protected servers, first use `probeEndpoint` and the core OAuth browser handoff (`oauth.probe`, `oauth.start` with the user's chosen `credentialScope`), then bind the completed connection with `mcp.configureSource` if needed. For header/API-key auth, first call `secrets.create` at the user's chosen credential scope so the value is entered in the browser, then pass the secret reference in `credentials`. Remote sources are still saved if discovery fails; inspect the returned `discovery` field and use `sources.refresh` after credentials or network access are fixed.",
2074
- annotations: {
2075
- requiresApproval: true,
2076
- approvalDescription: "Add an MCP source"
2077
- },
2078
- inputSchema: McpAddSourceInputStandardSchema,
2079
- outputSchema: McpAddSourceOutputStandardSchema,
2080
- execute: (rawInput, { ctx }) => {
2081
- const input = rawInput;
2082
- const sourceScope = defaultSourceInstallScopeId(ctx.scopes);
2083
- if (sourceScope === null) {
2084
- return Effect6.succeed(
2085
- mcpToolFailure(
2086
- "source_scope_unavailable",
2087
- "Cannot add an MCP source because this executor has no source install scope."
2088
- )
2089
- );
2090
- }
2091
- const normalizedInput = {
2092
- ...input,
2093
- scope: sourceScope
2094
- };
2095
- const added = self.addSource(normalizedInput).pipe(
2096
- Effect6.map(
2097
- (result) => ToolResult.ok({
2098
- ...result,
2099
- source: { id: result.namespace, scope: sourceScope },
2100
- discovery: { status: "ok" }
2101
- })
2102
- )
2103
- );
2104
- if (normalizedInput.transport !== "remote") return added;
2105
- const savedWithDiscoveryFailure = (failure) => Effect6.succeed(
2106
- ToolResult.ok({
2107
- namespace: normalizedInput.namespace ?? deriveMcpNamespace({
2108
- name: normalizedInput.name,
2109
- endpoint: normalizedInput.endpoint
2110
- }),
2111
- source: {
2112
- id: normalizedInput.namespace ?? deriveMcpNamespace({
2113
- name: normalizedInput.name,
2114
- endpoint: normalizedInput.endpoint
2115
- }),
2116
- scope: sourceScope
2117
- },
2118
- toolCount: 0,
2119
- discovery: {
2120
- status: "failed",
2121
- message: failure.message,
2122
- ...failure.stage ? { stage: failure.stage } : {}
2123
- }
2124
- })
2125
- );
2126
- return added.pipe(
2127
- Effect6.catchTags({
2128
- McpToolDiscoveryError: savedWithDiscoveryFailure,
2129
- McpConnectionError: ({ message }) => Effect6.succeed(
2130
- ToolResult.ok({
2131
- namespace: normalizedInput.namespace ?? deriveMcpNamespace({
2132
- name: normalizedInput.name,
2133
- endpoint: normalizedInput.endpoint
2134
- }),
2135
- source: {
2136
- id: normalizedInput.namespace ?? deriveMcpNamespace({
2137
- name: normalizedInput.name,
2138
- endpoint: normalizedInput.endpoint
2139
- }),
2140
- scope: sourceScope
2141
- },
2142
- toolCount: 0,
2143
- discovery: {
2144
- status: "failed",
2145
- message
2146
- }
2147
- })
2148
- )
2149
- })
2150
- );
2151
- }
2152
- }),
2153
- tool({
2154
- name: "configureSource",
2155
- description: 'Configure an existing remote MCP source with concrete fields. Use `source` returned by `mcp.addSource` or `sources.list`. The top-level `scope` is the credential target scope for bindings; in cloud, choose the user or organization credential scope deliberately. Pass secret refs as `{kind:"secret", secretId}` and OAuth connections as `{kind:"connection", connectionId}`.',
2156
- annotations: {
2157
- requiresApproval: true,
2158
- approvalDescription: "Configure an MCP source"
2159
- },
2160
- inputSchema: McpStaticConfigureSourceInputStandardSchema,
2161
- outputSchema: McpStaticConfigureSourceOutputStandardSchema,
2162
- execute: (rawInput, { ctx }) => Effect6.gen(function* () {
2163
- const { source, ...config } = rawInput;
2164
- const sourceScope = resolveStaticScopeInput(ctx, source.scope);
2165
- const targetScope = resolveStaticScopeInput(ctx, config.scope);
2166
- yield* ctx.core.sources.configure({
2167
- source: { id: source.id, scope: sourceScope },
2168
- scope: targetScope,
2169
- type: "mcp",
2170
- config: {
2171
- ...config.name !== void 0 ? { name: config.name } : {},
2172
- ...config.endpoint !== void 0 ? { endpoint: config.endpoint } : {},
2173
- ...config.headers !== void 0 ? { headers: config.headers } : {},
2174
- ...config.queryParams !== void 0 ? { queryParams: config.queryParams } : {},
2175
- ...config.auth !== void 0 ? { auth: config.auth } : {}
2176
- }
2177
- });
2178
- return ToolResult.ok({ configured: true });
2179
- })
2180
- })
2181
- ]
2182
- }
2183
- ],
2184
- invokeTool: ({ ctx, toolRow, args, elicit }) => Effect6.gen(function* () {
2185
- const runtime = yield* ensureRuntime();
2186
- const toolScope = toolRow.scope_id;
2187
- const entry = yield* ctx.storage.getBinding(toolRow.id, toolScope).pipe(
2188
- Effect6.withSpan("mcp.plugin.load_binding", {
2189
- attributes: { "mcp.tool.name": toolRow.id }
2190
- })
2191
- );
2192
- if (!entry) {
2193
- return yield* new McpInvocationError({
2194
- toolName: toolRow.id,
2195
- message: `No MCP binding found for tool "${toolRow.id}"`
2196
- });
2197
- }
2198
- const sd = yield* ctx.storage.getSourceConfig(entry.namespace, toolScope).pipe(
2199
- Effect6.withSpan("mcp.plugin.load_source_config", {
2200
- attributes: { "mcp.source.namespace": entry.namespace }
2201
- })
2202
- );
2203
- if (!sd) {
2204
- return yield* new McpConnectionError({
2205
- transport: "auto",
2206
- message: `No MCP source config for namespace "${entry.namespace}"`
2207
- });
2208
- }
2209
- const raw = yield* invokeMcpTool({
2210
- toolId: toolRow.id,
2211
- toolName: entry.binding.toolName,
2212
- args,
2213
- sourceData: sd,
2214
- sourceId: entry.namespace,
2215
- sourceScope: toolScope,
2216
- invokerScope: ctx.scopes[0].id,
2217
- resolveConnector: () => resolveConnectorInput(entry.namespace, toolScope, sd, ctx, allowStdio).pipe(
2218
- Effect6.catchTags({
2219
- StorageError: () => Effect6.fail(
2220
- new McpConnectionError({
2221
- transport: sd.transport,
2222
- message: "Failed to resolve MCP connector storage state"
2223
- })
2224
- ),
2225
- UniqueViolationError: () => Effect6.fail(
2226
- new McpConnectionError({
2227
- transport: sd.transport,
2228
- message: "Failed to resolve MCP connector storage state"
2229
- })
2230
- )
2231
- }),
2232
- Effect6.flatMap((ci) => createMcpConnector(ci)),
2233
- Effect6.withSpan("mcp.plugin.resolve_connector", {
2234
- attributes: {
2235
- "mcp.source.namespace": entry.namespace,
2236
- "mcp.source.transport": sd.transport
2237
- }
2238
- })
2239
- ),
2240
- connectionCache: runtime.connectionCache,
2241
- pendingConnectors: runtime.pendingConnectors,
2242
- elicit
2243
- });
2244
- const envelope = Option5.getOrUndefined(decodeMcpToolCallEnvelope(raw));
2245
- if (envelope?.isError === true) {
2246
- return ToolResult.fail({
2247
- code: "mcp_tool_error",
2248
- message: extractMcpErrorMessage(envelope.content),
2249
- details: { content: envelope.content }
2250
- });
2251
- }
2252
- return ToolResult.ok(raw);
2253
- }).pipe(
2254
- Effect6.catchTag(
2255
- "McpAuthRequiredError",
2256
- (error) => Effect6.succeed(mcpAuthToolFailure(error))
2257
- ),
2258
- Effect6.withSpan("mcp.plugin.invoke_tool", {
2259
- attributes: {
2260
- "mcp.tool.name": toolRow.id,
2261
- "mcp.tool.source_id": toolRow.source_id
2262
- }
2263
- })
2264
- ),
2265
- detect: ({ ctx, url }) => Effect6.gen(function* () {
2266
- const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
2267
- const trimmed = url.trim();
2268
- if (!trimmed) return null;
2269
- const parsed = yield* Effect6.try({
2270
- try: () => new URL(trimmed),
2271
- catch: (cause) => cause
2272
- }).pipe(Effect6.option);
2273
- if (Option5.isNone(parsed)) return null;
2274
- const name = parsed.value.hostname || "mcp";
2275
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
2276
- const connector = createMcpConnector({
2277
- transport: "remote",
2278
- endpoint: trimmed
2279
- });
2280
- const connected = yield* discoverTools(connector).pipe(
2281
- Effect6.map(() => true),
2282
- Effect6.catch(() => Effect6.succeed(false)),
2283
- Effect6.withSpan("mcp.plugin.discover_tools")
2284
- );
2285
- if (connected) {
2286
- return SourceDetectionResult.make({
2287
- kind: "mcp",
2288
- confidence: "high",
2289
- endpoint: trimmed,
2290
- name,
2291
- namespace
2292
- });
2293
- }
2294
- const shape = yield* probeMcpEndpointShape(trimmed, { httpClientLayer });
2295
- if (shape.kind === "mcp") {
2296
- return SourceDetectionResult.make({
2297
- kind: "mcp",
2298
- confidence: "high",
2299
- endpoint: trimmed,
2300
- name,
2301
- namespace
2302
- });
2303
- }
2304
- if (urlMatchesToken(parsed.value, "mcp")) {
2305
- return SourceDetectionResult.make({
2306
- kind: "mcp",
2307
- confidence: "low",
2308
- endpoint: trimmed,
2309
- name,
2310
- namespace
2311
- });
2312
- }
2313
- return null;
2314
- }).pipe(
2315
- Effect6.catch(() => Effect6.succeed(null)),
2316
- Effect6.withSpan("mcp.plugin.detect", {
2317
- attributes: { "mcp.endpoint": url }
2318
- })
2319
- ),
2320
- // Honor upstream destructiveHint from MCP ToolAnnotations.
2321
- // Bindings are fetched per scope so shadowed sources (e.g. an org-level
2322
- // source overridden per-user) each resolve against their own scope's
2323
- // row rather than collapsing onto whichever visible row would otherwise
2324
- // win first.
2325
- resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect6.gen(function* () {
2326
- const scopes = new Set(toolRows.map((row) => row.scope_id));
2327
- const entries = yield* Effect6.forEach(
2328
- [...scopes],
2329
- (scope) => Effect6.gen(function* () {
2330
- const list = yield* ctx.storage.listBindingsBySource(sourceId, scope);
2331
- const byId = new Map(list.map((e) => [e.toolId, e.binding]));
2332
- return [scope, byId];
2333
- }),
2334
- { concurrency: "unbounded" }
2335
- );
2336
- const byScope = new Map(entries);
2337
- const out = {};
2338
- for (const row of toolRows) {
2339
- const binding = byScope.get(row.scope_id)?.get(row.id);
2340
- const ann = binding?.annotations;
2341
- if (ann?.destructiveHint === true) {
2342
- out[row.id] = {
2343
- requiresApproval: true,
2344
- approvalDescription: ann.title ?? binding?.toolName ?? row.id
2345
- };
2346
- } else {
2347
- out[row.id] = { requiresApproval: false };
2348
- }
2349
- }
2350
- return out;
2351
- }),
2352
- removeSource: ({ ctx, sourceId, scope }) => Effect6.gen(function* () {
2353
- yield* ctx.transaction(
2354
- Effect6.gen(function* () {
2355
- yield* ctx.credentialBindings.removeForSource({
2356
- pluginId: MCP_PLUGIN_ID,
2357
- sourceId,
2358
- sourceScope: ScopeId.make(scope)
2359
- });
2360
- yield* ctx.storage.removeBindingsByNamespace(sourceId, scope);
2361
- yield* ctx.storage.removeSource(sourceId, scope);
2362
- })
2363
- );
2364
- if (options?.configFile) {
2365
- yield* options.configFile.removeSource(sourceId);
2366
- }
2367
- }),
2368
- usagesForSecret: () => Effect6.succeed([]),
2369
- usagesForConnection: () => Effect6.succeed([]),
2370
- refreshSource: () => Effect6.void,
2371
- // Connection refresh for oauth2-minted sources is owned by the
2372
- // canonical `"oauth2"` ConnectionProvider that core registers via
2373
- // `makeOAuth2Service`. No MCP-specific provider needed.
2374
- close: () => Effect6.gen(function* () {
2375
- const runtime = runtimeRef.current;
2376
- if (runtime) {
2377
- runtime.pendingConnectors.clear();
2378
- yield* ScopedCache2.invalidateAll(runtime.connectionCache);
2379
- yield* Scope.close(runtime.cacheScope, Exit2.void);
2380
- runtimeRef.current = null;
2381
- }
2382
- }).pipe(Effect6.withSpan("mcp.plugin.close"))
2383
- };
2384
- });
2385
-
2386
- export {
2387
- mcpSchema,
2388
- makeMcpStore,
2389
- mcpPlugin
2390
- };
2391
- //# sourceMappingURL=chunk-LEGVPKYH.js.map