@executor-js/plugin-mcp 0.0.2 → 0.1.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.
@@ -48,12 +48,20 @@ var McpStdioSourceData = Schema.Struct({
48
48
  cwd: Schema.optional(Schema.String)
49
49
  });
50
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
+ });
51
58
  var McpToolBinding = class extends Schema.Class("McpToolBinding")({
52
59
  toolId: Schema.String,
53
60
  toolName: Schema.String,
54
61
  description: Schema.NullOr(Schema.String),
55
62
  inputSchema: Schema.optional(Schema.Unknown),
56
- outputSchema: Schema.optional(Schema.Unknown)
63
+ outputSchema: Schema.optional(Schema.Unknown),
64
+ annotations: Schema.optional(McpToolAnnotations)
57
65
  }) {
58
66
  };
59
67
 
@@ -98,6 +106,19 @@ var makeMcpStore = ({
98
106
  adapter: db
99
107
  }) => {
100
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
+ }),
101
122
  getBinding: (toolId, scope) => Effect2.gen(function* () {
102
123
  const row = yield* db.findOne({
103
124
  model: "mcp_binding",
@@ -200,18 +221,13 @@ var makeMcpStore = ({
200
221
  };
201
222
 
202
223
  // src/sdk/plugin.ts
203
- import { Duration, Effect as Effect7, Exit as Exit2, Result, Scope, ScopedCache as ScopedCache2 } from "effect";
204
- import {
205
- SourceDetectionResult,
206
- definePlugin,
207
- resolveSecretBackedMap as resolveSharedSecretBackedMap
208
- } from "@executor-js/sdk/core";
224
+ import { Duration, Effect as Effect8, Exit as Exit2, Result, Scope, ScopedCache as ScopedCache2 } from "effect";
209
225
 
210
- // src/sdk/connection.ts
211
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
212
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
213
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
214
- import { Effect as Effect3 } from "effect";
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";
215
231
 
216
232
  // src/sdk/errors.ts
217
233
  import { Schema as Schema3 } from "effect";
@@ -251,7 +267,252 @@ var McpOAuthError = class extends Schema3.TaggedErrorClass()(
251
267
  ) {
252
268
  };
253
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
+
254
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";
255
516
  var buildEndpointUrl = (endpoint, queryParams) => {
256
517
  const url = new URL(endpoint);
257
518
  for (const [key, value] of Object.entries(queryParams)) {
@@ -261,23 +522,26 @@ var buildEndpointUrl = (endpoint, queryParams) => {
261
522
  };
262
523
  var createClient = () => new Client(
263
524
  { name: "executor-mcp", version: "0.1.0" },
264
- { capabilities: { elicitation: { form: {}, url: {} } } }
525
+ {
526
+ capabilities: { elicitation: { form: {}, url: {} } },
527
+ jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
528
+ }
265
529
  );
266
530
  var connectionFromClient = (client) => ({
267
531
  client,
268
532
  close: () => client.close()
269
533
  });
270
- var connectClient = (input) => Effect3.gen(function* () {
534
+ var connectClient = (input) => Effect4.gen(function* () {
271
535
  const client = createClient();
272
536
  const transportInstance = input.createTransport();
273
- yield* Effect3.tryPromise({
537
+ yield* Effect4.tryPromise({
274
538
  try: () => client.connect(transportInstance),
275
539
  catch: (cause) => new McpConnectionError({
276
540
  transport: input.transport,
277
541
  message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
278
542
  })
279
543
  }).pipe(
280
- Effect3.withSpan("plugin.mcp.connection.handshake", {
544
+ Effect4.withSpan("plugin.mcp.connection.handshake", {
281
545
  attributes: { "plugin.mcp.transport": input.transport }
282
546
  })
283
547
  );
@@ -287,15 +551,15 @@ var createMcpConnector = (input) => {
287
551
  if (input.transport === "stdio") {
288
552
  const command = input.command.trim();
289
553
  if (!command) {
290
- return Effect3.fail(
554
+ return Effect4.fail(
291
555
  new McpConnectionError({
292
556
  transport: "stdio",
293
557
  message: "MCP stdio transport requires a command"
294
558
  })
295
559
  );
296
560
  }
297
- return Effect3.gen(function* () {
298
- const { createStdioTransport } = yield* Effect3.tryPromise({
561
+ return Effect4.gen(function* () {
562
+ const { createStdioTransport } = yield* Effect4.tryPromise({
299
563
  try: () => import("./stdio-connector-KNHLETKM.js"),
300
564
  catch: (cause) => new McpConnectionError({
301
565
  transport: "stdio",
@@ -333,30 +597,31 @@ var createMcpConnector = (input) => {
333
597
  });
334
598
  if (remoteTransport === "streamable-http") return connectStreamableHttp;
335
599
  if (remoteTransport === "sse") return connectSse;
336
- return connectStreamableHttp.pipe(Effect3.catch(() => connectSse));
600
+ return connectStreamableHttp.pipe(Effect4.catch(() => connectSse));
337
601
  };
338
602
 
339
603
  // src/sdk/discover.ts
340
- import { Effect as Effect4 } from "effect";
604
+ import { Effect as Effect5 } from "effect";
341
605
 
342
606
  // src/sdk/manifest.ts
343
- import { Schema as Schema4 } from "effect";
344
- var ListedTool = Schema4.Struct({
345
- name: Schema4.String,
346
- description: Schema4.optional(Schema4.NullOr(Schema4.String)),
347
- inputSchema: Schema4.optional(Schema4.Unknown),
348
- parameters: Schema4.optional(Schema4.Unknown),
349
- outputSchema: Schema4.optional(Schema4.Unknown)
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)
350
615
  });
351
- var ListToolsResult = Schema4.Struct({
352
- tools: Schema4.Array(ListedTool)
616
+ var ListToolsResult = Schema6.Struct({
617
+ tools: Schema6.Array(ListedTool)
353
618
  });
354
- var ServerInfo = Schema4.Struct({
355
- name: Schema4.optional(Schema4.String),
356
- version: Schema4.optional(Schema4.String)
619
+ var ServerInfo = Schema6.Struct({
620
+ name: Schema6.optional(Schema6.String),
621
+ version: Schema6.optional(Schema6.String)
357
622
  });
358
- var decodeListToolsResult = Schema4.decodeUnknownOption(ListToolsResult);
359
- var decodeServerInfo = Schema4.decodeUnknownOption(ServerInfo);
623
+ var decodeListToolsResult = Schema6.decodeUnknownOption(ListToolsResult);
624
+ var decodeServerInfo = Schema6.decodeUnknownOption(ServerInfo);
360
625
  var isListToolsResult = (value) => decodeListToolsResult(value)._tag === "Some";
361
626
  var sanitize = (value) => {
362
627
  const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
@@ -385,7 +650,8 @@ var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
385
650
  toolName,
386
651
  description: tool.description ?? null,
387
652
  inputSchema: tool.inputSchema ?? tool.parameters,
388
- outputSchema: tool.outputSchema
653
+ outputSchema: tool.outputSchema,
654
+ annotations: tool.annotations
389
655
  }
390
656
  ];
391
657
  });
@@ -409,16 +675,16 @@ var deriveMcpNamespace = (input) => {
409
675
  };
410
676
 
411
677
  // src/sdk/discover.ts
412
- var discoverTools = (connector) => Effect4.gen(function* () {
678
+ var discoverTools = (connector) => Effect5.gen(function* () {
413
679
  const connection = yield* connector.pipe(
414
- Effect4.mapError(
680
+ Effect5.mapError(
415
681
  (err) => new McpToolDiscoveryError({
416
682
  stage: "connect",
417
683
  message: `Failed connecting to MCP server: ${err.message}`
418
684
  })
419
685
  )
420
686
  );
421
- const listResult = yield* Effect4.tryPromise({
687
+ const listResult = yield* Effect5.tryPromise({
422
688
  try: () => connection.client.listTools(),
423
689
  catch: (cause) => new McpToolDiscoveryError({
424
690
  stage: "list_tools",
@@ -426,9 +692,9 @@ var discoverTools = (connector) => Effect4.gen(function* () {
426
692
  })
427
693
  });
428
694
  if (!isListToolsResult(listResult)) {
429
- yield* Effect4.promise(() => connection.close().catch(() => {
695
+ yield* Effect5.promise(() => connection.close().catch(() => {
430
696
  }));
431
- return yield* Effect4.fail(
697
+ return yield* Effect5.fail(
432
698
  new McpToolDiscoveryError({
433
699
  stage: "list_tools",
434
700
  message: "MCP listTools response did not match the expected schema"
@@ -438,13 +704,13 @@ var discoverTools = (connector) => Effect4.gen(function* () {
438
704
  const manifest = extractManifestFromListToolsResult(listResult, {
439
705
  serverInfo: connection.client.getServerVersion?.()
440
706
  });
441
- yield* Effect4.promise(() => connection.close().catch(() => {
707
+ yield* Effect5.promise(() => connection.close().catch(() => {
442
708
  }));
443
709
  return manifest;
444
710
  });
445
711
 
446
712
  // src/sdk/invoke.ts
447
- import { Cause, Effect as Effect5, Exit, Schema as Schema5, ScopedCache } from "effect";
713
+ import { Cause, Effect as Effect6, Exit, Schema as Schema7, ScopedCache } from "effect";
448
714
  import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
449
715
  import {
450
716
  FormElicitation,
@@ -460,21 +726,21 @@ var connectionCacheKey = (sd, invokerScope) => sd.transport === "stdio" ? `stdio
460
726
  // (and user A's tokens).
461
727
  `remote:${invokerScope}:${sd.endpoint}`
462
728
  );
463
- var McpElicitParams = Schema5.Union([
464
- Schema5.Struct({
465
- mode: Schema5.Literal("url"),
466
- message: Schema5.String,
467
- url: Schema5.String,
468
- elicitationId: Schema5.optional(Schema5.String),
469
- id: Schema5.optional(Schema5.String)
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)
470
736
  }),
471
- Schema5.Struct({
472
- mode: Schema5.optional(Schema5.Literal("form")),
473
- message: Schema5.String,
474
- requestedSchema: Schema5.Record(Schema5.String, Schema5.Unknown)
737
+ Schema7.Struct({
738
+ mode: Schema7.optional(Schema7.Literal("form")),
739
+ message: Schema7.String,
740
+ requestedSchema: Schema7.Record(Schema7.String, Schema7.Unknown)
475
741
  })
476
742
  ]);
477
- var decodeElicitParams = Schema5.decodeUnknownSync(McpElicitParams);
743
+ var decodeElicitParams = Schema7.decodeUnknownSync(McpElicitParams);
478
744
  var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitation({
479
745
  message: params.message,
480
746
  url: params.url,
@@ -489,7 +755,7 @@ var installElicitationHandler = (client, elicit) => {
489
755
  async (request) => {
490
756
  const params = decodeElicitParams(request.params);
491
757
  const req = toElicitationRequest(params);
492
- const exit = await Effect5.runPromiseExit(elicit(req));
758
+ const exit = await Effect6.runPromiseExit(elicit(req));
493
759
  if (Exit.isSuccess(exit)) {
494
760
  const response = exit.value;
495
761
  return {
@@ -508,30 +774,30 @@ var installElicitationHandler = (client, elicit) => {
508
774
  }
509
775
  );
510
776
  };
511
- var useConnection = (connection, toolName, args, elicit) => Effect5.gen(function* () {
777
+ var useConnection = (connection, toolName, args, elicit) => Effect6.gen(function* () {
512
778
  installElicitationHandler(connection.client, elicit);
513
- return yield* Effect5.tryPromise({
779
+ return yield* Effect6.tryPromise({
514
780
  try: () => connection.client.callTool({ name: toolName, arguments: args }),
515
781
  catch: (cause) => new McpInvocationError({
516
782
  toolName,
517
783
  message: `MCP tool call failed for ${toolName}: ${cause instanceof Error ? cause.message : String(cause)}`
518
784
  })
519
785
  }).pipe(
520
- Effect5.withSpan("plugin.mcp.client.call_tool", {
786
+ Effect6.withSpan("plugin.mcp.client.call_tool", {
521
787
  attributes: { "mcp.tool.name": toolName }
522
788
  })
523
789
  );
524
790
  });
525
791
  var invokeMcpTool = (input) => {
526
792
  const transport = input.sourceData.transport === "stdio" ? "stdio" : input.sourceData.remoteTransport ?? "auto";
527
- return Effect5.gen(function* () {
793
+ return Effect6.gen(function* () {
528
794
  const cacheKey = connectionCacheKey(input.sourceData, input.invokerScope);
529
795
  const args = asRecord(input.args);
530
796
  const connector = input.resolveConnector();
531
797
  input.pendingConnectors.set(cacheKey, connector);
532
798
  const cacheHit = yield* ScopedCache.has(input.connectionCache, cacheKey);
533
799
  const firstConnection = yield* ScopedCache.get(input.connectionCache, cacheKey).pipe(
534
- Effect5.withSpan("plugin.mcp.connection.acquire", {
800
+ Effect6.withSpan("plugin.mcp.connection.acquire", {
535
801
  attributes: {
536
802
  "plugin.mcp.transport": transport,
537
803
  "plugin.mcp.cache_key": cacheKey,
@@ -548,8 +814,8 @@ var invokeMcpTool = (input) => {
548
814
  ).pipe(
549
815
  // On failure, invalidate the cache and retry once with a fresh
550
816
  // connection. Matches the old invoker's retry-once semantics.
551
- Effect5.catch(
552
- () => Effect5.gen(function* () {
817
+ Effect6.catch(
818
+ () => Effect6.gen(function* () {
553
819
  yield* ScopedCache.invalidate(input.connectionCache, cacheKey);
554
820
  input.pendingConnectors.set(cacheKey, connector);
555
821
  const fresh = yield* ScopedCache.get(input.connectionCache, cacheKey);
@@ -560,7 +826,7 @@ var invokeMcpTool = (input) => {
560
826
  input.elicit
561
827
  );
562
828
  }).pipe(
563
- Effect5.withSpan("plugin.mcp.invoke.retry", {
829
+ Effect6.withSpan("plugin.mcp.invoke.retry", {
564
830
  attributes: {
565
831
  "plugin.mcp.transport": transport,
566
832
  "plugin.mcp.cache_key": cacheKey,
@@ -571,8 +837,8 @@ var invokeMcpTool = (input) => {
571
837
  )
572
838
  );
573
839
  }).pipe(
574
- Effect5.scoped,
575
- Effect5.withSpan("plugin.mcp.invoke", {
840
+ Effect6.scoped,
841
+ Effect6.withSpan("plugin.mcp.invoke", {
576
842
  attributes: {
577
843
  "mcp.tool.name": input.toolName,
578
844
  "plugin.mcp.tool_id": input.toolId,
@@ -583,7 +849,7 @@ var invokeMcpTool = (input) => {
583
849
  };
584
850
 
585
851
  // src/sdk/probe-shape.ts
586
- import { Effect as Effect6 } from "effect";
852
+ import { Effect as Effect7 } from "effect";
587
853
  var INITIALIZE_BODY = JSON.stringify({
588
854
  jsonrpc: "2.0",
589
855
  id: 1,
@@ -603,10 +869,10 @@ var readHeader = (headers, name) => {
603
869
  }
604
870
  return null;
605
871
  };
606
- var probeMcpEndpointShape = (endpoint, options = {}) => Effect6.gen(function* () {
872
+ var probeMcpEndpointShape = (endpoint, options = {}) => Effect7.gen(function* () {
607
873
  const fetchImpl = options.fetch ?? globalThis.fetch;
608
874
  const timeoutMs = options.timeoutMs ?? 8e3;
609
- const outcome = yield* Effect6.tryPromise({
875
+ const outcome = yield* Effect7.tryPromise({
610
876
  try: async () => {
611
877
  const controller = new AbortController();
612
878
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -672,15 +938,15 @@ var probeMcpEndpointShape = (endpoint, options = {}) => Effect6.gen(function* ()
672
938
  },
673
939
  catch: (cause) => cause
674
940
  }).pipe(
675
- Effect6.catch(
676
- (cause) => Effect6.succeed({
941
+ Effect7.catch(
942
+ (cause) => Effect7.succeed({
677
943
  kind: "unreachable",
678
944
  reason: cause instanceof Error ? cause.message : String(cause)
679
945
  })
680
946
  )
681
947
  );
682
948
  return outcome;
683
- }).pipe(Effect6.withSpan("mcp.plugin.probe_shape"));
949
+ }).pipe(Effect7.withSpan("mcp.plugin.probe_shape"));
684
950
 
685
951
  // src/sdk/plugin.ts
686
952
  import {
@@ -715,7 +981,8 @@ var toBinding = (entry) => new McpToolBinding({
715
981
  toolName: entry.toolName,
716
982
  description: entry.description,
717
983
  inputSchema: entry.inputSchema,
718
- outputSchema: entry.outputSchema
984
+ outputSchema: entry.outputSchema,
985
+ annotations: entry.annotations
719
986
  });
720
987
  var makeOAuthProvider = (accessToken) => ({
721
988
  get redirectUrl() {
@@ -752,7 +1019,7 @@ var resolveSecretBackedMap = (values, ctx) => resolveSharedSecretBackedMap({
752
1019
  onMissing: (_name, value) => remoteConnectionError(`Failed to resolve secret "${value.secretId}"`),
753
1020
  onError: (err, _name, value) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError(`Failed to resolve secret "${value.secretId}"`) : err
754
1021
  }).pipe(
755
- Effect7.mapError(
1022
+ Effect8.mapError(
756
1023
  (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError("Failed to resolve secret") : err
757
1024
  )
758
1025
  );
@@ -766,14 +1033,14 @@ var plainStringMap = (values) => {
766
1033
  var resolveConnectorInput = (sd, ctx, allowStdio) => {
767
1034
  if (sd.transport === "stdio") {
768
1035
  if (!allowStdio) {
769
- return Effect7.fail(
1036
+ return Effect8.fail(
770
1037
  new McpConnectionError({
771
1038
  transport: "stdio",
772
1039
  message: "MCP stdio transport is disabled. Enable it by passing `dangerouslyAllowStdioMCP: true` to mcpPlugin() \u2014 only safe for trusted local contexts."
773
1040
  })
774
1041
  );
775
1042
  }
776
- return Effect7.succeed({
1043
+ return Effect8.succeed({
777
1044
  transport: "stdio",
778
1045
  command: sd.command,
779
1046
  args: sd.args,
@@ -781,7 +1048,7 @@ var resolveConnectorInput = (sd, ctx, allowStdio) => {
781
1048
  cwd: sd.cwd
782
1049
  });
783
1050
  }
784
- return Effect7.gen(function* () {
1051
+ return Effect8.gen(function* () {
785
1052
  const resolvedHeaders = yield* resolveSecretBackedMap(sd.headers, ctx);
786
1053
  const resolvedQueryParams = yield* resolveSecretBackedMap(sd.queryParams, ctx);
787
1054
  const headers = { ...resolvedHeaders ?? {} };
@@ -789,19 +1056,19 @@ var resolveConnectorInput = (sd, ctx, allowStdio) => {
789
1056
  const auth = sd.auth;
790
1057
  if (auth.kind === "header") {
791
1058
  const val = yield* ctx.secrets.get(auth.secretId).pipe(
792
- Effect7.mapError(
1059
+ Effect8.mapError(
793
1060
  (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? remoteConnectionError(`Failed to resolve secret "${auth.secretId}"`) : err
794
1061
  )
795
1062
  );
796
1063
  if (val === null) {
797
- return yield* Effect7.fail(
1064
+ return yield* Effect8.fail(
798
1065
  remoteConnectionError(`Failed to resolve secret "${auth.secretId}"`)
799
1066
  );
800
1067
  }
801
1068
  headers[auth.headerName] = auth.prefix ? `${auth.prefix}${val}` : val;
802
1069
  } else if (auth.kind === "oauth2") {
803
1070
  const accessToken = yield* ctx.connections.accessToken(auth.connectionId).pipe(
804
- Effect7.mapError(
1071
+ Effect8.mapError(
805
1072
  (err) => remoteConnectionError(
806
1073
  `Failed to resolve OAuth connection "${auth.connectionId}": ${"message" in err ? err.message : String(err)}`
807
1074
  )
@@ -819,15 +1086,15 @@ var resolveConnectorInput = (sd, ctx, allowStdio) => {
819
1086
  };
820
1087
  });
821
1088
  };
822
- var makeRuntime = () => Effect7.gen(function* () {
1089
+ var makeRuntime = () => Effect8.gen(function* () {
823
1090
  const cacheScope = yield* Scope.make();
824
1091
  const pendingConnectors = /* @__PURE__ */ new Map();
825
1092
  const connectionCache = yield* ScopedCache2.make({
826
- lookup: (key) => Effect7.acquireRelease(
827
- Effect7.suspend(() => {
1093
+ lookup: (key) => Effect8.acquireRelease(
1094
+ Effect8.suspend(() => {
828
1095
  const connector = pendingConnectors.get(key);
829
1096
  if (!connector) {
830
- return Effect7.fail(
1097
+ return Effect8.fail(
831
1098
  new McpConnectionError({
832
1099
  transport: "auto",
833
1100
  message: `No pending connector for key: ${key}`
@@ -836,7 +1103,7 @@ var makeRuntime = () => Effect7.gen(function* () {
836
1103
  }
837
1104
  return connector;
838
1105
  }),
839
- (connection) => Effect7.promise(() => connection.close().catch(() => {
1106
+ (connection) => Effect8.promise(() => connection.close().catch(() => {
840
1107
  }))
841
1108
  ),
842
1109
  capacity: 64,
@@ -891,29 +1158,30 @@ var toMcpConfigEntry = (namespace, sourceName, config) => {
891
1158
  var mcpPlugin = definePlugin((options) => {
892
1159
  const allowStdio = options?.dangerouslyAllowStdioMCP ?? false;
893
1160
  const runtimeRef = { current: null };
894
- const ensureRuntime = () => runtimeRef.current ? Effect7.succeed(runtimeRef.current) : makeRuntime().pipe(
895
- Effect7.tap(
896
- (rt) => Effect7.sync(() => {
1161
+ const ensureRuntime = () => runtimeRef.current ? Effect8.succeed(runtimeRef.current) : makeRuntime().pipe(
1162
+ Effect8.tap(
1163
+ (rt) => Effect8.sync(() => {
897
1164
  runtimeRef.current = rt;
898
1165
  })
899
1166
  )
900
1167
  );
901
1168
  return {
902
1169
  id: "mcp",
1170
+ packageName: "@executor-js/plugin-mcp",
903
1171
  schema: mcpSchema,
904
1172
  storage: (deps) => makeMcpStore(deps),
905
1173
  extension: (ctx) => {
906
- const probeEndpoint = (input) => Effect7.gen(function* () {
1174
+ const probeEndpoint = (input) => Effect8.gen(function* () {
907
1175
  const endpoint = typeof input === "string" ? input : input.endpoint;
908
1176
  const trimmed = endpoint.trim();
909
1177
  if (!trimmed) {
910
- return yield* Effect7.fail(remoteConnectionError("Endpoint URL is required"));
1178
+ return yield* Effect8.fail(remoteConnectionError("Endpoint URL is required"));
911
1179
  }
912
- const name = yield* Effect7.try({
1180
+ const name = yield* Effect8.try({
913
1181
  try: () => new URL(trimmed).hostname,
914
1182
  catch: () => "mcp"
915
1183
  }).pipe(
916
- Effect7.orElseSucceed(() => "mcp")
1184
+ Effect8.orElseSucceed(() => "mcp")
917
1185
  );
918
1186
  const namespace = deriveMcpNamespace({ endpoint: trimmed });
919
1187
  const probeHeaders = typeof input === "string" ? void 0 : yield* resolveSecretBackedMap(input.headers, ctx);
@@ -925,9 +1193,9 @@ var mcpPlugin = definePlugin((options) => {
925
1193
  queryParams: probeQueryParams
926
1194
  });
927
1195
  const result = yield* discoverTools(connector).pipe(
928
- Effect7.map((m) => ({ ok: true, manifest: m })),
929
- Effect7.catch(() => Effect7.succeed({ ok: false, manifest: null })),
930
- Effect7.withSpan("mcp.plugin.discover_tools")
1196
+ Effect8.map((m) => ({ ok: true, manifest: m })),
1197
+ Effect8.catch(() => Effect8.succeed({ ok: false, manifest: null })),
1198
+ Effect8.withSpan("mcp.plugin.discover_tools")
931
1199
  );
932
1200
  if (result.ok && result.manifest) {
933
1201
  return {
@@ -944,7 +1212,7 @@ var mcpPlugin = definePlugin((options) => {
944
1212
  queryParams: probeQueryParams
945
1213
  });
946
1214
  if (shape.kind !== "mcp") {
947
- return yield* Effect7.fail(
1215
+ return yield* Effect8.fail(
948
1216
  remoteConnectionError(
949
1217
  shape.kind === "not-mcp" ? `Endpoint does not look like an MCP server: ${shape.reason}` : `Could not reach endpoint: ${shape.reason}`
950
1218
  )
@@ -955,9 +1223,9 @@ var mcpPlugin = definePlugin((options) => {
955
1223
  headers: probeHeaders,
956
1224
  queryParams: probeQueryParams
957
1225
  }).pipe(
958
- Effect7.map(() => true),
959
- Effect7.catch(() => Effect7.succeed(false)),
960
- Effect7.withSpan("mcp.plugin.probe_oauth")
1226
+ Effect8.map(() => true),
1227
+ Effect8.catch(() => Effect8.succeed(false)),
1228
+ Effect8.withSpan("mcp.plugin.probe_oauth")
961
1229
  );
962
1230
  if (probeResult) {
963
1231
  return {
@@ -969,21 +1237,21 @@ var mcpPlugin = definePlugin((options) => {
969
1237
  serverName: null
970
1238
  };
971
1239
  }
972
- return yield* Effect7.fail(
1240
+ return yield* Effect8.fail(
973
1241
  remoteConnectionError("MCP server requires authentication but OAuth discovery failed")
974
1242
  );
975
1243
  }).pipe(
976
- Effect7.withSpan("mcp.plugin.probe_endpoint", {
1244
+ Effect8.withSpan("mcp.plugin.probe_endpoint", {
977
1245
  attributes: { "mcp.endpoint": typeof input === "string" ? input : input.endpoint }
978
1246
  })
979
1247
  );
980
1248
  const configFile = options?.configFile;
981
- const addSource = (config) => Effect7.gen(function* () {
1249
+ const addSource = (config) => Effect8.gen(function* () {
982
1250
  const namespace = normalizeNamespace(config);
983
1251
  const sd = toStoredSourceData(config);
984
1252
  const resolved = yield* resolveConnectorInput(sd, ctx, allowStdio).pipe(
985
- Effect7.result,
986
- Effect7.withSpan("mcp.plugin.resolve_connector", {
1253
+ Effect8.result,
1254
+ Effect8.withSpan("mcp.plugin.resolve_connector", {
987
1255
  attributes: {
988
1256
  "mcp.source.namespace": namespace,
989
1257
  "mcp.source.transport": sd.transport
@@ -991,21 +1259,21 @@ var mcpPlugin = definePlugin((options) => {
991
1259
  })
992
1260
  );
993
1261
  if (Result.isFailure(resolved) && sd.transport === "stdio") {
994
- return yield* Effect7.fail(resolved.failure);
1262
+ return yield* Effect8.fail(resolved.failure);
995
1263
  }
996
1264
  const discovery = Result.isSuccess(resolved) ? yield* discoverTools(createMcpConnector(resolved.success)).pipe(
997
- Effect7.mapError(
1265
+ Effect8.mapError(
998
1266
  (err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`)
999
1267
  ),
1000
- Effect7.result,
1001
- Effect7.withSpan("mcp.plugin.discover_tools", {
1268
+ Effect8.result,
1269
+ Effect8.withSpan("mcp.plugin.discover_tools", {
1002
1270
  attributes: { "mcp.source.namespace": namespace }
1003
1271
  })
1004
1272
  ) : Result.fail(resolved.failure);
1005
1273
  const manifest = Result.isSuccess(discovery) ? discovery.success : { server: void 0, tools: [] };
1006
1274
  const sourceName = config.name ?? manifest.server?.name ?? namespace;
1007
1275
  yield* ctx.transaction(
1008
- Effect7.gen(function* () {
1276
+ Effect8.gen(function* () {
1009
1277
  yield* ctx.storage.removeBindingsByNamespace(namespace, config.scope);
1010
1278
  yield* ctx.storage.removeSource(namespace, config.scope);
1011
1279
  yield* ctx.storage.putSource({
@@ -1040,7 +1308,7 @@ var mcpPlugin = definePlugin((options) => {
1040
1308
  });
1041
1309
  })
1042
1310
  ).pipe(
1043
- Effect7.withSpan("mcp.plugin.persist_source", {
1311
+ Effect8.withSpan("mcp.plugin.persist_source", {
1044
1312
  attributes: {
1045
1313
  "mcp.source.namespace": namespace,
1046
1314
  "mcp.source.tool_count": manifest.tools.length
@@ -1048,49 +1316,49 @@ var mcpPlugin = definePlugin((options) => {
1048
1316
  })
1049
1317
  );
1050
1318
  if (configFile) {
1051
- yield* configFile.upsertSource(toMcpConfigEntry(namespace, sourceName, config)).pipe(Effect7.withSpan("mcp.plugin.config_file.upsert"));
1319
+ yield* configFile.upsertSource(toMcpConfigEntry(namespace, sourceName, config)).pipe(Effect8.withSpan("mcp.plugin.config_file.upsert"));
1052
1320
  }
1053
1321
  if (Result.isFailure(discovery)) {
1054
- return yield* Effect7.fail(discovery.failure);
1322
+ return yield* Effect8.fail(discovery.failure);
1055
1323
  }
1056
1324
  return { toolCount: manifest.tools.length, namespace };
1057
1325
  }).pipe(
1058
- Effect7.withSpan("mcp.plugin.add_source", {
1326
+ Effect8.withSpan("mcp.plugin.add_source", {
1059
1327
  attributes: {
1060
1328
  "mcp.source.transport": config.transport,
1061
1329
  "mcp.source.name": config.name
1062
1330
  }
1063
1331
  })
1064
1332
  );
1065
- const removeSource = (namespace, scope) => Effect7.gen(function* () {
1333
+ const removeSource = (namespace, scope) => Effect8.gen(function* () {
1066
1334
  yield* ctx.transaction(
1067
- Effect7.gen(function* () {
1335
+ Effect8.gen(function* () {
1068
1336
  yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1069
1337
  yield* ctx.storage.removeSource(namespace, scope);
1070
1338
  yield* ctx.core.sources.unregister(namespace);
1071
1339
  })
1072
- ).pipe(Effect7.withSpan("mcp.plugin.persist_remove"));
1340
+ ).pipe(Effect8.withSpan("mcp.plugin.persist_remove"));
1073
1341
  if (configFile) {
1074
- yield* configFile.removeSource(namespace).pipe(Effect7.withSpan("mcp.plugin.config_file.remove"));
1342
+ yield* configFile.removeSource(namespace).pipe(Effect8.withSpan("mcp.plugin.config_file.remove"));
1075
1343
  }
1076
1344
  }).pipe(
1077
- Effect7.withSpan("mcp.plugin.remove_source", {
1345
+ Effect8.withSpan("mcp.plugin.remove_source", {
1078
1346
  attributes: { "mcp.source.namespace": namespace }
1079
1347
  })
1080
1348
  );
1081
- const refreshSource = (namespace, scope) => Effect7.gen(function* () {
1349
+ const refreshSource = (namespace, scope) => Effect8.gen(function* () {
1082
1350
  const sd = yield* ctx.storage.getSourceConfig(namespace, scope).pipe(
1083
- Effect7.withSpan("mcp.plugin.load_source_config", {
1351
+ Effect8.withSpan("mcp.plugin.load_source_config", {
1084
1352
  attributes: { "mcp.source.namespace": namespace }
1085
1353
  })
1086
1354
  );
1087
1355
  if (!sd) {
1088
- return yield* Effect7.fail(
1356
+ return yield* Effect8.fail(
1089
1357
  remoteConnectionError(`No stored config for MCP source "${namespace}"`)
1090
1358
  );
1091
1359
  }
1092
1360
  const ci = yield* resolveConnectorInput(sd, ctx, allowStdio).pipe(
1093
- Effect7.withSpan("mcp.plugin.resolve_connector", {
1361
+ Effect8.withSpan("mcp.plugin.resolve_connector", {
1094
1362
  attributes: {
1095
1363
  "mcp.source.namespace": namespace,
1096
1364
  "mcp.source.transport": sd.transport
@@ -1098,15 +1366,15 @@ var mcpPlugin = definePlugin((options) => {
1098
1366
  })
1099
1367
  );
1100
1368
  const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
1101
- Effect7.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`)),
1102
- Effect7.withSpan("mcp.plugin.discover_tools", {
1369
+ Effect8.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`)),
1370
+ Effect8.withSpan("mcp.plugin.discover_tools", {
1103
1371
  attributes: { "mcp.source.namespace": namespace }
1104
1372
  })
1105
1373
  );
1106
1374
  const existing = yield* ctx.storage.getSource(namespace, scope);
1107
1375
  const sourceName = manifest.server?.name ?? existing?.name ?? namespace;
1108
1376
  yield* ctx.transaction(
1109
- Effect7.gen(function* () {
1377
+ Effect8.gen(function* () {
1110
1378
  yield* ctx.storage.removeBindingsByNamespace(namespace, scope);
1111
1379
  yield* ctx.core.sources.unregister(namespace);
1112
1380
  yield* ctx.storage.putBindings(
@@ -1135,7 +1403,7 @@ var mcpPlugin = definePlugin((options) => {
1135
1403
  });
1136
1404
  })
1137
1405
  ).pipe(
1138
- Effect7.withSpan("mcp.plugin.persist_source", {
1406
+ Effect8.withSpan("mcp.plugin.persist_source", {
1139
1407
  attributes: {
1140
1408
  "mcp.source.namespace": namespace,
1141
1409
  "mcp.source.tool_count": manifest.tools.length
@@ -1144,11 +1412,11 @@ var mcpPlugin = definePlugin((options) => {
1144
1412
  );
1145
1413
  return { toolCount: manifest.tools.length };
1146
1414
  }).pipe(
1147
- Effect7.withSpan("mcp.plugin.refresh_source", {
1415
+ Effect8.withSpan("mcp.plugin.refresh_source", {
1148
1416
  attributes: { "mcp.source.namespace": namespace }
1149
1417
  })
1150
1418
  );
1151
- const updateSource = (namespace, scope, input) => Effect7.gen(function* () {
1419
+ const updateSource = (namespace, scope, input) => Effect8.gen(function* () {
1152
1420
  const existing = yield* ctx.storage.getSource(namespace, scope);
1153
1421
  if (!existing || existing.config.transport !== "remote") return;
1154
1422
  const remote = existing.config;
@@ -1166,12 +1434,12 @@ var mcpPlugin = definePlugin((options) => {
1166
1434
  config: updatedConfig
1167
1435
  });
1168
1436
  }).pipe(
1169
- Effect7.withSpan("mcp.plugin.update_source", {
1437
+ Effect8.withSpan("mcp.plugin.update_source", {
1170
1438
  attributes: { "mcp.source.namespace": namespace }
1171
1439
  })
1172
1440
  );
1173
1441
  const getSource = (namespace, scope) => ctx.storage.getSource(namespace, scope).pipe(
1174
- Effect7.withSpan("mcp.plugin.get_source", {
1442
+ Effect8.withSpan("mcp.plugin.get_source", {
1175
1443
  attributes: { "mcp.source.namespace": namespace }
1176
1444
  })
1177
1445
  );
@@ -1184,24 +1452,24 @@ var mcpPlugin = definePlugin((options) => {
1184
1452
  updateSource
1185
1453
  };
1186
1454
  },
1187
- invokeTool: ({ ctx, toolRow, args, elicit }) => Effect7.gen(function* () {
1455
+ invokeTool: ({ ctx, toolRow, args, elicit }) => Effect8.gen(function* () {
1188
1456
  const runtime = yield* ensureRuntime();
1189
1457
  const toolScope = toolRow.scope_id;
1190
1458
  const entry = yield* ctx.storage.getBinding(toolRow.id, toolScope).pipe(
1191
- Effect7.withSpan("mcp.plugin.load_binding", {
1459
+ Effect8.withSpan("mcp.plugin.load_binding", {
1192
1460
  attributes: { "mcp.tool.name": toolRow.id }
1193
1461
  })
1194
1462
  );
1195
1463
  if (!entry) {
1196
- return yield* Effect7.fail(new Error(`No MCP binding found for tool "${toolRow.id}"`));
1464
+ return yield* Effect8.fail(new Error(`No MCP binding found for tool "${toolRow.id}"`));
1197
1465
  }
1198
1466
  const sd = yield* ctx.storage.getSourceConfig(entry.namespace, toolScope).pipe(
1199
- Effect7.withSpan("mcp.plugin.load_source_config", {
1467
+ Effect8.withSpan("mcp.plugin.load_source_config", {
1200
1468
  attributes: { "mcp.source.namespace": entry.namespace }
1201
1469
  })
1202
1470
  );
1203
1471
  if (!sd) {
1204
- return yield* Effect7.fail(
1472
+ return yield* Effect8.fail(
1205
1473
  new Error(`No MCP source config for namespace "${entry.namespace}"`)
1206
1474
  );
1207
1475
  }
@@ -1212,14 +1480,14 @@ var mcpPlugin = definePlugin((options) => {
1212
1480
  sourceData: sd,
1213
1481
  invokerScope: ctx.scopes[0].id,
1214
1482
  resolveConnector: () => resolveConnectorInput(sd, ctx, allowStdio).pipe(
1215
- Effect7.flatMap((ci) => createMcpConnector(ci)),
1216
- Effect7.mapError(
1483
+ Effect8.flatMap((ci) => createMcpConnector(ci)),
1484
+ Effect8.mapError(
1217
1485
  (err) => err instanceof McpConnectionError ? err : new McpConnectionError({
1218
1486
  transport: "auto",
1219
1487
  message: err instanceof Error ? err.message : String(err)
1220
1488
  })
1221
1489
  ),
1222
- Effect7.withSpan("mcp.plugin.resolve_connector", {
1490
+ Effect8.withSpan("mcp.plugin.resolve_connector", {
1223
1491
  attributes: {
1224
1492
  "mcp.source.namespace": entry.namespace,
1225
1493
  "mcp.source.transport": sd.transport
@@ -1231,20 +1499,20 @@ var mcpPlugin = definePlugin((options) => {
1231
1499
  elicit
1232
1500
  });
1233
1501
  }).pipe(
1234
- Effect7.withSpan("mcp.plugin.invoke_tool", {
1502
+ Effect8.withSpan("mcp.plugin.invoke_tool", {
1235
1503
  attributes: {
1236
1504
  "mcp.tool.name": toolRow.id,
1237
1505
  "mcp.tool.source_id": toolRow.source_id
1238
1506
  }
1239
1507
  })
1240
1508
  ),
1241
- detect: ({ ctx, url }) => Effect7.gen(function* () {
1509
+ detect: ({ ctx, url }) => Effect8.gen(function* () {
1242
1510
  const trimmed = url.trim();
1243
1511
  if (!trimmed) return null;
1244
- const parsed = yield* Effect7.try({
1512
+ const parsed = yield* Effect8.try({
1245
1513
  try: () => new URL(trimmed),
1246
1514
  catch: (cause) => cause
1247
- }).pipe(Effect7.option);
1515
+ }).pipe(Effect8.option);
1248
1516
  if (parsed._tag === "None") return null;
1249
1517
  const name = parsed.value.hostname || "mcp";
1250
1518
  const namespace = deriveMcpNamespace({ endpoint: trimmed });
@@ -1253,9 +1521,9 @@ var mcpPlugin = definePlugin((options) => {
1253
1521
  endpoint: trimmed
1254
1522
  });
1255
1523
  const connected = yield* discoverTools(connector).pipe(
1256
- Effect7.map(() => true),
1257
- Effect7.catch(() => Effect7.succeed(false)),
1258
- Effect7.withSpan("mcp.plugin.discover_tools")
1524
+ Effect8.map(() => true),
1525
+ Effect8.catch(() => Effect8.succeed(false)),
1526
+ Effect8.withSpan("mcp.plugin.discover_tools")
1259
1527
  );
1260
1528
  if (connected) {
1261
1529
  return new SourceDetectionResult({
@@ -1269,9 +1537,9 @@ var mcpPlugin = definePlugin((options) => {
1269
1537
  const shape = yield* probeMcpEndpointShape(trimmed);
1270
1538
  if (shape.kind !== "mcp") return null;
1271
1539
  const probeOk = yield* ctx.oauth.probe({ endpoint: trimmed }).pipe(
1272
- Effect7.map(() => true),
1273
- Effect7.catch(() => Effect7.succeed(false)),
1274
- Effect7.withSpan("mcp.plugin.probe_oauth")
1540
+ Effect8.map(() => true),
1541
+ Effect8.catch(() => Effect8.succeed(false)),
1542
+ Effect8.withSpan("mcp.plugin.probe_oauth")
1275
1543
  );
1276
1544
  if (!probeOk) return null;
1277
1545
  return new SourceDetectionResult({
@@ -1282,29 +1550,52 @@ var mcpPlugin = definePlugin((options) => {
1282
1550
  namespace
1283
1551
  });
1284
1552
  }).pipe(
1285
- Effect7.catch(() => Effect7.succeed(null)),
1286
- Effect7.withSpan("mcp.plugin.detect", {
1553
+ Effect8.catch(() => Effect8.succeed(null)),
1554
+ Effect8.withSpan("mcp.plugin.detect", {
1287
1555
  attributes: { "mcp.endpoint": url }
1288
1556
  })
1289
1557
  ),
1290
- // MCP tools never require approval at the tool level — elicitation is
1291
- // handled mid-invocation by the server via the elicit capability.
1292
- resolveAnnotations: ({ toolRows }) => Effect7.sync(() => {
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);
1293
1575
  const out = {};
1294
1576
  for (const row of toolRows) {
1295
- out[row.id] = { requiresApproval: false };
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
+ }
1296
1587
  }
1297
1588
  return out;
1298
1589
  }),
1299
- removeSource: ({ ctx, sourceId, scope }) => Effect7.gen(function* () {
1590
+ removeSource: ({ ctx, sourceId, scope }) => Effect8.gen(function* () {
1300
1591
  yield* ctx.storage.removeBindingsByNamespace(sourceId, scope);
1301
1592
  yield* ctx.storage.removeSource(sourceId, scope);
1302
1593
  }),
1303
- refreshSource: () => Effect7.void,
1594
+ refreshSource: () => Effect8.void,
1304
1595
  // Connection refresh for oauth2-minted sources is owned by the
1305
1596
  // canonical `"oauth2"` ConnectionProvider that core registers via
1306
1597
  // `makeOAuth2Service`. No MCP-specific provider needed.
1307
- close: () => Effect7.gen(function* () {
1598
+ close: () => Effect8.gen(function* () {
1308
1599
  const runtime = runtimeRef.current;
1309
1600
  if (runtime) {
1310
1601
  runtime.pendingConnectors.clear();
@@ -1312,7 +1603,13 @@ var mcpPlugin = definePlugin((options) => {
1312
1603
  yield* Scope.close(runtime.cacheScope, Exit2.void);
1313
1604
  runtimeRef.current = null;
1314
1605
  }
1315
- }).pipe(Effect7.withSpan("mcp.plugin.close"))
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
1316
1613
  };
1317
1614
  });
1318
1615
 
@@ -1322,4 +1619,4 @@ export {
1322
1619
  makeMcpStore,
1323
1620
  mcpPlugin
1324
1621
  };
1325
- //# sourceMappingURL=chunk-DJANY5EU.js.map
1622
+ //# sourceMappingURL=chunk-C2GNZGFJ.js.map