@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.
- package/dist/api/group.d.ts +6 -6
- package/dist/{chunk-DJANY5EU.js → chunk-C2GNZGFJ.js} +456 -159
- package/dist/chunk-C2GNZGFJ.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/dist/react/atoms.d.ts +7 -7
- package/dist/react/client.d.ts +7 -340
- package/dist/react/plugin-client.d.ts +2 -0
- package/dist/react/source-plugin.d.ts +1 -1
- package/dist/sdk/binding-store.d.ts +4 -0
- package/dist/sdk/manifest.d.ts +2 -0
- package/dist/sdk/plugin.d.ts +132 -2
- package/dist/sdk/test-utils.d.ts +16 -0
- package/dist/sdk/types.d.ts +15 -0
- package/package.json +4 -4
- package/dist/chunk-DJANY5EU.js.map +0 -1
|
@@ -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
|
|
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/
|
|
211
|
-
import {
|
|
212
|
-
import {
|
|
213
|
-
import {
|
|
214
|
-
import {
|
|
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
|
-
{
|
|
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) =>
|
|
534
|
+
var connectClient = (input) => Effect4.gen(function* () {
|
|
271
535
|
const client = createClient();
|
|
272
536
|
const transportInstance = input.createTransport();
|
|
273
|
-
yield*
|
|
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
|
-
|
|
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
|
|
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
|
|
298
|
-
const { createStdioTransport } = yield*
|
|
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(
|
|
600
|
+
return connectStreamableHttp.pipe(Effect4.catch(() => connectSse));
|
|
337
601
|
};
|
|
338
602
|
|
|
339
603
|
// src/sdk/discover.ts
|
|
340
|
-
import { Effect as
|
|
604
|
+
import { Effect as Effect5 } from "effect";
|
|
341
605
|
|
|
342
606
|
// src/sdk/manifest.ts
|
|
343
|
-
import { Schema as
|
|
344
|
-
var ListedTool =
|
|
345
|
-
name:
|
|
346
|
-
description:
|
|
347
|
-
inputSchema:
|
|
348
|
-
parameters:
|
|
349
|
-
outputSchema:
|
|
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 =
|
|
352
|
-
tools:
|
|
616
|
+
var ListToolsResult = Schema6.Struct({
|
|
617
|
+
tools: Schema6.Array(ListedTool)
|
|
353
618
|
});
|
|
354
|
-
var ServerInfo =
|
|
355
|
-
name:
|
|
356
|
-
version:
|
|
619
|
+
var ServerInfo = Schema6.Struct({
|
|
620
|
+
name: Schema6.optional(Schema6.String),
|
|
621
|
+
version: Schema6.optional(Schema6.String)
|
|
357
622
|
});
|
|
358
|
-
var decodeListToolsResult =
|
|
359
|
-
var decodeServerInfo =
|
|
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) =>
|
|
678
|
+
var discoverTools = (connector) => Effect5.gen(function* () {
|
|
413
679
|
const connection = yield* connector.pipe(
|
|
414
|
-
|
|
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*
|
|
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*
|
|
695
|
+
yield* Effect5.promise(() => connection.close().catch(() => {
|
|
430
696
|
}));
|
|
431
|
-
return yield*
|
|
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*
|
|
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
|
|
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 =
|
|
464
|
-
|
|
465
|
-
mode:
|
|
466
|
-
message:
|
|
467
|
-
url:
|
|
468
|
-
elicitationId:
|
|
469
|
-
id:
|
|
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
|
-
|
|
472
|
-
mode:
|
|
473
|
-
message:
|
|
474
|
-
requestedSchema:
|
|
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 =
|
|
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
|
|
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) =>
|
|
777
|
+
var useConnection = (connection, toolName, args, elicit) => Effect6.gen(function* () {
|
|
512
778
|
installElicitationHandler(connection.client, elicit);
|
|
513
|
-
return yield*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
552
|
-
() =>
|
|
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
|
-
|
|
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
|
-
|
|
575
|
-
|
|
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
|
|
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 = {}) =>
|
|
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*
|
|
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
|
-
|
|
676
|
-
(cause) =>
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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*
|
|
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
|
-
|
|
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 = () =>
|
|
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) =>
|
|
827
|
-
|
|
1093
|
+
lookup: (key) => Effect8.acquireRelease(
|
|
1094
|
+
Effect8.suspend(() => {
|
|
828
1095
|
const connector = pendingConnectors.get(key);
|
|
829
1096
|
if (!connector) {
|
|
830
|
-
return
|
|
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) =>
|
|
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 ?
|
|
895
|
-
|
|
896
|
-
(rt) =>
|
|
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) =>
|
|
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*
|
|
1178
|
+
return yield* Effect8.fail(remoteConnectionError("Endpoint URL is required"));
|
|
911
1179
|
}
|
|
912
|
-
const name = yield*
|
|
1180
|
+
const name = yield* Effect8.try({
|
|
913
1181
|
try: () => new URL(trimmed).hostname,
|
|
914
1182
|
catch: () => "mcp"
|
|
915
1183
|
}).pipe(
|
|
916
|
-
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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*
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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*
|
|
1240
|
+
return yield* Effect8.fail(
|
|
973
1241
|
remoteConnectionError("MCP server requires authentication but OAuth discovery failed")
|
|
974
1242
|
);
|
|
975
1243
|
}).pipe(
|
|
976
|
-
|
|
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) =>
|
|
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
|
-
|
|
986
|
-
|
|
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*
|
|
1262
|
+
return yield* Effect8.fail(resolved.failure);
|
|
995
1263
|
}
|
|
996
1264
|
const discovery = Result.isSuccess(resolved) ? yield* discoverTools(createMcpConnector(resolved.success)).pipe(
|
|
997
|
-
|
|
1265
|
+
Effect8.mapError(
|
|
998
1266
|
(err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`)
|
|
999
1267
|
),
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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*
|
|
1322
|
+
return yield* Effect8.fail(discovery.failure);
|
|
1055
1323
|
}
|
|
1056
1324
|
return { toolCount: manifest.tools.length, namespace };
|
|
1057
1325
|
}).pipe(
|
|
1058
|
-
|
|
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) =>
|
|
1333
|
+
const removeSource = (namespace, scope) => Effect8.gen(function* () {
|
|
1066
1334
|
yield* ctx.transaction(
|
|
1067
|
-
|
|
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(
|
|
1340
|
+
).pipe(Effect8.withSpan("mcp.plugin.persist_remove"));
|
|
1073
1341
|
if (configFile) {
|
|
1074
|
-
yield* configFile.removeSource(namespace).pipe(
|
|
1342
|
+
yield* configFile.removeSource(namespace).pipe(Effect8.withSpan("mcp.plugin.config_file.remove"));
|
|
1075
1343
|
}
|
|
1076
1344
|
}).pipe(
|
|
1077
|
-
|
|
1345
|
+
Effect8.withSpan("mcp.plugin.remove_source", {
|
|
1078
1346
|
attributes: { "mcp.source.namespace": namespace }
|
|
1079
1347
|
})
|
|
1080
1348
|
);
|
|
1081
|
-
const refreshSource = (namespace, scope) =>
|
|
1349
|
+
const refreshSource = (namespace, scope) => Effect8.gen(function* () {
|
|
1082
1350
|
const sd = yield* ctx.storage.getSourceConfig(namespace, scope).pipe(
|
|
1083
|
-
|
|
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*
|
|
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
|
-
|
|
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
|
-
|
|
1102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1415
|
+
Effect8.withSpan("mcp.plugin.refresh_source", {
|
|
1148
1416
|
attributes: { "mcp.source.namespace": namespace }
|
|
1149
1417
|
})
|
|
1150
1418
|
);
|
|
1151
|
-
const updateSource = (namespace, scope, input) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 }) =>
|
|
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
|
-
|
|
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*
|
|
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
|
-
|
|
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*
|
|
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
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 }) =>
|
|
1509
|
+
detect: ({ ctx, url }) => Effect8.gen(function* () {
|
|
1242
1510
|
const trimmed = url.trim();
|
|
1243
1511
|
if (!trimmed) return null;
|
|
1244
|
-
const parsed = yield*
|
|
1512
|
+
const parsed = yield* Effect8.try({
|
|
1245
1513
|
try: () => new URL(trimmed),
|
|
1246
1514
|
catch: (cause) => cause
|
|
1247
|
-
}).pipe(
|
|
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
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1286
|
-
|
|
1553
|
+
Effect8.catch(() => Effect8.succeed(null)),
|
|
1554
|
+
Effect8.withSpan("mcp.plugin.detect", {
|
|
1287
1555
|
attributes: { "mcp.endpoint": url }
|
|
1288
1556
|
})
|
|
1289
1557
|
),
|
|
1290
|
-
//
|
|
1291
|
-
//
|
|
1292
|
-
|
|
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
|
-
|
|
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 }) =>
|
|
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: () =>
|
|
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: () =>
|
|
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(
|
|
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-
|
|
1622
|
+
//# sourceMappingURL=chunk-C2GNZGFJ.js.map
|