@executor-js/plugin-mcp 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/AddMcpSource-VM3HY26S.js +762 -0
  2. package/dist/AddMcpSource-VM3HY26S.js.map +1 -0
  3. package/dist/EditMcpSource-WELWGRJG.js +259 -0
  4. package/dist/EditMcpSource-WELWGRJG.js.map +1 -0
  5. package/dist/McpSourceSummary-7TDQXLT5.js +85 -0
  6. package/dist/McpSourceSummary-7TDQXLT5.js.map +1 -0
  7. package/dist/api/group.d.ts +109 -17
  8. package/dist/api/index.d.ts +391 -0
  9. package/dist/chunk-2ETJ6LQH.js +239 -0
  10. package/dist/chunk-2ETJ6LQH.js.map +1 -0
  11. package/dist/chunk-OOOH3IO4.js +2194 -0
  12. package/dist/chunk-OOOH3IO4.js.map +1 -0
  13. package/dist/chunk-SKSXXFOA.js +104 -0
  14. package/dist/chunk-SKSXXFOA.js.map +1 -0
  15. package/dist/chunk-Z4CRPOLI.js +186 -0
  16. package/dist/chunk-Z4CRPOLI.js.map +1 -0
  17. package/dist/chunk-ZIRGIRGP.js +115 -0
  18. package/dist/chunk-ZIRGIRGP.js.map +1 -0
  19. package/dist/client.js +51 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/core.js +26 -2
  22. package/dist/index.js +2 -1
  23. package/dist/react/McpRemoteSourceFields.d.ts +18 -0
  24. package/dist/react/McpSourceSummary.d.ts +5 -0
  25. package/dist/react/atoms.d.ts +206 -6
  26. package/dist/react/client.d.ts +113 -16
  27. package/dist/react/index.d.ts +1 -1
  28. package/dist/react/plugin-client.d.ts +9 -2
  29. package/dist/sdk/binding-store.d.ts +106 -1
  30. package/dist/sdk/index.d.ts +1 -1
  31. package/dist/sdk/invoke.d.ts +2 -0
  32. package/dist/sdk/plugin.d.ts +142 -114
  33. package/dist/sdk/probe-shape-real-servers.live.test.d.ts +1 -0
  34. package/dist/sdk/probe-shape.d.ts +17 -3
  35. package/dist/sdk/stored-source.d.ts +9 -6
  36. package/dist/sdk/types.d.ts +138 -13
  37. package/dist/{stdio-connector-KNHLETKM.js → stdio-connector-AA5S6UUJ.js} +1 -1
  38. package/dist/{stdio-connector-KNHLETKM.js.map → stdio-connector-AA5S6UUJ.js.map} +1 -1
  39. package/dist/testing/index.d.ts +1 -0
  40. package/dist/{sdk/test-utils.d.ts → testing/server.d.ts} +0 -6
  41. package/dist/testing.js +51 -0
  42. package/dist/testing.js.map +1 -0
  43. package/package.json +17 -4
  44. package/dist/chunk-C2GNZGFJ.js +0 -1622
  45. package/dist/chunk-C2GNZGFJ.js.map +0 -1
@@ -1,1622 +0,0 @@
1
- // src/sdk/types.ts
2
- import { Effect, Schema } from "effect";
3
- import { SecretBackedMap, SecretBackedValue } from "@executor-js/sdk/core";
4
- var McpRemoteTransport = Schema.Literals(["streamable-http", "sse", "auto"]);
5
- var McpTransport = Schema.Literals(["streamable-http", "sse", "stdio", "auto"]);
6
- var JsonObject = Schema.Record(Schema.String, Schema.Unknown);
7
- var McpConnectionAuth = Schema.Union([
8
- Schema.Struct({ kind: Schema.Literal("none") }),
9
- Schema.Struct({
10
- kind: Schema.Literal("header"),
11
- headerName: Schema.String,
12
- secretId: Schema.String,
13
- prefix: Schema.optional(Schema.String)
14
- }),
15
- Schema.Struct({
16
- kind: Schema.Literal("oauth2"),
17
- connectionId: Schema.String,
18
- clientIdSecretId: Schema.optional(Schema.String),
19
- clientSecretSecretId: Schema.optional(Schema.NullOr(Schema.String))
20
- })
21
- ]);
22
- var StringMap = Schema.Record(Schema.String, Schema.String);
23
- var McpRemoteSourceData = Schema.Struct({
24
- transport: Schema.Literal("remote"),
25
- /** The MCP server endpoint URL */
26
- endpoint: Schema.String,
27
- /** Transport preference for this remote source */
28
- remoteTransport: McpRemoteTransport.pipe(
29
- Schema.optionalKey,
30
- Schema.withConstructorDefault(Effect.succeed("auto"))
31
- ),
32
- /** Extra query params appended to the endpoint URL */
33
- queryParams: Schema.optional(SecretBackedMap),
34
- /** Extra headers sent on every request */
35
- headers: Schema.optional(SecretBackedMap),
36
- /** Auth configuration */
37
- auth: McpConnectionAuth
38
- });
39
- var McpStdioSourceData = Schema.Struct({
40
- transport: Schema.Literal("stdio"),
41
- /** The command to run */
42
- command: Schema.String,
43
- /** Arguments to the command */
44
- args: Schema.optional(Schema.Array(Schema.String)),
45
- /** Environment variables */
46
- env: Schema.optional(StringMap),
47
- /** Working directory */
48
- cwd: Schema.optional(Schema.String)
49
- });
50
- var McpStoredSourceData = Schema.Union([McpRemoteSourceData, McpStdioSourceData]);
51
- var McpToolAnnotations = Schema.Struct({
52
- title: Schema.optional(Schema.String),
53
- readOnlyHint: Schema.optional(Schema.Boolean),
54
- destructiveHint: Schema.optional(Schema.Boolean),
55
- idempotentHint: Schema.optional(Schema.Boolean),
56
- openWorldHint: Schema.optional(Schema.Boolean)
57
- });
58
- var McpToolBinding = class extends Schema.Class("McpToolBinding")({
59
- toolId: Schema.String,
60
- toolName: Schema.String,
61
- description: Schema.NullOr(Schema.String),
62
- inputSchema: Schema.optional(Schema.Unknown),
63
- outputSchema: Schema.optional(Schema.Unknown),
64
- annotations: Schema.optional(McpToolAnnotations)
65
- }) {
66
- };
67
-
68
- // src/sdk/binding-store.ts
69
- import { Effect as Effect2, Schema as Schema2 } from "effect";
70
- import {
71
- defineSchema
72
- } from "@executor-js/sdk/core";
73
- var mcpSchema = defineSchema({
74
- mcp_source: {
75
- fields: {
76
- id: { type: "string", required: true },
77
- scope_id: { type: "string", required: true, index: true },
78
- name: { type: "string", required: true },
79
- config: { type: "json", required: true },
80
- created_at: { type: "date", required: true }
81
- }
82
- },
83
- mcp_binding: {
84
- fields: {
85
- id: { type: "string", required: true },
86
- scope_id: { type: "string", required: true, index: true },
87
- source_id: { type: "string", required: true, index: true },
88
- binding: { type: "json", required: true },
89
- created_at: { type: "date", required: true }
90
- }
91
- }
92
- });
93
- var decodeSourceData = Schema2.decodeUnknownSync(McpStoredSourceData);
94
- var encodeSourceData = Schema2.encodeSync(McpStoredSourceData);
95
- var decodeBinding = Schema2.decodeUnknownSync(McpToolBinding);
96
- var encodeBinding = Schema2.encodeSync(McpToolBinding);
97
- var coerceJson = (value) => {
98
- if (typeof value !== "string") return value;
99
- try {
100
- return JSON.parse(value);
101
- } catch {
102
- return value;
103
- }
104
- };
105
- var makeMcpStore = ({
106
- adapter: db
107
- }) => {
108
- return {
109
- listBindingsBySource: (namespace, scope) => Effect2.gen(function* () {
110
- const rows = yield* db.findMany({
111
- model: "mcp_binding",
112
- where: [
113
- { field: "source_id", value: namespace },
114
- { field: "scope_id", value: scope }
115
- ]
116
- });
117
- return rows.map((row) => ({
118
- toolId: row.id,
119
- binding: decodeBinding(coerceJson(row.binding))
120
- }));
121
- }),
122
- getBinding: (toolId, scope) => Effect2.gen(function* () {
123
- const row = yield* db.findOne({
124
- model: "mcp_binding",
125
- where: [
126
- { field: "id", value: toolId },
127
- { field: "scope_id", value: scope }
128
- ]
129
- });
130
- if (!row) return null;
131
- const binding = decodeBinding(coerceJson(row.binding));
132
- return { binding, namespace: row.source_id };
133
- }),
134
- putBindings: (namespace, scope, entries) => Effect2.gen(function* () {
135
- if (entries.length === 0) return;
136
- const now = /* @__PURE__ */ new Date();
137
- yield* db.createMany({
138
- model: "mcp_binding",
139
- data: entries.map((e) => ({
140
- id: e.toolId,
141
- scope_id: scope,
142
- source_id: namespace,
143
- binding: encodeBinding(e.binding),
144
- created_at: now
145
- })),
146
- forceAllowId: true
147
- });
148
- }),
149
- removeBindingsByNamespace: (namespace, scope) => db.deleteMany({
150
- model: "mcp_binding",
151
- where: [
152
- { field: "source_id", value: namespace },
153
- { field: "scope_id", value: scope }
154
- ]
155
- }).pipe(Effect2.asVoid),
156
- getSource: (namespace, scope) => Effect2.gen(function* () {
157
- const row = yield* db.findOne({
158
- model: "mcp_source",
159
- where: [
160
- { field: "id", value: namespace },
161
- { field: "scope_id", value: scope }
162
- ]
163
- });
164
- if (!row) return null;
165
- return {
166
- namespace: row.id,
167
- scope: row.scope_id,
168
- name: row.name,
169
- config: decodeSourceData(coerceJson(row.config))
170
- };
171
- }),
172
- getSourceConfig: (namespace, scope) => Effect2.gen(function* () {
173
- const row = yield* db.findOne({
174
- model: "mcp_source",
175
- where: [
176
- { field: "id", value: namespace },
177
- { field: "scope_id", value: scope }
178
- ]
179
- });
180
- if (!row) return null;
181
- return decodeSourceData(coerceJson(row.config));
182
- }),
183
- putSource: (source) => Effect2.gen(function* () {
184
- const now = /* @__PURE__ */ new Date();
185
- yield* db.delete({
186
- model: "mcp_source",
187
- where: [
188
- { field: "id", value: source.namespace },
189
- { field: "scope_id", value: source.scope }
190
- ]
191
- });
192
- yield* db.create({
193
- model: "mcp_source",
194
- data: {
195
- id: source.namespace,
196
- scope_id: source.scope,
197
- name: source.name,
198
- config: encodeSourceData(source.config),
199
- created_at: now
200
- },
201
- forceAllowId: true
202
- });
203
- }),
204
- removeSource: (namespace, scope) => Effect2.gen(function* () {
205
- yield* db.deleteMany({
206
- model: "mcp_binding",
207
- where: [
208
- { field: "source_id", value: namespace },
209
- { field: "scope_id", value: scope }
210
- ]
211
- });
212
- yield* db.delete({
213
- model: "mcp_source",
214
- where: [
215
- { field: "id", value: namespace },
216
- { field: "scope_id", value: scope }
217
- ]
218
- });
219
- })
220
- };
221
- };
222
-
223
- // src/sdk/plugin.ts
224
- import { Duration, Effect as Effect8, Exit as Exit2, Result, Scope, ScopedCache as ScopedCache2 } from "effect";
225
-
226
- // src/api/group.ts
227
- import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
228
- import { Schema as Schema5 } from "effect";
229
- import { ScopeId, SecretBackedMap as SecretBackedMap2 } from "@executor-js/sdk/core";
230
- import { InternalError } from "@executor-js/api";
231
-
232
- // src/sdk/errors.ts
233
- import { Schema as Schema3 } from "effect";
234
- var McpConnectionError = class extends Schema3.TaggedErrorClass()(
235
- "McpConnectionError",
236
- {
237
- transport: Schema3.String,
238
- message: Schema3.String
239
- },
240
- { httpApiStatus: 400 }
241
- ) {
242
- };
243
- var McpToolDiscoveryError = class extends Schema3.TaggedErrorClass()(
244
- "McpToolDiscoveryError",
245
- {
246
- stage: Schema3.Literals(["connect", "list_tools"]),
247
- message: Schema3.String
248
- },
249
- { httpApiStatus: 400 }
250
- ) {
251
- };
252
- var McpInvocationError = class extends Schema3.TaggedErrorClass()(
253
- "McpInvocationError",
254
- {
255
- toolName: Schema3.String,
256
- message: Schema3.String
257
- },
258
- { httpApiStatus: 400 }
259
- ) {
260
- };
261
- var McpOAuthError = class extends Schema3.TaggedErrorClass()(
262
- "McpOAuthError",
263
- {
264
- message: Schema3.String
265
- },
266
- { httpApiStatus: 400 }
267
- ) {
268
- };
269
-
270
- // src/sdk/stored-source.ts
271
- import { Schema as Schema4 } from "effect";
272
- var McpStoredSourceSchema = class extends Schema4.Class("McpStoredSource")({
273
- namespace: Schema4.String,
274
- name: Schema4.String,
275
- config: McpStoredSourceData
276
- }) {
277
- };
278
-
279
- // src/api/group.ts
280
- var ScopeParams = { scopeId: ScopeId };
281
- var SourceParams = { scopeId: ScopeId, namespace: Schema5.String };
282
- var AuthPayload = Schema5.Union([
283
- Schema5.Struct({ kind: Schema5.Literal("none") }),
284
- Schema5.Struct({
285
- kind: Schema5.Literal("header"),
286
- headerName: Schema5.String,
287
- secretId: Schema5.String,
288
- prefix: Schema5.optional(Schema5.String)
289
- }),
290
- Schema5.Struct({
291
- kind: Schema5.Literal("oauth2"),
292
- /** Stable id of the SDK Connection minted by `completeOAuth`. The
293
- * backing access/refresh secrets live on the connection row; the
294
- * source only needs this pointer. */
295
- connectionId: Schema5.String,
296
- clientIdSecretId: Schema5.optional(Schema5.String),
297
- clientSecretSecretId: Schema5.optional(Schema5.NullOr(Schema5.String))
298
- })
299
- ]);
300
- var StringMap2 = Schema5.Record(Schema5.String, Schema5.String);
301
- var AddRemoteSourcePayload = Schema5.Struct({
302
- transport: Schema5.Literal("remote"),
303
- name: Schema5.String,
304
- endpoint: Schema5.String,
305
- remoteTransport: Schema5.optional(Schema5.Literals(["streamable-http", "sse", "auto"])),
306
- namespace: Schema5.optional(Schema5.String),
307
- queryParams: Schema5.optional(SecretBackedMap2),
308
- headers: Schema5.optional(SecretBackedMap2),
309
- auth: Schema5.optional(AuthPayload)
310
- });
311
- var AddStdioSourcePayload = Schema5.Struct({
312
- transport: Schema5.Literal("stdio"),
313
- name: Schema5.String,
314
- command: Schema5.String,
315
- args: Schema5.optional(Schema5.Array(Schema5.String)),
316
- env: Schema5.optional(StringMap2),
317
- cwd: Schema5.optional(Schema5.String),
318
- namespace: Schema5.optional(Schema5.String)
319
- });
320
- var AddSourcePayload = Schema5.Union([AddRemoteSourcePayload, AddStdioSourcePayload]);
321
- var UpdateSourcePayload = Schema5.Struct({
322
- name: Schema5.optional(Schema5.String),
323
- endpoint: Schema5.optional(Schema5.String),
324
- headers: Schema5.optional(SecretBackedMap2),
325
- queryParams: Schema5.optional(SecretBackedMap2),
326
- auth: Schema5.optional(AuthPayload)
327
- });
328
- var UpdateSourceResponse = Schema5.Struct({
329
- updated: Schema5.Boolean
330
- });
331
- var ProbeEndpointPayload = Schema5.Struct({
332
- endpoint: Schema5.String,
333
- headers: Schema5.optional(SecretBackedMap2),
334
- queryParams: Schema5.optional(SecretBackedMap2)
335
- });
336
- var ProbeEndpointResponse = Schema5.Struct({
337
- connected: Schema5.Boolean,
338
- requiresOAuth: Schema5.Boolean,
339
- name: Schema5.String,
340
- namespace: Schema5.String,
341
- toolCount: Schema5.NullOr(Schema5.Number),
342
- serverName: Schema5.NullOr(Schema5.String)
343
- });
344
- var NamespacePayload = Schema5.Struct({
345
- namespace: Schema5.String
346
- });
347
- var AddSourceResponse = Schema5.Struct({
348
- toolCount: Schema5.Number,
349
- namespace: Schema5.String
350
- });
351
- var RefreshSourceResponse = Schema5.Struct({
352
- toolCount: Schema5.Number
353
- });
354
- var RemoveSourceResponse = Schema5.Struct({
355
- removed: Schema5.Boolean
356
- });
357
- var McpGroup = HttpApiGroup.make("mcp").add(
358
- HttpApiEndpoint.post("probeEndpoint", "/scopes/:scopeId/mcp/probe", {
359
- params: ScopeParams,
360
- payload: ProbeEndpointPayload,
361
- success: ProbeEndpointResponse,
362
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
363
- })
364
- ).add(
365
- HttpApiEndpoint.post("addSource", "/scopes/:scopeId/mcp/sources", {
366
- params: ScopeParams,
367
- payload: AddSourcePayload,
368
- success: AddSourceResponse,
369
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
370
- })
371
- ).add(
372
- HttpApiEndpoint.post("removeSource", "/scopes/:scopeId/mcp/sources/remove", {
373
- params: ScopeParams,
374
- payload: NamespacePayload,
375
- success: RemoveSourceResponse,
376
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
377
- })
378
- ).add(
379
- HttpApiEndpoint.post("refreshSource", "/scopes/:scopeId/mcp/sources/refresh", {
380
- params: ScopeParams,
381
- payload: NamespacePayload,
382
- success: RefreshSourceResponse,
383
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
384
- })
385
- ).add(
386
- HttpApiEndpoint.get("getSource", "/scopes/:scopeId/mcp/sources/:namespace", {
387
- params: SourceParams,
388
- success: Schema5.NullOr(McpStoredSourceSchema),
389
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
390
- })
391
- ).add(
392
- HttpApiEndpoint.patch("updateSource", "/scopes/:scopeId/mcp/sources/:namespace", {
393
- params: SourceParams,
394
- payload: UpdateSourcePayload,
395
- success: UpdateSourceResponse,
396
- error: [InternalError, McpConnectionError, McpToolDiscoveryError]
397
- })
398
- );
399
-
400
- // src/api/handlers.ts
401
- import { HttpApiBuilder } from "effect/unstable/httpapi";
402
- import { Context, Effect as Effect3 } from "effect";
403
- import { addGroup, capture } from "@executor-js/api";
404
- var McpExtensionService = class extends Context.Service()("McpExtensionService") {
405
- };
406
- var ExecutorApiWithMcp = addGroup(McpGroup);
407
- var toSourceConfig = (payload, scope) => {
408
- if (payload.transport === "stdio") {
409
- const p2 = payload;
410
- return {
411
- transport: "stdio",
412
- scope,
413
- name: p2.name,
414
- command: p2.command,
415
- args: p2.args ? [...p2.args] : void 0,
416
- env: p2.env,
417
- cwd: p2.cwd,
418
- namespace: p2.namespace
419
- };
420
- }
421
- const p = payload;
422
- return {
423
- transport: "remote",
424
- scope,
425
- name: p.name,
426
- endpoint: p.endpoint,
427
- remoteTransport: p.remoteTransport,
428
- queryParams: p.queryParams,
429
- headers: p.headers,
430
- namespace: p.namespace,
431
- auth: p.auth
432
- };
433
- };
434
- var McpHandlers = HttpApiBuilder.group(
435
- ExecutorApiWithMcp,
436
- "mcp",
437
- (handlers) => handlers.handle(
438
- "probeEndpoint",
439
- ({ payload }) => capture(
440
- Effect3.gen(function* () {
441
- const ext = yield* McpExtensionService;
442
- return yield* ext.probeEndpoint(payload);
443
- })
444
- )
445
- ).handle(
446
- "addSource",
447
- ({ params: path, payload }) => capture(
448
- Effect3.gen(function* () {
449
- const ext = yield* McpExtensionService;
450
- return yield* ext.addSource(
451
- toSourceConfig(payload, path.scopeId)
452
- );
453
- })
454
- )
455
- ).handle(
456
- "removeSource",
457
- ({ params: path, payload }) => capture(
458
- Effect3.gen(function* () {
459
- const ext = yield* McpExtensionService;
460
- yield* ext.removeSource(payload.namespace, path.scopeId);
461
- return { removed: true };
462
- })
463
- )
464
- ).handle(
465
- "refreshSource",
466
- ({ params: path, payload }) => capture(
467
- Effect3.gen(function* () {
468
- const ext = yield* McpExtensionService;
469
- return yield* ext.refreshSource(payload.namespace, path.scopeId);
470
- })
471
- )
472
- ).handle(
473
- "getSource",
474
- ({ params: path }) => capture(
475
- Effect3.gen(function* () {
476
- const ext = yield* McpExtensionService;
477
- const source = yield* ext.getSource(path.namespace, path.scopeId);
478
- return source ? new McpStoredSourceSchema({
479
- namespace: source.namespace,
480
- name: source.name,
481
- config: source.config
482
- }) : null;
483
- })
484
- )
485
- ).handle(
486
- "updateSource",
487
- ({ params: path, payload }) => capture(
488
- Effect3.gen(function* () {
489
- const ext = yield* McpExtensionService;
490
- yield* ext.updateSource(path.namespace, path.scopeId, {
491
- name: payload.name,
492
- endpoint: payload.endpoint,
493
- headers: payload.headers,
494
- queryParams: payload.queryParams,
495
- auth: payload.auth
496
- });
497
- return { updated: true };
498
- })
499
- )
500
- )
501
- );
502
-
503
- // src/sdk/plugin.ts
504
- import {
505
- SourceDetectionResult,
506
- definePlugin,
507
- resolveSecretBackedMap as resolveSharedSecretBackedMap
508
- } from "@executor-js/sdk/core";
509
-
510
- // src/sdk/connection.ts
511
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
512
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
513
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
514
- import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
515
- import { Effect as Effect4 } from "effect";
516
- var buildEndpointUrl = (endpoint, queryParams) => {
517
- const url = new URL(endpoint);
518
- for (const [key, value] of Object.entries(queryParams)) {
519
- url.searchParams.set(key, value);
520
- }
521
- return url;
522
- };
523
- var createClient = () => new Client(
524
- { name: "executor-mcp", version: "0.1.0" },
525
- {
526
- capabilities: { elicitation: { form: {}, url: {} } },
527
- jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
528
- }
529
- );
530
- var connectionFromClient = (client) => ({
531
- client,
532
- close: () => client.close()
533
- });
534
- var connectClient = (input) => Effect4.gen(function* () {
535
- const client = createClient();
536
- const transportInstance = input.createTransport();
537
- yield* Effect4.tryPromise({
538
- try: () => client.connect(transportInstance),
539
- catch: (cause) => new McpConnectionError({
540
- transport: input.transport,
541
- message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
542
- })
543
- }).pipe(
544
- Effect4.withSpan("plugin.mcp.connection.handshake", {
545
- attributes: { "plugin.mcp.transport": input.transport }
546
- })
547
- );
548
- return connectionFromClient(client);
549
- });
550
- var createMcpConnector = (input) => {
551
- if (input.transport === "stdio") {
552
- const command = input.command.trim();
553
- if (!command) {
554
- return Effect4.fail(
555
- new McpConnectionError({
556
- transport: "stdio",
557
- message: "MCP stdio transport requires a command"
558
- })
559
- );
560
- }
561
- return Effect4.gen(function* () {
562
- const { createStdioTransport } = yield* Effect4.tryPromise({
563
- try: () => import("./stdio-connector-KNHLETKM.js"),
564
- catch: (cause) => new McpConnectionError({
565
- transport: "stdio",
566
- message: `Failed to load stdio transport module: ${cause instanceof Error ? cause.message : String(cause)}`
567
- })
568
- });
569
- return yield* connectClient({
570
- transport: "stdio",
571
- createTransport: () => createStdioTransport({
572
- command,
573
- args: input.args,
574
- env: input.env,
575
- cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
576
- })
577
- });
578
- });
579
- }
580
- const headers = input.headers ?? {};
581
- const remoteTransport = input.remoteTransport ?? "auto";
582
- const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
583
- const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
584
- const connectStreamableHttp = connectClient({
585
- transport: "streamable-http",
586
- createTransport: () => new StreamableHTTPClientTransport(endpoint, {
587
- requestInit,
588
- authProvider: input.authProvider
589
- })
590
- });
591
- const connectSse = connectClient({
592
- transport: "sse",
593
- createTransport: () => new SSEClientTransport(endpoint, {
594
- requestInit,
595
- authProvider: input.authProvider
596
- })
597
- });
598
- if (remoteTransport === "streamable-http") return connectStreamableHttp;
599
- if (remoteTransport === "sse") return connectSse;
600
- return connectStreamableHttp.pipe(Effect4.catch(() => connectSse));
601
- };
602
-
603
- // src/sdk/discover.ts
604
- import { Effect as Effect5 } from "effect";
605
-
606
- // src/sdk/manifest.ts
607
- import { Schema as Schema6 } from "effect";
608
- var ListedTool = Schema6.Struct({
609
- name: Schema6.String,
610
- description: Schema6.optional(Schema6.NullOr(Schema6.String)),
611
- inputSchema: Schema6.optional(Schema6.Unknown),
612
- parameters: Schema6.optional(Schema6.Unknown),
613
- outputSchema: Schema6.optional(Schema6.Unknown),
614
- annotations: Schema6.optional(McpToolAnnotations)
615
- });
616
- var ListToolsResult = Schema6.Struct({
617
- tools: Schema6.Array(ListedTool)
618
- });
619
- var ServerInfo = Schema6.Struct({
620
- name: Schema6.optional(Schema6.String),
621
- version: Schema6.optional(Schema6.String)
622
- });
623
- var decodeListToolsResult = Schema6.decodeUnknownOption(ListToolsResult);
624
- var decodeServerInfo = Schema6.decodeUnknownOption(ServerInfo);
625
- var isListToolsResult = (value) => decodeListToolsResult(value)._tag === "Some";
626
- var sanitize = (value) => {
627
- const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
628
- return s || "tool";
629
- };
630
- var uniqueId = (value, seen) => {
631
- const base = sanitize(value);
632
- const n = (seen.get(base) ?? 0) + 1;
633
- seen.set(base, n);
634
- return n === 1 ? base : `${base}_${n}`;
635
- };
636
- var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
637
- const seen = /* @__PURE__ */ new Map();
638
- const listed = decodeListToolsResult(listToolsResult).pipe(
639
- (opt) => opt._tag === "Some" ? opt.value.tools : []
640
- );
641
- const server = decodeServerInfo(metadata?.serverInfo).pipe(
642
- (opt) => opt._tag === "Some" ? { name: opt.value.name ?? null, version: opt.value.version ?? null } : null
643
- );
644
- const tools = listed.flatMap((tool) => {
645
- const toolName = tool.name.trim();
646
- if (!toolName) return [];
647
- return [
648
- {
649
- toolId: uniqueId(toolName, seen),
650
- toolName,
651
- description: tool.description ?? null,
652
- inputSchema: tool.inputSchema ?? tool.parameters,
653
- outputSchema: tool.outputSchema,
654
- annotations: tool.annotations
655
- }
656
- ];
657
- });
658
- return { server, tools };
659
- };
660
- var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
661
- var hostnameOf = (url) => {
662
- try {
663
- return new URL(url).hostname;
664
- } catch {
665
- return null;
666
- }
667
- };
668
- var basenameOf = (path) => path.trim().split(/[\\/]/).pop() ?? path.trim();
669
- var deriveMcpNamespace = (input) => {
670
- if (input.name?.trim()) return slugify(input.name) || "mcp";
671
- const fromEndpoint = input.endpoint?.trim() ? hostnameOf(input.endpoint) : null;
672
- if (fromEndpoint) return slugify(fromEndpoint) || "mcp";
673
- if (input.command?.trim()) return slugify(basenameOf(input.command)) || "mcp";
674
- return "mcp";
675
- };
676
-
677
- // src/sdk/discover.ts
678
- var discoverTools = (connector) => Effect5.gen(function* () {
679
- const connection = yield* connector.pipe(
680
- Effect5.mapError(
681
- (err) => new McpToolDiscoveryError({
682
- stage: "connect",
683
- message: `Failed connecting to MCP server: ${err.message}`
684
- })
685
- )
686
- );
687
- const listResult = yield* Effect5.tryPromise({
688
- try: () => connection.client.listTools(),
689
- catch: (cause) => new McpToolDiscoveryError({
690
- stage: "list_tools",
691
- message: `Failed listing MCP tools: ${cause instanceof Error ? cause.message : String(cause)}`
692
- })
693
- });
694
- if (!isListToolsResult(listResult)) {
695
- yield* Effect5.promise(() => connection.close().catch(() => {
696
- }));
697
- return yield* Effect5.fail(
698
- new McpToolDiscoveryError({
699
- stage: "list_tools",
700
- message: "MCP listTools response did not match the expected schema"
701
- })
702
- );
703
- }
704
- const manifest = extractManifestFromListToolsResult(listResult, {
705
- serverInfo: connection.client.getServerVersion?.()
706
- });
707
- yield* Effect5.promise(() => connection.close().catch(() => {
708
- }));
709
- return manifest;
710
- });
711
-
712
- // src/sdk/invoke.ts
713
- import { Cause, Effect as Effect6, Exit, Schema as Schema7, ScopedCache } from "effect";
714
- import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
715
- import {
716
- FormElicitation,
717
- UrlElicitation
718
- } from "@executor-js/sdk/core";
719
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
720
- var connectionCacheKey = (sd, invokerScope) => sd.transport === "stdio" ? `stdio:${sd.command}` : (
721
- // Remote sources may resolve per-user secrets (OAuth tokens, header
722
- // auth) via scope shadowing, so two users invoking the same source
723
- // get different Authorization headers. The connection caches that
724
- // header in transport state, so the cache key must include the
725
- // invoking scope — otherwise user B re-uses user A's connection
726
- // (and user A's tokens).
727
- `remote:${invokerScope}:${sd.endpoint}`
728
- );
729
- var McpElicitParams = Schema7.Union([
730
- Schema7.Struct({
731
- mode: Schema7.Literal("url"),
732
- message: Schema7.String,
733
- url: Schema7.String,
734
- elicitationId: Schema7.optional(Schema7.String),
735
- id: Schema7.optional(Schema7.String)
736
- }),
737
- Schema7.Struct({
738
- mode: Schema7.optional(Schema7.Literal("form")),
739
- message: Schema7.String,
740
- requestedSchema: Schema7.Record(Schema7.String, Schema7.Unknown)
741
- })
742
- ]);
743
- var decodeElicitParams = Schema7.decodeUnknownSync(McpElicitParams);
744
- var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitation({
745
- message: params.message,
746
- url: params.url,
747
- elicitationId: params.elicitationId ?? params.id ?? ""
748
- }) : new FormElicitation({
749
- message: params.message,
750
- requestedSchema: params.requestedSchema
751
- });
752
- var installElicitationHandler = (client, elicit) => {
753
- client.setRequestHandler(
754
- ElicitRequestSchema,
755
- async (request) => {
756
- const params = decodeElicitParams(request.params);
757
- const req = toElicitationRequest(params);
758
- const exit = await Effect6.runPromiseExit(elicit(req));
759
- if (Exit.isSuccess(exit)) {
760
- const response = exit.value;
761
- return {
762
- action: response.action,
763
- ...response.action === "accept" && response.content ? { content: response.content } : {}
764
- };
765
- }
766
- const failure = exit.cause.reasons.find(Cause.isFailReason);
767
- if (failure) {
768
- const err = failure.error;
769
- if (err._tag === "ElicitationDeclinedError") {
770
- return { action: err.action ?? "decline" };
771
- }
772
- }
773
- throw Cause.squash(exit.cause);
774
- }
775
- );
776
- };
777
- var useConnection = (connection, toolName, args, elicit) => Effect6.gen(function* () {
778
- installElicitationHandler(connection.client, elicit);
779
- return yield* Effect6.tryPromise({
780
- try: () => connection.client.callTool({ name: toolName, arguments: args }),
781
- catch: (cause) => new McpInvocationError({
782
- toolName,
783
- message: `MCP tool call failed for ${toolName}: ${cause instanceof Error ? cause.message : String(cause)}`
784
- })
785
- }).pipe(
786
- Effect6.withSpan("plugin.mcp.client.call_tool", {
787
- attributes: { "mcp.tool.name": toolName }
788
- })
789
- );
790
- });
791
- var invokeMcpTool = (input) => {
792
- const transport = input.sourceData.transport === "stdio" ? "stdio" : input.sourceData.remoteTransport ?? "auto";
793
- return Effect6.gen(function* () {
794
- const cacheKey = connectionCacheKey(input.sourceData, input.invokerScope);
795
- const args = asRecord(input.args);
796
- const connector = input.resolveConnector();
797
- input.pendingConnectors.set(cacheKey, connector);
798
- const cacheHit = yield* ScopedCache.has(input.connectionCache, cacheKey);
799
- const firstConnection = yield* ScopedCache.get(input.connectionCache, cacheKey).pipe(
800
- Effect6.withSpan("plugin.mcp.connection.acquire", {
801
- attributes: {
802
- "plugin.mcp.transport": transport,
803
- "plugin.mcp.cache_key": cacheKey,
804
- "plugin.mcp.attempt": 1,
805
- "plugin.mcp.cache_hit": cacheHit
806
- }
807
- })
808
- );
809
- return yield* useConnection(
810
- firstConnection,
811
- input.toolName,
812
- args,
813
- input.elicit
814
- ).pipe(
815
- // On failure, invalidate the cache and retry once with a fresh
816
- // connection. Matches the old invoker's retry-once semantics.
817
- Effect6.catch(
818
- () => Effect6.gen(function* () {
819
- yield* ScopedCache.invalidate(input.connectionCache, cacheKey);
820
- input.pendingConnectors.set(cacheKey, connector);
821
- const fresh = yield* ScopedCache.get(input.connectionCache, cacheKey);
822
- return yield* useConnection(
823
- fresh,
824
- input.toolName,
825
- args,
826
- input.elicit
827
- );
828
- }).pipe(
829
- Effect6.withSpan("plugin.mcp.invoke.retry", {
830
- attributes: {
831
- "plugin.mcp.transport": transport,
832
- "plugin.mcp.cache_key": cacheKey,
833
- "mcp.tool.name": input.toolName
834
- }
835
- })
836
- )
837
- )
838
- );
839
- }).pipe(
840
- Effect6.scoped,
841
- Effect6.withSpan("plugin.mcp.invoke", {
842
- attributes: {
843
- "mcp.tool.name": input.toolName,
844
- "plugin.mcp.tool_id": input.toolId,
845
- "plugin.mcp.transport": transport
846
- }
847
- })
848
- );
849
- };
850
-
851
- // src/sdk/probe-shape.ts
852
- import { Effect as Effect7 } from "effect";
853
- var INITIALIZE_BODY = JSON.stringify({
854
- jsonrpc: "2.0",
855
- id: 1,
856
- method: "initialize",
857
- params: {
858
- protocolVersion: "2025-06-18",
859
- capabilities: {},
860
- clientInfo: { name: "executor-probe", version: "0" }
861
- }
862
- });
863
- var readHeader = (headers, name) => {
864
- const direct = headers.get(name);
865
- if (direct !== null) return direct;
866
- const lower = name.toLowerCase();
867
- for (const [k, v] of headers) {
868
- if (k.toLowerCase() === lower) return v;
869
- }
870
- return null;
871
- };
872
- var probeMcpEndpointShape = (endpoint, options = {}) => Effect7.gen(function* () {
873
- const fetchImpl = options.fetch ?? globalThis.fetch;
874
- const timeoutMs = options.timeoutMs ?? 8e3;
875
- const outcome = yield* Effect7.tryPromise({
876
- try: async () => {
877
- const controller = new AbortController();
878
- const timer = setTimeout(() => controller.abort(), timeoutMs);
879
- try {
880
- const classify = (response, method) => {
881
- if (response.status === 401) {
882
- const wwwAuth = readHeader(response.headers, "www-authenticate");
883
- if (wwwAuth && /^\s*bearer\b/i.test(wwwAuth)) {
884
- return { kind: "mcp", requiresAuth: true };
885
- }
886
- return {
887
- kind: "not-mcp",
888
- reason: "401 without Bearer WWW-Authenticate \u2014 not an MCP auth challenge"
889
- };
890
- }
891
- if (response.status >= 200 && response.status < 300) {
892
- if (method === "GET") {
893
- const contentType = readHeader(response.headers, "content-type") ?? "";
894
- if (!/^\s*text\/event-stream\b/i.test(contentType)) {
895
- return {
896
- kind: "not-mcp",
897
- reason: "GET response is not an SSE stream"
898
- };
899
- }
900
- }
901
- return { kind: "mcp", requiresAuth: false };
902
- }
903
- return null;
904
- };
905
- const url = new URL(endpoint);
906
- for (const [key, value] of Object.entries(options.queryParams ?? {})) {
907
- url.searchParams.set(key, value);
908
- }
909
- const authHeaders = options.headers ?? {};
910
- const postResponse = await fetchImpl(url, {
911
- method: "POST",
912
- headers: {
913
- ...authHeaders,
914
- "content-type": "application/json",
915
- accept: "application/json, text/event-stream"
916
- },
917
- body: INITIALIZE_BODY,
918
- signal: controller.signal
919
- });
920
- const postResult = classify(postResponse, "POST");
921
- if (postResult) return postResult;
922
- if ([404, 405, 406, 415].includes(postResponse.status)) {
923
- const getResponse = await fetchImpl(url, {
924
- method: "GET",
925
- headers: { ...authHeaders, accept: "text/event-stream" },
926
- signal: controller.signal
927
- });
928
- const getResult = classify(getResponse, "GET");
929
- if (getResult) return getResult;
930
- }
931
- return {
932
- kind: "not-mcp",
933
- reason: `unexpected status ${postResponse.status} for initialize`
934
- };
935
- } finally {
936
- clearTimeout(timer);
937
- }
938
- },
939
- catch: (cause) => cause
940
- }).pipe(
941
- Effect7.catch(
942
- (cause) => Effect7.succeed({
943
- kind: "unreachable",
944
- reason: cause instanceof Error ? cause.message : String(cause)
945
- })
946
- )
947
- );
948
- return outcome;
949
- }).pipe(Effect7.withSpan("mcp.plugin.probe_shape"));
950
-
951
- // src/sdk/plugin.ts
952
- import {
953
- SECRET_REF_PREFIX
954
- } from "@executor-js/config";
955
- var toStoredSourceData = (config) => {
956
- if (config.transport === "stdio") {
957
- return {
958
- transport: "stdio",
959
- command: config.command,
960
- args: config.args,
961
- env: config.env,
962
- cwd: config.cwd
963
- };
964
- }
965
- return {
966
- transport: "remote",
967
- endpoint: config.endpoint,
968
- remoteTransport: config.remoteTransport ?? "auto",
969
- queryParams: config.queryParams,
970
- headers: config.headers,
971
- auth: config.auth ?? { kind: "none" }
972
- };
973
- };
974
- var normalizeNamespace = (config) => config.namespace ?? deriveMcpNamespace({
975
- name: config.name,
976
- endpoint: config.transport === "remote" ? config.endpoint : void 0,
977
- command: config.transport === "stdio" ? config.command : void 0
978
- });
979
- var toBinding = (entry) => new McpToolBinding({
980
- toolId: entry.toolId,
981
- toolName: entry.toolName,
982
- description: entry.description,
983
- inputSchema: entry.inputSchema,
984
- outputSchema: entry.outputSchema,
985
- annotations: entry.annotations
986
- });
987
- var makeOAuthProvider = (accessToken) => ({
988
- get redirectUrl() {
989
- return "http://localhost/oauth/callback";
990
- },
991
- get clientMetadata() {
992
- return {
993
- redirect_uris: ["http://localhost/oauth/callback"],
994
- grant_types: ["authorization_code", "refresh_token"],
995
- response_types: ["code"],
996
- token_endpoint_auth_method: "none",
997
- client_name: "Executor"
998
- };
999
- },
1000
- clientInformation: () => void 0,
1001
- saveClientInformation: () => void 0,
1002
- tokens: () => ({ access_token: accessToken, token_type: "Bearer" }),
1003
- saveTokens: () => void 0,
1004
- redirectToAuthorization: async () => {
1005
- throw new Error("MCP OAuth re-authorization required");
1006
- },
1007
- saveCodeVerifier: () => void 0,
1008
- codeVerifier: () => {
1009
- throw new Error("No active PKCE verifier");
1010
- },
1011
- saveDiscoveryState: () => void 0,
1012
- discoveryState: () => void 0
1013
- });
1014
- var remoteConnectionError = (message) => new McpConnectionError({ transport: "remote", message });
1015
- var mcpDiscoveryError = (message) => new McpToolDiscoveryError({ stage: "list_tools", message });
1016
- var resolveSecretBackedMap = (values, ctx) => resolveSharedSecretBackedMap({
1017
- values,
1018
- getSecret: ctx.secrets.get,
1019
- onMissing: (_name, value) => remoteConnectionError(`Failed to resolve secret "${value.secretId}"`),
1020
- onError: (err, _name, value) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError(`Failed to resolve secret "${value.secretId}"`) : err
1021
- }).pipe(
1022
- Effect8.mapError(
1023
- (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError("Failed to resolve secret") : err
1024
- )
1025
- );
1026
- var plainStringMap = (values) => {
1027
- if (!values) return void 0;
1028
- const entries = Object.entries(values).filter(
1029
- (entry) => typeof entry[1] === "string"
1030
- );
1031
- return entries.length > 0 ? Object.fromEntries(entries) : void 0;
1032
- };
1033
- var resolveConnectorInput = (sd, ctx, allowStdio) => {
1034
- if (sd.transport === "stdio") {
1035
- if (!allowStdio) {
1036
- return Effect8.fail(
1037
- new McpConnectionError({
1038
- transport: "stdio",
1039
- message: "MCP stdio transport is disabled. Enable it by passing `dangerouslyAllowStdioMCP: true` to mcpPlugin() \u2014 only safe for trusted local contexts."
1040
- })
1041
- );
1042
- }
1043
- return Effect8.succeed({
1044
- transport: "stdio",
1045
- command: sd.command,
1046
- args: sd.args,
1047
- env: sd.env,
1048
- cwd: sd.cwd
1049
- });
1050
- }
1051
- return Effect8.gen(function* () {
1052
- const resolvedHeaders = yield* resolveSecretBackedMap(sd.headers, ctx);
1053
- const resolvedQueryParams = yield* resolveSecretBackedMap(sd.queryParams, ctx);
1054
- const headers = { ...resolvedHeaders ?? {} };
1055
- let authProvider;
1056
- const auth = sd.auth;
1057
- if (auth.kind === "header") {
1058
- const val = yield* ctx.secrets.get(auth.secretId).pipe(
1059
- Effect8.mapError(
1060
- (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError(`Failed to resolve secret "${auth.secretId}"`) : err
1061
- )
1062
- );
1063
- if (val === null) {
1064
- return yield* Effect8.fail(
1065
- remoteConnectionError(`Failed to resolve secret "${auth.secretId}"`)
1066
- );
1067
- }
1068
- headers[auth.headerName] = auth.prefix ? `${auth.prefix}${val}` : val;
1069
- } else if (auth.kind === "oauth2") {
1070
- const accessToken = yield* ctx.connections.accessToken(auth.connectionId).pipe(
1071
- Effect8.mapError(
1072
- (err) => remoteConnectionError(
1073
- `Failed to resolve OAuth connection "${auth.connectionId}": ${"message" in err ? err.message : String(err)}`
1074
- )
1075
- )
1076
- );
1077
- authProvider = makeOAuthProvider(accessToken);
1078
- }
1079
- return {
1080
- transport: "remote",
1081
- endpoint: sd.endpoint,
1082
- remoteTransport: sd.remoteTransport,
1083
- queryParams: resolvedQueryParams,
1084
- headers: Object.keys(headers).length > 0 ? headers : void 0,
1085
- authProvider
1086
- };
1087
- });
1088
- };
1089
- var makeRuntime = () => Effect8.gen(function* () {
1090
- const cacheScope = yield* Scope.make();
1091
- const pendingConnectors = /* @__PURE__ */ new Map();
1092
- const connectionCache = yield* ScopedCache2.make({
1093
- lookup: (key) => Effect8.acquireRelease(
1094
- Effect8.suspend(() => {
1095
- const connector = pendingConnectors.get(key);
1096
- if (!connector) {
1097
- return Effect8.fail(
1098
- new McpConnectionError({
1099
- transport: "auto",
1100
- message: `No pending connector for key: ${key}`
1101
- })
1102
- );
1103
- }
1104
- return connector;
1105
- }),
1106
- (connection) => Effect8.promise(() => connection.close().catch(() => {
1107
- }))
1108
- ),
1109
- capacity: 64,
1110
- timeToLive: Duration.minutes(5)
1111
- }).pipe(Scope.provide(cacheScope));
1112
- return { connectionCache, pendingConnectors, cacheScope };
1113
- });
1114
- var secretRef = (id) => `${SECRET_REF_PREFIX}${id}`;
1115
- var authToConfig = (auth) => {
1116
- if (!auth) return void 0;
1117
- if (auth.kind === "none") return { kind: "none" };
1118
- if (auth.kind === "header") {
1119
- return {
1120
- kind: "header",
1121
- headerName: auth.headerName,
1122
- secret: secretRef(auth.secretId),
1123
- prefix: auth.prefix
1124
- };
1125
- }
1126
- return {
1127
- kind: "oauth2",
1128
- connectionId: auth.connectionId
1129
- };
1130
- };
1131
- var toMcpConfigEntry = (namespace, sourceName, config) => {
1132
- if (config.transport === "stdio") {
1133
- const entry2 = {
1134
- kind: "mcp",
1135
- transport: "stdio",
1136
- name: sourceName,
1137
- command: config.command,
1138
- args: config.args,
1139
- env: config.env,
1140
- cwd: config.cwd,
1141
- namespace
1142
- };
1143
- return entry2;
1144
- }
1145
- const entry = {
1146
- kind: "mcp",
1147
- transport: "remote",
1148
- name: sourceName,
1149
- endpoint: config.endpoint,
1150
- remoteTransport: config.remoteTransport,
1151
- queryParams: plainStringMap(config.queryParams),
1152
- headers: plainStringMap(config.headers),
1153
- namespace,
1154
- auth: authToConfig(config.auth)
1155
- };
1156
- return entry;
1157
- };
1158
- var mcpPlugin = definePlugin((options) => {
1159
- const allowStdio = options?.dangerouslyAllowStdioMCP ?? false;
1160
- const runtimeRef = { current: null };
1161
- const ensureRuntime = () => runtimeRef.current ? Effect8.succeed(runtimeRef.current) : makeRuntime().pipe(
1162
- Effect8.tap(
1163
- (rt) => Effect8.sync(() => {
1164
- runtimeRef.current = rt;
1165
- })
1166
- )
1167
- );
1168
- return {
1169
- id: "mcp",
1170
- packageName: "@executor-js/plugin-mcp",
1171
- schema: mcpSchema,
1172
- storage: (deps) => makeMcpStore(deps),
1173
- extension: (ctx) => {
1174
- const probeEndpoint = (input) => Effect8.gen(function* () {
1175
- const endpoint = typeof input === "string" ? input : input.endpoint;
1176
- const trimmed = endpoint.trim();
1177
- if (!trimmed) {
1178
- return yield* Effect8.fail(remoteConnectionError("Endpoint URL is required"));
1179
- }
1180
- const name = yield* Effect8.try({
1181
- try: () => new URL(trimmed).hostname,
1182
- catch: () => "mcp"
1183
- }).pipe(
1184
- Effect8.orElseSucceed(() => "mcp")
1185
- );
1186
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
1187
- const probeHeaders = typeof input === "string" ? void 0 : yield* resolveSecretBackedMap(input.headers, ctx);
1188
- const probeQueryParams = typeof input === "string" ? void 0 : yield* resolveSecretBackedMap(input.queryParams, ctx);
1189
- const connector = createMcpConnector({
1190
- transport: "remote",
1191
- endpoint: trimmed,
1192
- headers: probeHeaders,
1193
- queryParams: probeQueryParams
1194
- });
1195
- const result = yield* discoverTools(connector).pipe(
1196
- Effect8.map((m) => ({ ok: true, manifest: m })),
1197
- Effect8.catch(() => Effect8.succeed({ ok: false, manifest: null })),
1198
- Effect8.withSpan("mcp.plugin.discover_tools")
1199
- );
1200
- if (result.ok && result.manifest) {
1201
- return {
1202
- connected: true,
1203
- requiresOAuth: false,
1204
- name: result.manifest.server?.name ?? name,
1205
- namespace,
1206
- toolCount: result.manifest.tools.length,
1207
- serverName: result.manifest.server?.name ?? null
1208
- };
1209
- }
1210
- const shape = yield* probeMcpEndpointShape(trimmed, {
1211
- headers: probeHeaders,
1212
- queryParams: probeQueryParams
1213
- });
1214
- if (shape.kind !== "mcp") {
1215
- return yield* Effect8.fail(
1216
- remoteConnectionError(
1217
- shape.kind === "not-mcp" ? `Endpoint does not look like an MCP server: ${shape.reason}` : `Could not reach endpoint: ${shape.reason}`
1218
- )
1219
- );
1220
- }
1221
- const probeResult = yield* ctx.oauth.probe({
1222
- endpoint: trimmed,
1223
- headers: probeHeaders,
1224
- queryParams: probeQueryParams
1225
- }).pipe(
1226
- Effect8.map(() => true),
1227
- Effect8.catch(() => Effect8.succeed(false)),
1228
- Effect8.withSpan("mcp.plugin.probe_oauth")
1229
- );
1230
- if (probeResult) {
1231
- return {
1232
- connected: false,
1233
- requiresOAuth: true,
1234
- name,
1235
- namespace,
1236
- toolCount: null,
1237
- serverName: null
1238
- };
1239
- }
1240
- return yield* Effect8.fail(
1241
- remoteConnectionError("MCP server requires authentication but OAuth discovery failed")
1242
- );
1243
- }).pipe(
1244
- Effect8.withSpan("mcp.plugin.probe_endpoint", {
1245
- attributes: { "mcp.endpoint": typeof input === "string" ? input : input.endpoint }
1246
- })
1247
- );
1248
- const configFile = options?.configFile;
1249
- const addSource = (config) => Effect8.gen(function* () {
1250
- const namespace = normalizeNamespace(config);
1251
- const sd = toStoredSourceData(config);
1252
- const resolved = yield* resolveConnectorInput(sd, ctx, allowStdio).pipe(
1253
- Effect8.result,
1254
- Effect8.withSpan("mcp.plugin.resolve_connector", {
1255
- attributes: {
1256
- "mcp.source.namespace": namespace,
1257
- "mcp.source.transport": sd.transport
1258
- }
1259
- })
1260
- );
1261
- if (Result.isFailure(resolved) && sd.transport === "stdio") {
1262
- return yield* Effect8.fail(resolved.failure);
1263
- }
1264
- const discovery = Result.isSuccess(resolved) ? yield* discoverTools(createMcpConnector(resolved.success)).pipe(
1265
- Effect8.mapError(
1266
- (err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`)
1267
- ),
1268
- Effect8.result,
1269
- Effect8.withSpan("mcp.plugin.discover_tools", {
1270
- attributes: { "mcp.source.namespace": namespace }
1271
- })
1272
- ) : Result.fail(resolved.failure);
1273
- const manifest = Result.isSuccess(discovery) ? discovery.success : { server: void 0, tools: [] };
1274
- const sourceName = config.name ?? manifest.server?.name ?? namespace;
1275
- yield* ctx.transaction(
1276
- Effect8.gen(function* () {
1277
- yield* ctx.storage.removeBindingsByNamespace(namespace, config.scope);
1278
- yield* ctx.storage.removeSource(namespace, config.scope);
1279
- yield* ctx.storage.putSource({
1280
- namespace,
1281
- scope: config.scope,
1282
- name: sourceName,
1283
- config: sd
1284
- });
1285
- yield* ctx.storage.putBindings(
1286
- namespace,
1287
- config.scope,
1288
- manifest.tools.map((e) => ({
1289
- toolId: `${namespace}.${e.toolId}`,
1290
- binding: toBinding(e)
1291
- }))
1292
- );
1293
- yield* ctx.core.sources.register({
1294
- id: namespace,
1295
- scope: config.scope,
1296
- kind: "mcp",
1297
- name: sourceName,
1298
- url: sd.transport === "remote" ? sd.endpoint : void 0,
1299
- canRemove: true,
1300
- canRefresh: true,
1301
- canEdit: sd.transport === "remote",
1302
- tools: manifest.tools.map((e) => ({
1303
- name: e.toolId,
1304
- description: e.description ?? `MCP tool: ${e.toolName}`,
1305
- inputSchema: e.inputSchema,
1306
- outputSchema: e.outputSchema
1307
- }))
1308
- });
1309
- })
1310
- ).pipe(
1311
- Effect8.withSpan("mcp.plugin.persist_source", {
1312
- attributes: {
1313
- "mcp.source.namespace": namespace,
1314
- "mcp.source.tool_count": manifest.tools.length
1315
- }
1316
- })
1317
- );
1318
- if (configFile) {
1319
- yield* configFile.upsertSource(toMcpConfigEntry(namespace, sourceName, config)).pipe(Effect8.withSpan("mcp.plugin.config_file.upsert"));
1320
- }
1321
- if (Result.isFailure(discovery)) {
1322
- return yield* Effect8.fail(discovery.failure);
1323
- }
1324
- return { toolCount: manifest.tools.length, namespace };
1325
- }).pipe(
1326
- Effect8.withSpan("mcp.plugin.add_source", {
1327
- attributes: {
1328
- "mcp.source.transport": config.transport,
1329
- "mcp.source.name": config.name
1330
- }
1331
- })
1332
- );
1333
- const removeSource = (namespace, scope) => Effect8.gen(function* () {
1334
- yield* ctx.transaction(
1335
- Effect8.gen(function* () {
1336
- yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1337
- yield* ctx.storage.removeSource(namespace, scope);
1338
- yield* ctx.core.sources.unregister(namespace);
1339
- })
1340
- ).pipe(Effect8.withSpan("mcp.plugin.persist_remove"));
1341
- if (configFile) {
1342
- yield* configFile.removeSource(namespace).pipe(Effect8.withSpan("mcp.plugin.config_file.remove"));
1343
- }
1344
- }).pipe(
1345
- Effect8.withSpan("mcp.plugin.remove_source", {
1346
- attributes: { "mcp.source.namespace": namespace }
1347
- })
1348
- );
1349
- const refreshSource = (namespace, scope) => Effect8.gen(function* () {
1350
- const sd = yield* ctx.storage.getSourceConfig(namespace, scope).pipe(
1351
- Effect8.withSpan("mcp.plugin.load_source_config", {
1352
- attributes: { "mcp.source.namespace": namespace }
1353
- })
1354
- );
1355
- if (!sd) {
1356
- return yield* Effect8.fail(
1357
- remoteConnectionError(`No stored config for MCP source "${namespace}"`)
1358
- );
1359
- }
1360
- const ci = yield* resolveConnectorInput(sd, ctx, allowStdio).pipe(
1361
- Effect8.withSpan("mcp.plugin.resolve_connector", {
1362
- attributes: {
1363
- "mcp.source.namespace": namespace,
1364
- "mcp.source.transport": sd.transport
1365
- }
1366
- })
1367
- );
1368
- const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
1369
- Effect8.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`)),
1370
- Effect8.withSpan("mcp.plugin.discover_tools", {
1371
- attributes: { "mcp.source.namespace": namespace }
1372
- })
1373
- );
1374
- const existing = yield* ctx.storage.getSource(namespace, scope);
1375
- const sourceName = manifest.server?.name ?? existing?.name ?? namespace;
1376
- yield* ctx.transaction(
1377
- Effect8.gen(function* () {
1378
- yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1379
- yield* ctx.core.sources.unregister(namespace);
1380
- yield* ctx.storage.putBindings(
1381
- namespace,
1382
- scope,
1383
- manifest.tools.map((e) => ({
1384
- toolId: `${namespace}.${e.toolId}`,
1385
- binding: toBinding(e)
1386
- }))
1387
- );
1388
- yield* ctx.core.sources.register({
1389
- id: namespace,
1390
- scope,
1391
- kind: "mcp",
1392
- name: sourceName,
1393
- url: sd.transport === "remote" ? sd.endpoint : void 0,
1394
- canRemove: true,
1395
- canRefresh: true,
1396
- canEdit: sd.transport === "remote",
1397
- tools: manifest.tools.map((e) => ({
1398
- name: e.toolId,
1399
- description: e.description ?? `MCP tool: ${e.toolName}`,
1400
- inputSchema: e.inputSchema,
1401
- outputSchema: e.outputSchema
1402
- }))
1403
- });
1404
- })
1405
- ).pipe(
1406
- Effect8.withSpan("mcp.plugin.persist_source", {
1407
- attributes: {
1408
- "mcp.source.namespace": namespace,
1409
- "mcp.source.tool_count": manifest.tools.length
1410
- }
1411
- })
1412
- );
1413
- return { toolCount: manifest.tools.length };
1414
- }).pipe(
1415
- Effect8.withSpan("mcp.plugin.refresh_source", {
1416
- attributes: { "mcp.source.namespace": namespace }
1417
- })
1418
- );
1419
- const updateSource = (namespace, scope, input) => Effect8.gen(function* () {
1420
- const existing = yield* ctx.storage.getSource(namespace, scope);
1421
- if (!existing || existing.config.transport !== "remote") return;
1422
- const remote = existing.config;
1423
- const updatedConfig = {
1424
- ...remote,
1425
- ...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
1426
- ...input.headers !== void 0 ? { headers: input.headers } : {},
1427
- ...input.auth !== void 0 ? { auth: input.auth } : {},
1428
- ...input.queryParams !== void 0 ? { queryParams: input.queryParams } : {}
1429
- };
1430
- yield* ctx.storage.putSource({
1431
- namespace,
1432
- scope,
1433
- name: input.name?.trim() || existing.name,
1434
- config: updatedConfig
1435
- });
1436
- }).pipe(
1437
- Effect8.withSpan("mcp.plugin.update_source", {
1438
- attributes: { "mcp.source.namespace": namespace }
1439
- })
1440
- );
1441
- const getSource = (namespace, scope) => ctx.storage.getSource(namespace, scope).pipe(
1442
- Effect8.withSpan("mcp.plugin.get_source", {
1443
- attributes: { "mcp.source.namespace": namespace }
1444
- })
1445
- );
1446
- return {
1447
- probeEndpoint,
1448
- addSource,
1449
- removeSource,
1450
- refreshSource,
1451
- getSource,
1452
- updateSource
1453
- };
1454
- },
1455
- invokeTool: ({ ctx, toolRow, args, elicit }) => Effect8.gen(function* () {
1456
- const runtime = yield* ensureRuntime();
1457
- const toolScope = toolRow.scope_id;
1458
- const entry = yield* ctx.storage.getBinding(toolRow.id, toolScope).pipe(
1459
- Effect8.withSpan("mcp.plugin.load_binding", {
1460
- attributes: { "mcp.tool.name": toolRow.id }
1461
- })
1462
- );
1463
- if (!entry) {
1464
- return yield* Effect8.fail(new Error(`No MCP binding found for tool "${toolRow.id}"`));
1465
- }
1466
- const sd = yield* ctx.storage.getSourceConfig(entry.namespace, toolScope).pipe(
1467
- Effect8.withSpan("mcp.plugin.load_source_config", {
1468
- attributes: { "mcp.source.namespace": entry.namespace }
1469
- })
1470
- );
1471
- if (!sd) {
1472
- return yield* Effect8.fail(
1473
- new Error(`No MCP source config for namespace "${entry.namespace}"`)
1474
- );
1475
- }
1476
- return yield* invokeMcpTool({
1477
- toolId: toolRow.id,
1478
- toolName: entry.binding.toolName,
1479
- args,
1480
- sourceData: sd,
1481
- invokerScope: ctx.scopes[0].id,
1482
- resolveConnector: () => resolveConnectorInput(sd, ctx, allowStdio).pipe(
1483
- Effect8.flatMap((ci) => createMcpConnector(ci)),
1484
- Effect8.mapError(
1485
- (err) => err instanceof McpConnectionError ? err : new McpConnectionError({
1486
- transport: "auto",
1487
- message: err instanceof Error ? err.message : String(err)
1488
- })
1489
- ),
1490
- Effect8.withSpan("mcp.plugin.resolve_connector", {
1491
- attributes: {
1492
- "mcp.source.namespace": entry.namespace,
1493
- "mcp.source.transport": sd.transport
1494
- }
1495
- })
1496
- ),
1497
- connectionCache: runtime.connectionCache,
1498
- pendingConnectors: runtime.pendingConnectors,
1499
- elicit
1500
- });
1501
- }).pipe(
1502
- Effect8.withSpan("mcp.plugin.invoke_tool", {
1503
- attributes: {
1504
- "mcp.tool.name": toolRow.id,
1505
- "mcp.tool.source_id": toolRow.source_id
1506
- }
1507
- })
1508
- ),
1509
- detect: ({ ctx, url }) => Effect8.gen(function* () {
1510
- const trimmed = url.trim();
1511
- if (!trimmed) return null;
1512
- const parsed = yield* Effect8.try({
1513
- try: () => new URL(trimmed),
1514
- catch: (cause) => cause
1515
- }).pipe(Effect8.option);
1516
- if (parsed._tag === "None") return null;
1517
- const name = parsed.value.hostname || "mcp";
1518
- const namespace = deriveMcpNamespace({ endpoint: trimmed });
1519
- const connector = createMcpConnector({
1520
- transport: "remote",
1521
- endpoint: trimmed
1522
- });
1523
- const connected = yield* discoverTools(connector).pipe(
1524
- Effect8.map(() => true),
1525
- Effect8.catch(() => Effect8.succeed(false)),
1526
- Effect8.withSpan("mcp.plugin.discover_tools")
1527
- );
1528
- if (connected) {
1529
- return new SourceDetectionResult({
1530
- kind: "mcp",
1531
- confidence: "high",
1532
- endpoint: trimmed,
1533
- name,
1534
- namespace
1535
- });
1536
- }
1537
- const shape = yield* probeMcpEndpointShape(trimmed);
1538
- if (shape.kind !== "mcp") return null;
1539
- const probeOk = yield* ctx.oauth.probe({ endpoint: trimmed }).pipe(
1540
- Effect8.map(() => true),
1541
- Effect8.catch(() => Effect8.succeed(false)),
1542
- Effect8.withSpan("mcp.plugin.probe_oauth")
1543
- );
1544
- if (!probeOk) return null;
1545
- return new SourceDetectionResult({
1546
- kind: "mcp",
1547
- confidence: "high",
1548
- endpoint: trimmed,
1549
- name,
1550
- namespace
1551
- });
1552
- }).pipe(
1553
- Effect8.catch(() => Effect8.succeed(null)),
1554
- Effect8.withSpan("mcp.plugin.detect", {
1555
- attributes: { "mcp.endpoint": url }
1556
- })
1557
- ),
1558
- // Honor upstream destructiveHint from MCP ToolAnnotations.
1559
- // Bindings are fetched per scope so shadowed sources (e.g. an org-level
1560
- // source overridden per-user) each resolve against their own scope's
1561
- // row rather than collapsing onto whichever row the scoped adapter
1562
- // sees first.
1563
- resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect8.gen(function* () {
1564
- const scopes = new Set(toolRows.map((row) => row.scope_id));
1565
- const entries = yield* Effect8.forEach(
1566
- [...scopes],
1567
- (scope) => Effect8.gen(function* () {
1568
- const list = yield* ctx.storage.listBindingsBySource(sourceId, scope);
1569
- const byId = new Map(list.map((e) => [e.toolId, e.binding]));
1570
- return [scope, byId];
1571
- }),
1572
- { concurrency: "unbounded" }
1573
- );
1574
- const byScope = new Map(entries);
1575
- const out = {};
1576
- for (const row of toolRows) {
1577
- const binding = byScope.get(row.scope_id)?.get(row.id);
1578
- const ann = binding?.annotations;
1579
- if (ann?.destructiveHint === true) {
1580
- out[row.id] = {
1581
- requiresApproval: true,
1582
- approvalDescription: ann.title ?? binding?.toolName ?? row.id
1583
- };
1584
- } else {
1585
- out[row.id] = { requiresApproval: false };
1586
- }
1587
- }
1588
- return out;
1589
- }),
1590
- removeSource: ({ ctx, sourceId, scope }) => Effect8.gen(function* () {
1591
- yield* ctx.storage.removeBindingsByNamespace(sourceId, scope);
1592
- yield* ctx.storage.removeSource(sourceId, scope);
1593
- }),
1594
- refreshSource: () => Effect8.void,
1595
- // Connection refresh for oauth2-minted sources is owned by the
1596
- // canonical `"oauth2"` ConnectionProvider that core registers via
1597
- // `makeOAuth2Service`. No MCP-specific provider needed.
1598
- close: () => Effect8.gen(function* () {
1599
- const runtime = runtimeRef.current;
1600
- if (runtime) {
1601
- runtime.pendingConnectors.clear();
1602
- yield* ScopedCache2.invalidateAll(runtime.connectionCache);
1603
- yield* Scope.close(runtime.cacheScope, Exit2.void);
1604
- runtimeRef.current = null;
1605
- }
1606
- }).pipe(Effect8.withSpan("mcp.plugin.close")),
1607
- // HTTP transport. `McpHandlers` requires `McpExtensionService`; the
1608
- // host satisfies it via the `extensionService` Tag — at boot for
1609
- // local, per request for cloud.
1610
- routes: () => McpGroup,
1611
- handlers: () => McpHandlers,
1612
- extensionService: McpExtensionService
1613
- };
1614
- });
1615
-
1616
- export {
1617
- McpConnectionAuth,
1618
- mcpSchema,
1619
- makeMcpStore,
1620
- mcpPlugin
1621
- };
1622
- //# sourceMappingURL=chunk-C2GNZGFJ.js.map