@executor-js/plugin-mcp 1.4.32 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AddMcpSource-4LLERUW5.js +602 -0
- package/dist/AddMcpSource-4LLERUW5.js.map +1 -0
- package/dist/EditMcpSource-GKJRP75X.js +313 -0
- package/dist/EditMcpSource-GKJRP75X.js.map +1 -0
- package/dist/McpAccountsPanel-UX7MHEIG.js +132 -0
- package/dist/McpAccountsPanel-UX7MHEIG.js.map +1 -0
- package/dist/api/group.d.ts +79 -143
- package/dist/api/index.d.ts +99 -155
- package/dist/chunk-2TXHTMKM.js +1298 -0
- package/dist/chunk-2TXHTMKM.js.map +1 -0
- package/dist/chunk-6OEQZ72N.js +124 -0
- package/dist/chunk-6OEQZ72N.js.map +1 -0
- package/dist/chunk-7FJ3PUUL.js +21 -0
- package/dist/chunk-7FJ3PUUL.js.map +1 -0
- package/dist/chunk-N4EAF5CA.js +146 -0
- package/dist/chunk-N4EAF5CA.js.map +1 -0
- package/dist/client.js +9 -9
- package/dist/client.js.map +1 -1
- package/dist/core.js +36 -26
- package/dist/index.js +2 -2
- package/dist/promise.d.ts +1 -1
- package/dist/react/AddMcpSource.d.ts +1 -1
- package/dist/react/McpAccountsPanel.d.ts +6 -0
- package/dist/react/McpRemoteSourceFields.d.ts +4 -2
- package/dist/react/McpSignInButton.d.ts +2 -0
- package/dist/react/atoms.d.ts +93 -313
- package/dist/react/auth-method-config.d.ts +8 -0
- package/dist/react/client.d.ts +78 -142
- package/dist/react/index.d.ts +3 -3
- package/dist/react/source-plugin.d.ts +5 -5
- package/dist/sdk/connection.d.ts +4 -4
- package/dist/sdk/errors.d.ts +0 -19
- package/dist/sdk/index.d.ts +4 -3
- package/dist/sdk/invoke.d.ts +9 -16
- package/dist/sdk/plugin.d.ts +101 -236
- package/dist/sdk/types.d.ts +25 -130
- package/package.json +5 -4
- package/dist/AddMcpSource-PADMBVX2.js +0 -688
- package/dist/AddMcpSource-PADMBVX2.js.map +0 -1
- package/dist/EditMcpSource-L5GC2B4J.js +0 -281
- package/dist/EditMcpSource-L5GC2B4J.js.map +0 -1
- package/dist/McpSourceSummary-LE3WXFUE.js +0 -170
- package/dist/McpSourceSummary-LE3WXFUE.js.map +0 -1
- package/dist/chunk-6OYEXHU3.js +0 -156
- package/dist/chunk-6OYEXHU3.js.map +0 -1
- package/dist/chunk-FMTVLO5L.js +0 -179
- package/dist/chunk-FMTVLO5L.js.map +0 -1
- package/dist/chunk-LEGVPKYH.js +0 -2391
- package/dist/chunk-LEGVPKYH.js.map +0 -1
- package/dist/chunk-ZIRGIRGP.js +0 -115
- package/dist/chunk-ZIRGIRGP.js.map +0 -1
- package/dist/react/McpSourceSummary.d.ts +0 -5
- package/dist/sdk/binding-store.d.ts +0 -31
- package/dist/sdk/stored-source.d.ts +0 -42
- /package/dist/{sdk/connection-pool.test.d.ts → react/auth-method-config.test.d.ts} +0 -0
- /package/dist/sdk/{cross-user-isolation.test.d.ts → describe-auth-methods.test.d.ts} +0 -0
- /package/dist/sdk/{per-user-auth-isolation.test.d.ts → owner-isolation.test.d.ts} +0 -0
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mcpPresets
|
|
3
|
+
} from "./chunk-TW44CBXJ.js";
|
|
4
|
+
import {
|
|
5
|
+
McpAuthTemplate,
|
|
6
|
+
McpConnectionError,
|
|
7
|
+
McpInvocationError,
|
|
8
|
+
McpRemoteTransport,
|
|
9
|
+
McpToolAnnotations,
|
|
10
|
+
McpToolDiscoveryError,
|
|
11
|
+
parseMcpIntegrationConfig
|
|
12
|
+
} from "./chunk-6OEQZ72N.js";
|
|
13
|
+
|
|
14
|
+
// src/sdk/manifest.ts
|
|
15
|
+
import { Option, Schema } from "effect";
|
|
16
|
+
var ListedTool = Schema.Struct({
|
|
17
|
+
name: Schema.String,
|
|
18
|
+
description: Schema.optional(Schema.NullOr(Schema.String)),
|
|
19
|
+
inputSchema: Schema.optional(Schema.Unknown),
|
|
20
|
+
parameters: Schema.optional(Schema.Unknown),
|
|
21
|
+
outputSchema: Schema.optional(Schema.Unknown),
|
|
22
|
+
annotations: Schema.optional(McpToolAnnotations)
|
|
23
|
+
});
|
|
24
|
+
var ListToolsResult = Schema.Struct({
|
|
25
|
+
tools: Schema.Array(ListedTool)
|
|
26
|
+
});
|
|
27
|
+
var ServerInfo = Schema.Struct({
|
|
28
|
+
name: Schema.optional(Schema.String),
|
|
29
|
+
version: Schema.optional(Schema.String)
|
|
30
|
+
});
|
|
31
|
+
var decodeListToolsResult = Schema.decodeUnknownOption(ListToolsResult);
|
|
32
|
+
var decodeServerInfo = Schema.decodeUnknownOption(ServerInfo);
|
|
33
|
+
var isListToolsResult = (value) => Option.isSome(decodeListToolsResult(value));
|
|
34
|
+
var sanitize = (value) => {
|
|
35
|
+
const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
36
|
+
return s || "tool";
|
|
37
|
+
};
|
|
38
|
+
var uniqueId = (value, seen) => {
|
|
39
|
+
const base = sanitize(value);
|
|
40
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
41
|
+
seen.set(base, n);
|
|
42
|
+
return n === 1 ? base : `${base}_${n}`;
|
|
43
|
+
};
|
|
44
|
+
var joinToolPath = (namespace, toolId) => namespace?.trim() ? `${namespace}.${toolId}` : toolId;
|
|
45
|
+
var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
|
|
46
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47
|
+
const listed = decodeListToolsResult(listToolsResult).pipe(
|
|
48
|
+
Option.map((result) => result.tools),
|
|
49
|
+
Option.getOrElse(() => [])
|
|
50
|
+
);
|
|
51
|
+
const server = decodeServerInfo(metadata?.serverInfo).pipe(
|
|
52
|
+
Option.map(
|
|
53
|
+
(info) => ({
|
|
54
|
+
name: info.name ?? null,
|
|
55
|
+
version: info.version ?? null
|
|
56
|
+
})
|
|
57
|
+
),
|
|
58
|
+
Option.getOrNull
|
|
59
|
+
);
|
|
60
|
+
const tools = listed.flatMap((tool2) => {
|
|
61
|
+
const toolName = tool2.name.trim();
|
|
62
|
+
if (!toolName) return [];
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
toolId: uniqueId(toolName, seen),
|
|
66
|
+
toolName,
|
|
67
|
+
description: tool2.description ?? null,
|
|
68
|
+
inputSchema: tool2.inputSchema ?? tool2.parameters,
|
|
69
|
+
outputSchema: tool2.outputSchema,
|
|
70
|
+
annotations: tool2.annotations
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
});
|
|
74
|
+
return { server, tools };
|
|
75
|
+
};
|
|
76
|
+
var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
77
|
+
var hostnameOf = (url) => {
|
|
78
|
+
if (!URL.canParse(url)) return null;
|
|
79
|
+
return new URL(url).hostname;
|
|
80
|
+
};
|
|
81
|
+
var basenameOf = (path) => path.trim().split(/[\\/]/).pop() ?? path.trim();
|
|
82
|
+
var deriveMcpNamespace = (input) => {
|
|
83
|
+
if (input.name?.trim()) return slugify(input.name) || "mcp";
|
|
84
|
+
const fromEndpoint = input.endpoint?.trim() ? hostnameOf(input.endpoint) : null;
|
|
85
|
+
if (fromEndpoint) return slugify(fromEndpoint) || "mcp";
|
|
86
|
+
if (input.command?.trim()) return slugify(basenameOf(input.command)) || "mcp";
|
|
87
|
+
return "mcp";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/sdk/plugin.ts
|
|
91
|
+
import { Effect as Effect5, Match, Option as Option4, Result, Schema as Schema4 } from "effect";
|
|
92
|
+
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
93
|
+
import * as z from "zod/v4";
|
|
94
|
+
import {
|
|
95
|
+
authToolFailure,
|
|
96
|
+
definePlugin,
|
|
97
|
+
IntegrationAlreadyExistsError,
|
|
98
|
+
IntegrationSlug,
|
|
99
|
+
tool,
|
|
100
|
+
ToolResult,
|
|
101
|
+
ToolName
|
|
102
|
+
} from "@executor-js/sdk";
|
|
103
|
+
|
|
104
|
+
// src/sdk/connection.ts
|
|
105
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
106
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
107
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
108
|
+
import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
|
|
109
|
+
import { Effect } from "effect";
|
|
110
|
+
var buildEndpointUrl = (endpoint, queryParams) => {
|
|
111
|
+
const url = new URL(endpoint);
|
|
112
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
113
|
+
url.searchParams.set(key, value);
|
|
114
|
+
}
|
|
115
|
+
return url;
|
|
116
|
+
};
|
|
117
|
+
var createClient = () => new Client(
|
|
118
|
+
{ name: "executor-mcp", version: "0.1.0" },
|
|
119
|
+
{
|
|
120
|
+
capabilities: { elicitation: { form: {}, url: {} } },
|
|
121
|
+
jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
var connectionFromClient = (client) => ({
|
|
125
|
+
client,
|
|
126
|
+
close: () => client.close()
|
|
127
|
+
});
|
|
128
|
+
var connectClient = (input) => Effect.gen(function* () {
|
|
129
|
+
const client = createClient();
|
|
130
|
+
const transportInstance = input.createTransport();
|
|
131
|
+
yield* Effect.tryPromise({
|
|
132
|
+
try: () => client.connect(transportInstance),
|
|
133
|
+
catch: () => new McpConnectionError({
|
|
134
|
+
transport: input.transport,
|
|
135
|
+
message: `Failed connecting via ${input.transport}`
|
|
136
|
+
})
|
|
137
|
+
}).pipe(
|
|
138
|
+
Effect.withSpan("plugin.mcp.connection.handshake", {
|
|
139
|
+
attributes: { "plugin.mcp.transport": input.transport }
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
return connectionFromClient(client);
|
|
143
|
+
});
|
|
144
|
+
var createMcpConnector = (input) => {
|
|
145
|
+
if (input.transport === "stdio") {
|
|
146
|
+
const command = input.command.trim();
|
|
147
|
+
if (!command) {
|
|
148
|
+
return Effect.fail(
|
|
149
|
+
new McpConnectionError({
|
|
150
|
+
transport: "stdio",
|
|
151
|
+
message: "MCP stdio transport requires a command"
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return Effect.gen(function* () {
|
|
156
|
+
const { createStdioTransport } = yield* Effect.tryPromise({
|
|
157
|
+
try: () => import("./stdio-connector-AA5S6UUJ.js"),
|
|
158
|
+
catch: () => new McpConnectionError({
|
|
159
|
+
transport: "stdio",
|
|
160
|
+
message: "Failed to load stdio transport module"
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
return yield* connectClient({
|
|
164
|
+
transport: "stdio",
|
|
165
|
+
createTransport: () => createStdioTransport({
|
|
166
|
+
command,
|
|
167
|
+
args: input.args,
|
|
168
|
+
env: input.env,
|
|
169
|
+
cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const headers = input.headers ?? {};
|
|
175
|
+
const remoteTransport = input.remoteTransport ?? "auto";
|
|
176
|
+
const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
|
|
177
|
+
const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
|
|
178
|
+
const connectStreamableHttp = connectClient({
|
|
179
|
+
transport: "streamable-http",
|
|
180
|
+
createTransport: () => new StreamableHTTPClientTransport(endpoint, {
|
|
181
|
+
requestInit,
|
|
182
|
+
authProvider: input.authProvider
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
const connectSse = connectClient({
|
|
186
|
+
transport: "sse",
|
|
187
|
+
createTransport: () => new SSEClientTransport(endpoint, {
|
|
188
|
+
requestInit,
|
|
189
|
+
authProvider: input.authProvider
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
if (remoteTransport === "streamable-http") return connectStreamableHttp;
|
|
193
|
+
if (remoteTransport === "sse") return connectSse;
|
|
194
|
+
return connectStreamableHttp.pipe(Effect.catch(() => connectSse));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// src/sdk/discover.ts
|
|
198
|
+
import { Effect as Effect2 } from "effect";
|
|
199
|
+
var discoverTools = (connector) => Effect2.gen(function* () {
|
|
200
|
+
const connection = yield* connector.pipe(
|
|
201
|
+
Effect2.mapError(
|
|
202
|
+
({ message }) => new McpToolDiscoveryError({
|
|
203
|
+
stage: "connect",
|
|
204
|
+
message: `Failed connecting to MCP server: ${message}`
|
|
205
|
+
})
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
const listResult = yield* Effect2.tryPromise({
|
|
209
|
+
try: () => connection.client.listTools(),
|
|
210
|
+
catch: () => new McpToolDiscoveryError({
|
|
211
|
+
stage: "list_tools",
|
|
212
|
+
message: "Failed listing MCP tools"
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
if (!isListToolsResult(listResult)) {
|
|
216
|
+
yield* closeConnection(connection);
|
|
217
|
+
return yield* new McpToolDiscoveryError({
|
|
218
|
+
stage: "list_tools",
|
|
219
|
+
message: "MCP listTools response did not match the expected schema"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const manifest = extractManifestFromListToolsResult(listResult, {
|
|
223
|
+
serverInfo: connection.client.getServerVersion?.()
|
|
224
|
+
});
|
|
225
|
+
yield* closeConnection(connection);
|
|
226
|
+
return manifest;
|
|
227
|
+
});
|
|
228
|
+
var closeConnection = (connection) => Effect2.ignore(
|
|
229
|
+
Effect2.tryPromise({
|
|
230
|
+
try: () => connection.close(),
|
|
231
|
+
catch: () => new McpToolDiscoveryError({
|
|
232
|
+
stage: "list_tools",
|
|
233
|
+
message: "Failed closing MCP connection"
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// src/sdk/invoke.ts
|
|
239
|
+
import { Cause, Effect as Effect3, Exit, Option as Option2, Predicate, Schema as Schema2 } from "effect";
|
|
240
|
+
import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
241
|
+
import {
|
|
242
|
+
ElicitationId,
|
|
243
|
+
FormElicitation,
|
|
244
|
+
UrlElicitation
|
|
245
|
+
} from "@executor-js/sdk";
|
|
246
|
+
var ArgsRecord = Schema2.Record(Schema2.String, Schema2.Unknown);
|
|
247
|
+
var decodeArgsRecord = Schema2.decodeUnknownOption(ArgsRecord);
|
|
248
|
+
var argsRecord = (value) => Option2.getOrElse(decodeArgsRecord(value), () => ({}));
|
|
249
|
+
var McpElicitParams = Schema2.Union([
|
|
250
|
+
Schema2.Struct({
|
|
251
|
+
mode: Schema2.Literal("url"),
|
|
252
|
+
message: Schema2.String,
|
|
253
|
+
url: Schema2.String,
|
|
254
|
+
elicitationId: Schema2.optional(Schema2.String),
|
|
255
|
+
id: Schema2.optional(Schema2.String)
|
|
256
|
+
}),
|
|
257
|
+
Schema2.Struct({
|
|
258
|
+
mode: Schema2.optional(Schema2.Literal("form")),
|
|
259
|
+
message: Schema2.String,
|
|
260
|
+
requestedSchema: Schema2.Record(Schema2.String, Schema2.Unknown)
|
|
261
|
+
})
|
|
262
|
+
]);
|
|
263
|
+
var decodeElicitParams = Schema2.decodeUnknownSync(McpElicitParams);
|
|
264
|
+
var toElicitationRequest = (params) => params.mode === "url" ? UrlElicitation.make({
|
|
265
|
+
message: params.message,
|
|
266
|
+
url: params.url,
|
|
267
|
+
elicitationId: ElicitationId.make(params.elicitationId ?? params.id ?? "")
|
|
268
|
+
}) : FormElicitation.make({
|
|
269
|
+
message: params.message,
|
|
270
|
+
requestedSchema: params.requestedSchema
|
|
271
|
+
});
|
|
272
|
+
var installElicitationHandler = (client, elicit) => {
|
|
273
|
+
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
274
|
+
const params = decodeElicitParams(request.params);
|
|
275
|
+
const req = toElicitationRequest(params);
|
|
276
|
+
const exit = await Effect3.runPromiseExit(elicit(req));
|
|
277
|
+
if (Exit.isSuccess(exit)) {
|
|
278
|
+
const response = exit.value;
|
|
279
|
+
return {
|
|
280
|
+
action: response.action,
|
|
281
|
+
...response.action === "accept" && response.content ? { content: response.content } : {}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const failure = exit.cause.reasons.find(Cause.isFailReason);
|
|
285
|
+
if (failure) {
|
|
286
|
+
const err = failure.error;
|
|
287
|
+
if (Predicate.isTagged(err, "ElicitationDeclinedError")) {
|
|
288
|
+
const action = Predicate.hasProperty(err, "action") && err.action === "cancel" ? "cancel" : "decline";
|
|
289
|
+
return { action };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
throw Cause.squash(exit.cause);
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
var useConnection = (connection, toolName, args, elicit) => Effect3.gen(function* () {
|
|
296
|
+
installElicitationHandler(connection.client, elicit);
|
|
297
|
+
return yield* Effect3.tryPromise({
|
|
298
|
+
try: () => connection.client.callTool({ name: toolName, arguments: args }),
|
|
299
|
+
catch: () => new McpInvocationError({
|
|
300
|
+
toolName,
|
|
301
|
+
message: `MCP tool call failed for ${toolName}`
|
|
302
|
+
})
|
|
303
|
+
}).pipe(
|
|
304
|
+
Effect3.withSpan("plugin.mcp.client.call_tool", {
|
|
305
|
+
attributes: { "mcp.tool.name": toolName }
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
var invokeMcpTool = (input) => Effect3.gen(function* () {
|
|
310
|
+
const args = argsRecord(input.args);
|
|
311
|
+
const connection = yield* Effect3.acquireRelease(
|
|
312
|
+
input.connector.pipe(
|
|
313
|
+
Effect3.withSpan("plugin.mcp.connection.acquire", {
|
|
314
|
+
attributes: { "plugin.mcp.transport": input.transport }
|
|
315
|
+
})
|
|
316
|
+
),
|
|
317
|
+
(conn) => Effect3.ignore(
|
|
318
|
+
Effect3.tryPromise({
|
|
319
|
+
try: () => conn.close(),
|
|
320
|
+
catch: () => new McpConnectionError({
|
|
321
|
+
transport: input.transport,
|
|
322
|
+
message: "Failed to close MCP connection"
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
)
|
|
326
|
+
);
|
|
327
|
+
return yield* useConnection(connection, input.toolName, args, input.elicit);
|
|
328
|
+
}).pipe(
|
|
329
|
+
Effect3.scoped,
|
|
330
|
+
Effect3.withSpan("plugin.mcp.invoke", {
|
|
331
|
+
attributes: {
|
|
332
|
+
"mcp.tool.name": input.toolName,
|
|
333
|
+
"plugin.mcp.tool_id": input.toolId,
|
|
334
|
+
"plugin.mcp.transport": input.transport
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// src/sdk/probe-shape.ts
|
|
340
|
+
import { Data, Duration, Effect as Effect4, Option as Option3, Schema as Schema3 } from "effect";
|
|
341
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
342
|
+
var INITIALIZE_BODY = JSON.stringify({
|
|
343
|
+
jsonrpc: "2.0",
|
|
344
|
+
id: 1,
|
|
345
|
+
method: "initialize",
|
|
346
|
+
params: {
|
|
347
|
+
protocolVersion: "2025-06-18",
|
|
348
|
+
capabilities: {},
|
|
349
|
+
clientInfo: { name: "executor-probe", version: "0" }
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
var readHeader = (headers, name) => {
|
|
353
|
+
const direct = headers[name];
|
|
354
|
+
if (direct !== void 0) return direct;
|
|
355
|
+
const lower = name.toLowerCase();
|
|
356
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
357
|
+
if (k.toLowerCase() === lower) return v;
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
};
|
|
361
|
+
var ProbeTransportError = class extends Data.TaggedError("ProbeTransportError") {
|
|
362
|
+
};
|
|
363
|
+
var decodeJsonString = Schema3.decodeUnknownOption(Schema3.fromJsonString(Schema3.Unknown));
|
|
364
|
+
var asObject = (body) => {
|
|
365
|
+
if (!body) return null;
|
|
366
|
+
const parsed = decodeJsonString(body);
|
|
367
|
+
if (Option3.isNone(parsed)) return null;
|
|
368
|
+
const value = parsed.value;
|
|
369
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
370
|
+
return value;
|
|
371
|
+
};
|
|
372
|
+
var isJsonRpcEnvelope = (body) => {
|
|
373
|
+
const obj = asObject(body);
|
|
374
|
+
if (!obj) return false;
|
|
375
|
+
if (obj.jsonrpc !== "2.0") return false;
|
|
376
|
+
return "result" in obj || "error" in obj || "method" in obj;
|
|
377
|
+
};
|
|
378
|
+
var isOAuthErrorBody = (body) => {
|
|
379
|
+
const obj = asObject(body);
|
|
380
|
+
if (!obj) return false;
|
|
381
|
+
if (Array.isArray(obj.errors)) return false;
|
|
382
|
+
return typeof obj.error === "string";
|
|
383
|
+
};
|
|
384
|
+
var ProtectedResourceMetadata = Schema3.Struct({
|
|
385
|
+
resource: Schema3.String,
|
|
386
|
+
authorization_servers: Schema3.Array(Schema3.String)
|
|
387
|
+
});
|
|
388
|
+
var decodeProtectedResourceMetadata = Schema3.decodeUnknownOption(
|
|
389
|
+
Schema3.fromJsonString(ProtectedResourceMetadata)
|
|
390
|
+
);
|
|
391
|
+
var protectedResourceMetadataUrl = (endpoint) => {
|
|
392
|
+
const path = endpoint.pathname === "/" ? "" : endpoint.pathname;
|
|
393
|
+
return `${endpoint.origin}/.well-known/oauth-protected-resource${path}`;
|
|
394
|
+
};
|
|
395
|
+
var resourceMatchesEndpoint = (resource, endpoint) => {
|
|
396
|
+
if (!URL.canParse(resource)) return false;
|
|
397
|
+
const parsed = new URL(resource);
|
|
398
|
+
if (parsed.origin !== endpoint.origin) return false;
|
|
399
|
+
const resourcePath = parsed.pathname.replace(/\/+$/, "");
|
|
400
|
+
const endpointPath = endpoint.pathname.replace(/\/+$/, "");
|
|
401
|
+
return endpointPath === resourcePath || endpointPath.startsWith(`${resourcePath}/`);
|
|
402
|
+
};
|
|
403
|
+
var probeProtectedResourceMetadata = (client, endpoint, timeoutMs) => Effect4.gen(function* () {
|
|
404
|
+
const response = yield* client.execute(
|
|
405
|
+
HttpClientRequest.get(protectedResourceMetadataUrl(endpoint)).pipe(
|
|
406
|
+
HttpClientRequest.setHeader("accept", "application/json")
|
|
407
|
+
)
|
|
408
|
+
).pipe(Effect4.timeout(Duration.millis(timeoutMs)));
|
|
409
|
+
if (response.status < 200 || response.status >= 300) return false;
|
|
410
|
+
const body = yield* response.text.pipe(
|
|
411
|
+
Effect4.timeout(Duration.millis(timeoutMs)),
|
|
412
|
+
Effect4.catch(() => Effect4.succeed(""))
|
|
413
|
+
);
|
|
414
|
+
const metadata = decodeProtectedResourceMetadata(body);
|
|
415
|
+
if (Option3.isNone(metadata)) return false;
|
|
416
|
+
if (metadata.value.authorization_servers.length === 0) return false;
|
|
417
|
+
return resourceMatchesEndpoint(metadata.value.resource, endpoint);
|
|
418
|
+
}).pipe(Effect4.catch(() => Effect4.succeed(false)));
|
|
419
|
+
var ErrorMessageShape = Schema3.Struct({ message: Schema3.String });
|
|
420
|
+
var decodeErrorMessageShape = Schema3.decodeUnknownOption(ErrorMessageShape);
|
|
421
|
+
var reasonFromBoundaryCause = (cause) => {
|
|
422
|
+
const messageShape = decodeErrorMessageShape(cause);
|
|
423
|
+
if (Option3.isSome(messageShape)) return messageShape.value.message;
|
|
424
|
+
if (typeof cause === "string") return cause;
|
|
425
|
+
if (typeof cause === "number" || typeof cause === "boolean" || typeof cause === "bigint") {
|
|
426
|
+
return `${cause}`;
|
|
427
|
+
}
|
|
428
|
+
if (typeof cause === "symbol") return cause.description ?? "symbol";
|
|
429
|
+
if (cause === null) return "null";
|
|
430
|
+
if (typeof cause === "undefined") return "undefined";
|
|
431
|
+
return "fetch failed";
|
|
432
|
+
};
|
|
433
|
+
var probeMcpEndpointShape = (endpoint, options = {}) => Effect4.gen(function* () {
|
|
434
|
+
const timeoutMs = options.timeoutMs ?? 8e3;
|
|
435
|
+
const outcome = yield* Effect4.gen(function* () {
|
|
436
|
+
const client = yield* HttpClient.HttpClient;
|
|
437
|
+
const readBody = (response) => response.text.pipe(
|
|
438
|
+
Effect4.timeout(Duration.millis(timeoutMs)),
|
|
439
|
+
Effect4.catch(() => Effect4.succeed(""))
|
|
440
|
+
);
|
|
441
|
+
const classify = (response, method) => Effect4.gen(function* () {
|
|
442
|
+
const contentType = readHeader(response.headers, "content-type") ?? "";
|
|
443
|
+
const isSse = /^\s*text\/event-stream\b/i.test(contentType);
|
|
444
|
+
if (response.status === 401) {
|
|
445
|
+
const wwwAuth = readHeader(response.headers, "www-authenticate");
|
|
446
|
+
if (!wwwAuth || !/^\s*bearer\b/i.test(wwwAuth)) {
|
|
447
|
+
if (yield* probeProtectedResourceMetadata(client, url, timeoutMs)) {
|
|
448
|
+
return { kind: "mcp", requiresAuth: true };
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
kind: "not-mcp",
|
|
452
|
+
category: "auth-required",
|
|
453
|
+
reason: "401 without Bearer WWW-Authenticate \u2014 not an MCP auth challenge"
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (/(?:^|[\s,])resource_metadata\s*=/i.test(wwwAuth)) {
|
|
457
|
+
return { kind: "mcp", requiresAuth: true };
|
|
458
|
+
}
|
|
459
|
+
if (/(?:^|[\s,])error\s*=/i.test(wwwAuth)) {
|
|
460
|
+
return { kind: "mcp", requiresAuth: true };
|
|
461
|
+
}
|
|
462
|
+
if (isSse) return { kind: "mcp", requiresAuth: true };
|
|
463
|
+
const body = yield* readBody(response);
|
|
464
|
+
if (!isJsonRpcEnvelope(body) && !isOAuthErrorBody(body)) {
|
|
465
|
+
if (yield* probeProtectedResourceMetadata(client, url, timeoutMs)) {
|
|
466
|
+
return { kind: "mcp", requiresAuth: true };
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
kind: "not-mcp",
|
|
470
|
+
category: "auth-required",
|
|
471
|
+
reason: "401 + Bearer without resource_metadata, JSON-RPC body, or OAuth error body"
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return { kind: "mcp", requiresAuth: true };
|
|
475
|
+
}
|
|
476
|
+
if (response.status >= 200 && response.status < 300) {
|
|
477
|
+
if (method === "GET") {
|
|
478
|
+
if (!isSse) {
|
|
479
|
+
return {
|
|
480
|
+
kind: "not-mcp",
|
|
481
|
+
category: "wrong-shape",
|
|
482
|
+
reason: "GET response is not an SSE stream"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return { kind: "mcp", requiresAuth: false };
|
|
486
|
+
}
|
|
487
|
+
if (isSse) return { kind: "mcp", requiresAuth: false };
|
|
488
|
+
const body = yield* readBody(response);
|
|
489
|
+
if (!isJsonRpcEnvelope(body)) {
|
|
490
|
+
return {
|
|
491
|
+
kind: "not-mcp",
|
|
492
|
+
category: "wrong-shape",
|
|
493
|
+
reason: "2xx POST body is not a JSON-RPC envelope"
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
return { kind: "mcp", requiresAuth: false };
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
});
|
|
500
|
+
const url = new URL(endpoint);
|
|
501
|
+
for (const [key, value] of Object.entries(options.queryParams ?? {})) {
|
|
502
|
+
url.searchParams.set(key, value);
|
|
503
|
+
}
|
|
504
|
+
let postRequest = HttpClientRequest.post(url.toString()).pipe(
|
|
505
|
+
HttpClientRequest.setHeader("content-type", "application/json"),
|
|
506
|
+
HttpClientRequest.setHeader("accept", "application/json, text/event-stream"),
|
|
507
|
+
HttpClientRequest.bodyText(INITIALIZE_BODY, "application/json")
|
|
508
|
+
);
|
|
509
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
510
|
+
postRequest = HttpClientRequest.setHeader(postRequest, name, value);
|
|
511
|
+
}
|
|
512
|
+
const postResponse = yield* client.execute(postRequest).pipe(Effect4.timeout(Duration.millis(timeoutMs)));
|
|
513
|
+
const postResult = yield* classify(postResponse, "POST");
|
|
514
|
+
if (postResult) return postResult;
|
|
515
|
+
if ([404, 405, 406, 415].includes(postResponse.status)) {
|
|
516
|
+
let getRequest = HttpClientRequest.get(url.toString()).pipe(
|
|
517
|
+
HttpClientRequest.setHeader("accept", "text/event-stream")
|
|
518
|
+
);
|
|
519
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
520
|
+
getRequest = HttpClientRequest.setHeader(getRequest, name, value);
|
|
521
|
+
}
|
|
522
|
+
const getResponse = yield* client.execute(getRequest).pipe(Effect4.timeout(Duration.millis(timeoutMs)));
|
|
523
|
+
const getResult = yield* classify(getResponse, "GET");
|
|
524
|
+
if (getResult) return getResult;
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
kind: "not-mcp",
|
|
528
|
+
category: "wrong-shape",
|
|
529
|
+
reason: `unexpected status ${postResponse.status} for initialize`
|
|
530
|
+
};
|
|
531
|
+
}).pipe(
|
|
532
|
+
Effect4.provide(options.httpClientLayer ?? FetchHttpClient.layer),
|
|
533
|
+
Effect4.mapError(
|
|
534
|
+
(cause) => new ProbeTransportError({
|
|
535
|
+
reason: reasonFromBoundaryCause(cause),
|
|
536
|
+
cause
|
|
537
|
+
})
|
|
538
|
+
),
|
|
539
|
+
Effect4.catch(
|
|
540
|
+
(cause) => Effect4.succeed({
|
|
541
|
+
kind: "unreachable",
|
|
542
|
+
reason: cause.reason
|
|
543
|
+
})
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
return outcome;
|
|
547
|
+
}).pipe(Effect4.withSpan("mcp.plugin.probe_shape"));
|
|
548
|
+
|
|
549
|
+
// src/sdk/plugin.ts
|
|
550
|
+
var MCP_PLUGIN_ID = "mcp";
|
|
551
|
+
var legacyOAuthClientSlugCandidate = (value) => {
|
|
552
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
553
|
+
return slug.length > 0 ? slug : null;
|
|
554
|
+
};
|
|
555
|
+
var legacyOAuthClientSlugCandidates = (slug, integration) => {
|
|
556
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
557
|
+
const fromSlug = legacyOAuthClientSlugCandidate(slug);
|
|
558
|
+
if (fromSlug) candidates.add(fromSlug);
|
|
559
|
+
const fromDescription = integration == null ? null : legacyOAuthClientSlugCandidate(integration.description);
|
|
560
|
+
if (fromDescription) candidates.add(fromDescription);
|
|
561
|
+
return candidates;
|
|
562
|
+
};
|
|
563
|
+
var oauthClientKey = (owner, slug) => `${owner}:${String(slug)}`;
|
|
564
|
+
var legacyMcpClientMatches = (client, candidates, config) => {
|
|
565
|
+
if (!candidates.has(String(client.slug))) return false;
|
|
566
|
+
if (!config || config.transport !== "remote" || config.auth.kind !== "oauth2") return false;
|
|
567
|
+
return client.grant === "authorization_code" && (client.resource ?? null) === config.endpoint;
|
|
568
|
+
};
|
|
569
|
+
var McpStampSchema = Schema4.Struct({
|
|
570
|
+
toolName: Schema4.String,
|
|
571
|
+
upstream: Schema4.optional(
|
|
572
|
+
Schema4.Struct({
|
|
573
|
+
title: Schema4.optional(Schema4.String),
|
|
574
|
+
readOnlyHint: Schema4.optional(Schema4.Boolean),
|
|
575
|
+
destructiveHint: Schema4.optional(Schema4.Boolean),
|
|
576
|
+
idempotentHint: Schema4.optional(Schema4.Boolean),
|
|
577
|
+
openWorldHint: Schema4.optional(Schema4.Boolean)
|
|
578
|
+
})
|
|
579
|
+
)
|
|
580
|
+
});
|
|
581
|
+
var AnnotationsWithStamp = Schema4.Struct({ mcp: McpStampSchema });
|
|
582
|
+
var decodeStamp = Schema4.decodeUnknownOption(AnnotationsWithStamp);
|
|
583
|
+
var readStamp = (annotations) => Option4.match(decodeStamp(annotations), {
|
|
584
|
+
onNone: () => null,
|
|
585
|
+
onSome: (decoded) => decoded.mcp
|
|
586
|
+
});
|
|
587
|
+
var McpRemoteServerInputSchema = Schema4.Struct({
|
|
588
|
+
transport: Schema4.optional(Schema4.Literal("remote")),
|
|
589
|
+
name: Schema4.String,
|
|
590
|
+
endpoint: Schema4.String,
|
|
591
|
+
remoteTransport: Schema4.optional(McpRemoteTransport),
|
|
592
|
+
headers: Schema4.optional(Schema4.Record(Schema4.String, Schema4.String)),
|
|
593
|
+
queryParams: Schema4.optional(Schema4.Record(Schema4.String, Schema4.String)),
|
|
594
|
+
slug: Schema4.optional(Schema4.String),
|
|
595
|
+
/** How a connection's value is applied to requests. Defaults to none. */
|
|
596
|
+
auth: Schema4.optional(McpAuthTemplate)
|
|
597
|
+
});
|
|
598
|
+
var McpStdioServerInputSchema = Schema4.Struct({
|
|
599
|
+
transport: Schema4.Literal("stdio"),
|
|
600
|
+
name: Schema4.String,
|
|
601
|
+
command: Schema4.String,
|
|
602
|
+
args: Schema4.optional(Schema4.Array(Schema4.String)),
|
|
603
|
+
env: Schema4.optional(Schema4.Record(Schema4.String, Schema4.String)),
|
|
604
|
+
cwd: Schema4.optional(Schema4.String),
|
|
605
|
+
slug: Schema4.optional(Schema4.String)
|
|
606
|
+
});
|
|
607
|
+
var McpAddServerInputSchema = Schema4.Union([
|
|
608
|
+
McpRemoteServerInputSchema,
|
|
609
|
+
McpStdioServerInputSchema
|
|
610
|
+
]);
|
|
611
|
+
var McpAddServerOutputSchema = Schema4.Struct({
|
|
612
|
+
slug: Schema4.String
|
|
613
|
+
});
|
|
614
|
+
var McpProbeEndpointInputSchema = Schema4.Struct({
|
|
615
|
+
endpoint: Schema4.String,
|
|
616
|
+
headers: Schema4.optional(Schema4.Record(Schema4.String, Schema4.String)),
|
|
617
|
+
queryParams: Schema4.optional(Schema4.Record(Schema4.String, Schema4.String))
|
|
618
|
+
});
|
|
619
|
+
var McpProbeEndpointOutputSchema = Schema4.Struct({
|
|
620
|
+
connected: Schema4.Boolean,
|
|
621
|
+
requiresAuthentication: Schema4.Boolean,
|
|
622
|
+
requiresOAuth: Schema4.Boolean,
|
|
623
|
+
supportsDynamicRegistration: Schema4.Boolean,
|
|
624
|
+
name: Schema4.String,
|
|
625
|
+
slug: Schema4.String,
|
|
626
|
+
toolCount: Schema4.NullOr(Schema4.Number),
|
|
627
|
+
serverName: Schema4.NullOr(Schema4.String)
|
|
628
|
+
});
|
|
629
|
+
var McpGetServerInputSchema = Schema4.Struct({
|
|
630
|
+
slug: Schema4.String
|
|
631
|
+
});
|
|
632
|
+
var McpGetServerOutputSchema = Schema4.Struct({
|
|
633
|
+
integration: Schema4.NullOr(Schema4.Unknown)
|
|
634
|
+
});
|
|
635
|
+
var schemaToStaticToolSchema = (schema) => Schema4.toStandardSchemaV1(Schema4.toStandardJSONSchemaV1(schema));
|
|
636
|
+
var McpAddServerInputStandardSchema = schemaToStaticToolSchema(McpAddServerInputSchema);
|
|
637
|
+
var McpAddServerOutputStandardSchema = schemaToStaticToolSchema(McpAddServerOutputSchema);
|
|
638
|
+
var McpProbeEndpointInputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointInputSchema);
|
|
639
|
+
var McpProbeEndpointOutputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointOutputSchema);
|
|
640
|
+
var McpGetServerInputStandardSchema = schemaToStaticToolSchema(McpGetServerInputSchema);
|
|
641
|
+
var McpGetServerOutputStandardSchema = schemaToStaticToolSchema(McpGetServerOutputSchema);
|
|
642
|
+
var mcpToolFailure = (code, message, details) => ToolResult.fail({
|
|
643
|
+
code,
|
|
644
|
+
message,
|
|
645
|
+
...details === void 0 ? {} : { details }
|
|
646
|
+
});
|
|
647
|
+
var slugFrom = (slug) => IntegrationSlug.make(slug);
|
|
648
|
+
var normalizeSlug = (input) => input.slug ?? deriveMcpNamespace({
|
|
649
|
+
name: input.name,
|
|
650
|
+
endpoint: input.transport === "stdio" ? void 0 : input.endpoint,
|
|
651
|
+
command: input.transport === "stdio" ? input.command : void 0
|
|
652
|
+
});
|
|
653
|
+
var toIntegrationConfig = (input) => {
|
|
654
|
+
if (input.transport === "stdio") {
|
|
655
|
+
return {
|
|
656
|
+
transport: "stdio",
|
|
657
|
+
command: input.command,
|
|
658
|
+
args: input.args ? [...input.args] : void 0,
|
|
659
|
+
env: input.env,
|
|
660
|
+
cwd: input.cwd
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
transport: "remote",
|
|
665
|
+
endpoint: input.endpoint,
|
|
666
|
+
remoteTransport: input.remoteTransport ?? "auto",
|
|
667
|
+
queryParams: input.queryParams,
|
|
668
|
+
headers: input.headers,
|
|
669
|
+
auth: input.auth ?? { kind: "none" }
|
|
670
|
+
};
|
|
671
|
+
};
|
|
672
|
+
var McpCallToolResultJsonSchema = z.toJSONSchema(CallToolResultSchema);
|
|
673
|
+
var mcpCallToolResultOutputSchema = (structuredContentSchema) => {
|
|
674
|
+
const defaultStructuredContentSchema = McpCallToolResultJsonSchema.properties?.structuredContent ?? {};
|
|
675
|
+
return {
|
|
676
|
+
...McpCallToolResultJsonSchema,
|
|
677
|
+
properties: {
|
|
678
|
+
...McpCallToolResultJsonSchema.properties,
|
|
679
|
+
structuredContent: structuredContentSchema === void 0 ? defaultStructuredContentSchema : structuredContentSchema,
|
|
680
|
+
isError: { const: false }
|
|
681
|
+
},
|
|
682
|
+
required: structuredContentSchema === void 0 ? ["content"] : ["content", "structuredContent"]
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
var toToolDef = (entry) => {
|
|
686
|
+
const destructive = entry.annotations?.destructiveHint === true;
|
|
687
|
+
const stamp = {
|
|
688
|
+
toolName: entry.toolName,
|
|
689
|
+
...entry.annotations ? { upstream: entry.annotations } : {}
|
|
690
|
+
};
|
|
691
|
+
const annotations = {
|
|
692
|
+
requiresApproval: destructive,
|
|
693
|
+
...destructive ? { approvalDescription: entry.annotations?.title ?? entry.toolName } : {},
|
|
694
|
+
mcp: stamp
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
name: ToolName.make(entry.toolId),
|
|
698
|
+
description: entry.description ?? `MCP tool: ${entry.toolName}`,
|
|
699
|
+
inputSchema: entry.inputSchema,
|
|
700
|
+
outputSchema: mcpCallToolResultOutputSchema(entry.outputSchema),
|
|
701
|
+
annotations
|
|
702
|
+
};
|
|
703
|
+
};
|
|
704
|
+
var McpTextContent = Schema4.Struct({ type: Schema4.Literal("text"), text: Schema4.String });
|
|
705
|
+
var McpToolCallEnvelope = Schema4.Struct({
|
|
706
|
+
isError: Schema4.optional(Schema4.Boolean),
|
|
707
|
+
content: Schema4.optional(Schema4.Array(Schema4.Unknown))
|
|
708
|
+
});
|
|
709
|
+
var decodeMcpTextContent = Schema4.decodeUnknownOption(McpTextContent);
|
|
710
|
+
var decodeMcpToolCallEnvelope = Schema4.decodeUnknownOption(McpToolCallEnvelope);
|
|
711
|
+
var extractMcpErrorMessage = (content) => {
|
|
712
|
+
if (Array.isArray(content)) {
|
|
713
|
+
for (const item of content) {
|
|
714
|
+
const decoded = Option4.getOrUndefined(decodeMcpTextContent(item));
|
|
715
|
+
if (decoded !== void 0 && decoded.text.length > 0) return decoded.text;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return "MCP tool returned an error";
|
|
719
|
+
};
|
|
720
|
+
var urlMatchesToken = (url, token) => {
|
|
721
|
+
const re = new RegExp(`(?:^|[^a-z0-9])${token}(?:$|[^a-z0-9])`, "i");
|
|
722
|
+
return re.test(url.hostname) || re.test(url.pathname);
|
|
723
|
+
};
|
|
724
|
+
var userFacingProbeMessage = (shape) => {
|
|
725
|
+
if (shape.kind === "unreachable") {
|
|
726
|
+
return "Couldn't reach this URL. Check the address, your network, and that the server is running.";
|
|
727
|
+
}
|
|
728
|
+
return Match.value(shape.category).pipe(
|
|
729
|
+
Match.when(
|
|
730
|
+
"auth-required",
|
|
731
|
+
() => "This server requires authentication. Add credentials (Authorization header, query parameter, or API key) below and retry."
|
|
732
|
+
),
|
|
733
|
+
Match.when(
|
|
734
|
+
"wrong-shape",
|
|
735
|
+
() => "This URL doesn't appear to host an MCP server. Double-check the address, including the path."
|
|
736
|
+
),
|
|
737
|
+
Match.exhaustive
|
|
738
|
+
);
|
|
739
|
+
};
|
|
740
|
+
var makeOAuthProvider = (accessToken) => ({
|
|
741
|
+
get redirectUrl() {
|
|
742
|
+
return "http://localhost/oauth/callback";
|
|
743
|
+
},
|
|
744
|
+
get clientMetadata() {
|
|
745
|
+
return {
|
|
746
|
+
redirect_uris: ["http://localhost/oauth/callback"],
|
|
747
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
748
|
+
response_types: ["code"],
|
|
749
|
+
token_endpoint_auth_method: "none",
|
|
750
|
+
client_name: "Executor"
|
|
751
|
+
};
|
|
752
|
+
},
|
|
753
|
+
clientInformation: () => void 0,
|
|
754
|
+
saveClientInformation: () => void 0,
|
|
755
|
+
tokens: () => ({ access_token: accessToken, token_type: "Bearer" }),
|
|
756
|
+
saveTokens: () => void 0,
|
|
757
|
+
redirectToAuthorization: async () => {
|
|
758
|
+
throw new Error("MCP OAuth re-authorization required");
|
|
759
|
+
},
|
|
760
|
+
saveCodeVerifier: () => void 0,
|
|
761
|
+
codeVerifier: () => {
|
|
762
|
+
throw new Error("No active PKCE verifier");
|
|
763
|
+
},
|
|
764
|
+
saveDiscoveryState: () => void 0,
|
|
765
|
+
discoveryState: () => void 0
|
|
766
|
+
});
|
|
767
|
+
var buildConnectorInput = (config, value, allowStdio) => {
|
|
768
|
+
if (config.transport === "stdio") {
|
|
769
|
+
if (!allowStdio) {
|
|
770
|
+
return Effect5.fail(
|
|
771
|
+
new McpConnectionError({
|
|
772
|
+
transport: "stdio",
|
|
773
|
+
message: "MCP stdio transport is disabled. Enable it by passing `dangerouslyAllowStdioMCP: true` to mcpPlugin() \u2014 only safe for trusted local contexts."
|
|
774
|
+
})
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return Effect5.succeed({
|
|
778
|
+
transport: "stdio",
|
|
779
|
+
command: config.command,
|
|
780
|
+
args: config.args,
|
|
781
|
+
env: config.env,
|
|
782
|
+
cwd: config.cwd
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const headers = { ...config.headers ?? {} };
|
|
786
|
+
let authProvider;
|
|
787
|
+
const auth = config.auth;
|
|
788
|
+
if (auth.kind === "header" && value !== null) {
|
|
789
|
+
headers[auth.headerName] = auth.prefix ? `${auth.prefix}${value}` : value;
|
|
790
|
+
} else if (auth.kind === "oauth2" && value !== null) {
|
|
791
|
+
authProvider = makeOAuthProvider(value);
|
|
792
|
+
}
|
|
793
|
+
return Effect5.succeed({
|
|
794
|
+
transport: "remote",
|
|
795
|
+
endpoint: config.endpoint,
|
|
796
|
+
remoteTransport: config.remoteTransport ?? "auto",
|
|
797
|
+
queryParams: config.queryParams && Object.keys(config.queryParams).length > 0 ? config.queryParams : void 0,
|
|
798
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
799
|
+
authProvider
|
|
800
|
+
});
|
|
801
|
+
};
|
|
802
|
+
var describeMcpAuthMethods = (record) => {
|
|
803
|
+
const config = parseMcpIntegrationConfig(record.config);
|
|
804
|
+
if (!config || config.transport === "stdio") return [];
|
|
805
|
+
const auth = config.auth;
|
|
806
|
+
if (auth.kind === "none") {
|
|
807
|
+
return [
|
|
808
|
+
{
|
|
809
|
+
id: "none",
|
|
810
|
+
label: "No authentication",
|
|
811
|
+
kind: "none",
|
|
812
|
+
template: "none"
|
|
813
|
+
}
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
if (auth.kind === "header") {
|
|
817
|
+
return [
|
|
818
|
+
{
|
|
819
|
+
id: "header",
|
|
820
|
+
label: "API key (header)",
|
|
821
|
+
kind: "apikey",
|
|
822
|
+
template: "header",
|
|
823
|
+
placements: [{ carrier: "header", name: auth.headerName, prefix: auth.prefix ?? "" }]
|
|
824
|
+
}
|
|
825
|
+
];
|
|
826
|
+
}
|
|
827
|
+
if (auth.kind === "oauth2") {
|
|
828
|
+
return [
|
|
829
|
+
{
|
|
830
|
+
id: "oauth2",
|
|
831
|
+
label: "OAuth",
|
|
832
|
+
kind: "oauth",
|
|
833
|
+
template: "oauth2",
|
|
834
|
+
oauth: { discoveryUrl: config.endpoint, supportsDynamicRegistration: true }
|
|
835
|
+
}
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
return [];
|
|
839
|
+
};
|
|
840
|
+
var describeMcpIntegrationDisplay = (record) => {
|
|
841
|
+
const config = parseMcpIntegrationConfig(record.config);
|
|
842
|
+
if (!config || config.transport === "stdio") return {};
|
|
843
|
+
return { url: config.endpoint };
|
|
844
|
+
};
|
|
845
|
+
var mcpPlugin = definePlugin((options) => {
|
|
846
|
+
const allowStdio = options?.dangerouslyAllowStdioMCP ?? false;
|
|
847
|
+
const presetEntries = (allowStdio ? mcpPresets : mcpPresets.filter((preset) => !("transport" in preset && preset.transport === "stdio"))).map((preset) => ({
|
|
848
|
+
id: preset.id,
|
|
849
|
+
name: preset.name,
|
|
850
|
+
summary: preset.summary,
|
|
851
|
+
..."url" in preset && preset.url ? { url: preset.url } : {},
|
|
852
|
+
..."endpoint" in preset && preset.endpoint ? { endpoint: preset.endpoint } : {},
|
|
853
|
+
...preset.icon ? { icon: preset.icon } : {},
|
|
854
|
+
...preset.featured ? { featured: preset.featured } : {},
|
|
855
|
+
transport: "transport" in preset && preset.transport === "stdio" ? "stdio" : "remote",
|
|
856
|
+
..."command" in preset ? { command: preset.command } : {},
|
|
857
|
+
..."args" in preset && preset.args ? { args: [...preset.args] } : {},
|
|
858
|
+
..."env" in preset && preset.env ? { env: preset.env } : {}
|
|
859
|
+
}));
|
|
860
|
+
return {
|
|
861
|
+
id: MCP_PLUGIN_ID,
|
|
862
|
+
packageName: "@executor-js/plugin-mcp",
|
|
863
|
+
integrationPresets: presetEntries,
|
|
864
|
+
// Surfaced to the client bundle via the Vite plugin. The MCP `./client`
|
|
865
|
+
// factory reads `allowStdio` and gates the stdio tab + presets.
|
|
866
|
+
clientConfig: { allowStdio },
|
|
867
|
+
storage: () => ({}),
|
|
868
|
+
extension: (ctx) => {
|
|
869
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
870
|
+
const probeEndpoint = (input) => Effect5.gen(function* () {
|
|
871
|
+
const endpoint = typeof input === "string" ? input : input.endpoint;
|
|
872
|
+
const trimmed = endpoint.trim();
|
|
873
|
+
if (!trimmed) {
|
|
874
|
+
return yield* new McpConnectionError({
|
|
875
|
+
transport: "remote",
|
|
876
|
+
message: "Endpoint URL is required"
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
const name = yield* Effect5.try({
|
|
880
|
+
try: () => new URL(trimmed).hostname,
|
|
881
|
+
catch: () => "mcp"
|
|
882
|
+
}).pipe(Effect5.orElseSucceed(() => "mcp"));
|
|
883
|
+
const slug = deriveMcpNamespace({ endpoint: trimmed });
|
|
884
|
+
const probeHeaders = typeof input === "string" ? void 0 : input.headers;
|
|
885
|
+
const probeQueryParams = typeof input === "string" ? void 0 : input.queryParams;
|
|
886
|
+
const connector = createMcpConnector({
|
|
887
|
+
transport: "remote",
|
|
888
|
+
endpoint: trimmed,
|
|
889
|
+
headers: probeHeaders,
|
|
890
|
+
queryParams: probeQueryParams
|
|
891
|
+
});
|
|
892
|
+
const result = yield* discoverTools(connector).pipe(
|
|
893
|
+
Effect5.map((m) => ({ ok: true, manifest: m })),
|
|
894
|
+
Effect5.catch(() => Effect5.succeed({ ok: false, manifest: null })),
|
|
895
|
+
Effect5.withSpan("mcp.plugin.discover_tools")
|
|
896
|
+
);
|
|
897
|
+
if (result.ok && result.manifest) {
|
|
898
|
+
return {
|
|
899
|
+
connected: true,
|
|
900
|
+
requiresAuthentication: false,
|
|
901
|
+
requiresOAuth: false,
|
|
902
|
+
supportsDynamicRegistration: false,
|
|
903
|
+
name: result.manifest.server?.name ?? name,
|
|
904
|
+
slug,
|
|
905
|
+
toolCount: result.manifest.tools.length,
|
|
906
|
+
serverName: result.manifest.server?.name ?? null
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const shape = yield* probeMcpEndpointShape(trimmed, {
|
|
910
|
+
httpClientLayer,
|
|
911
|
+
headers: probeHeaders,
|
|
912
|
+
queryParams: probeQueryParams
|
|
913
|
+
});
|
|
914
|
+
if (shape.kind !== "mcp") {
|
|
915
|
+
return yield* new McpConnectionError({
|
|
916
|
+
transport: "remote",
|
|
917
|
+
message: userFacingProbeMessage(shape)
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
const probeResult = yield* ctx.oauth.probe({ url: trimmed }).pipe(
|
|
921
|
+
Effect5.map((oauth) => ({ ok: true, oauth })),
|
|
922
|
+
Effect5.catch(() => Effect5.succeed({ ok: false, oauth: null })),
|
|
923
|
+
Effect5.withSpan("mcp.plugin.probe_oauth")
|
|
924
|
+
);
|
|
925
|
+
if (probeResult.ok) {
|
|
926
|
+
return {
|
|
927
|
+
connected: false,
|
|
928
|
+
requiresAuthentication: true,
|
|
929
|
+
requiresOAuth: true,
|
|
930
|
+
supportsDynamicRegistration: probeResult.oauth.registrationEndpoint != null,
|
|
931
|
+
name,
|
|
932
|
+
slug,
|
|
933
|
+
toolCount: null,
|
|
934
|
+
serverName: null
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
if (shape.requiresAuth) {
|
|
938
|
+
return {
|
|
939
|
+
connected: false,
|
|
940
|
+
requiresAuthentication: true,
|
|
941
|
+
requiresOAuth: false,
|
|
942
|
+
supportsDynamicRegistration: false,
|
|
943
|
+
name,
|
|
944
|
+
slug,
|
|
945
|
+
toolCount: null,
|
|
946
|
+
serverName: null
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
return yield* new McpConnectionError({
|
|
950
|
+
transport: "remote",
|
|
951
|
+
message: "This endpoint looks like MCP, but Executor couldn't discover tools from it. Check the URL and try again."
|
|
952
|
+
});
|
|
953
|
+
}).pipe(
|
|
954
|
+
Effect5.withSpan("mcp.plugin.probe_endpoint", {
|
|
955
|
+
attributes: { "mcp.endpoint": typeof input === "string" ? input : input.endpoint }
|
|
956
|
+
})
|
|
957
|
+
);
|
|
958
|
+
const addServer = (input) => Effect5.gen(function* () {
|
|
959
|
+
const slug = normalizeSlug(input);
|
|
960
|
+
const config = toIntegrationConfig(input);
|
|
961
|
+
const existing = yield* ctx.core.integrations.get(slugFrom(slug));
|
|
962
|
+
if (existing) {
|
|
963
|
+
return yield* new IntegrationAlreadyExistsError({ slug: slugFrom(slug) });
|
|
964
|
+
}
|
|
965
|
+
yield* ctx.core.integrations.register({
|
|
966
|
+
slug: slugFrom(slug),
|
|
967
|
+
description: input.name,
|
|
968
|
+
config,
|
|
969
|
+
canRemove: true,
|
|
970
|
+
canRefresh: true
|
|
971
|
+
}).pipe(
|
|
972
|
+
Effect5.withSpan("mcp.plugin.register_integration", {
|
|
973
|
+
attributes: { "mcp.integration.slug": slug }
|
|
974
|
+
})
|
|
975
|
+
);
|
|
976
|
+
return { slug };
|
|
977
|
+
}).pipe(
|
|
978
|
+
Effect5.withSpan("mcp.plugin.add_server", {
|
|
979
|
+
attributes: {
|
|
980
|
+
"mcp.server.transport": input.transport ?? "remote",
|
|
981
|
+
"mcp.server.name": input.name
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
);
|
|
985
|
+
const removeServer = (slug) => Effect5.gen(function* () {
|
|
986
|
+
const integration = slugFrom(slug);
|
|
987
|
+
const record = yield* ctx.core.integrations.get(integration);
|
|
988
|
+
const config = record ? parseMcpIntegrationConfig(record.config) : null;
|
|
989
|
+
const legacyCandidates = legacyOAuthClientSlugCandidates(slug, record);
|
|
990
|
+
const connections = yield* ctx.connections.list({ integration });
|
|
991
|
+
const allConnections = yield* ctx.connections.list();
|
|
992
|
+
const oauthClientSummaries = yield* ctx.oauth.listClients();
|
|
993
|
+
const usedElsewhere = new Set(
|
|
994
|
+
allConnections.filter((connection) => String(connection.integration) !== String(integration)).flatMap(
|
|
995
|
+
(connection) => connection.oauthClient == null ? [] : [
|
|
996
|
+
oauthClientKey(
|
|
997
|
+
connection.oauthClientOwner ?? connection.owner,
|
|
998
|
+
connection.oauthClient
|
|
999
|
+
)
|
|
1000
|
+
]
|
|
1001
|
+
)
|
|
1002
|
+
);
|
|
1003
|
+
const oauthClientsByKey = new Map(
|
|
1004
|
+
oauthClientSummaries.map((client) => [
|
|
1005
|
+
oauthClientKey(client.owner, client.slug),
|
|
1006
|
+
client
|
|
1007
|
+
])
|
|
1008
|
+
);
|
|
1009
|
+
const clientsToRemove = /* @__PURE__ */ new Map();
|
|
1010
|
+
for (const connection of connections) {
|
|
1011
|
+
if (connection.oauthClient == null) continue;
|
|
1012
|
+
const owner = connection.oauthClientOwner ?? connection.owner;
|
|
1013
|
+
const key = oauthClientKey(owner, connection.oauthClient);
|
|
1014
|
+
const client = oauthClientsByKey.get(key);
|
|
1015
|
+
if (client?.origin.kind !== "dynamic_client_registration") continue;
|
|
1016
|
+
clientsToRemove.set(key, {
|
|
1017
|
+
owner,
|
|
1018
|
+
slug: connection.oauthClient
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
for (const client of oauthClientSummaries) {
|
|
1022
|
+
const key = oauthClientKey(client.owner, client.slug);
|
|
1023
|
+
if (usedElsewhere.has(key)) continue;
|
|
1024
|
+
if (client.origin.kind === "dynamic_client_registration" && (client.origin.integration == null || String(client.origin.integration) === slug)) {
|
|
1025
|
+
clientsToRemove.set(key, { owner: client.owner, slug: client.slug });
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (legacyMcpClientMatches(client, legacyCandidates, config)) {
|
|
1029
|
+
clientsToRemove.set(key, { owner: client.owner, slug: client.slug });
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
yield* ctx.core.integrations.remove(integration).pipe(Effect5.catchTag("IntegrationRemovalNotAllowedError", () => Effect5.void));
|
|
1033
|
+
yield* Effect5.forEach(
|
|
1034
|
+
clientsToRemove.values(),
|
|
1035
|
+
(client) => ctx.oauth.removeClient(client.owner, client.slug),
|
|
1036
|
+
{ discard: true }
|
|
1037
|
+
);
|
|
1038
|
+
}).pipe(
|
|
1039
|
+
Effect5.withSpan("mcp.plugin.remove_server", {
|
|
1040
|
+
attributes: { "mcp.integration.slug": slug }
|
|
1041
|
+
})
|
|
1042
|
+
);
|
|
1043
|
+
const getServer = (slug) => ctx.core.integrations.get(slugFrom(slug)).pipe(
|
|
1044
|
+
Effect5.withSpan("mcp.plugin.get_server", {
|
|
1045
|
+
attributes: { "mcp.integration.slug": slug }
|
|
1046
|
+
})
|
|
1047
|
+
);
|
|
1048
|
+
const configureServer = (slug, config) => ctx.core.integrations.update(slugFrom(slug), { config }).pipe(
|
|
1049
|
+
Effect5.withSpan("mcp.plugin.configure_server", {
|
|
1050
|
+
attributes: { "mcp.integration.slug": slug }
|
|
1051
|
+
})
|
|
1052
|
+
);
|
|
1053
|
+
return {
|
|
1054
|
+
probeEndpoint,
|
|
1055
|
+
addServer,
|
|
1056
|
+
removeServer,
|
|
1057
|
+
getServer,
|
|
1058
|
+
configureServer
|
|
1059
|
+
};
|
|
1060
|
+
},
|
|
1061
|
+
// -----------------------------------------------------------------------
|
|
1062
|
+
// Per-connection tool production. Dial the server using the connection's
|
|
1063
|
+
// resolved value (rendered through the integration's auth template) and
|
|
1064
|
+
// list its tools. The real MCP tool name + upstream annotations are
|
|
1065
|
+
// stamped into each ToolDef's annotations so invokeTool can recover them.
|
|
1066
|
+
// Discovery failures (auth not ready, server down) yield an empty tool set
|
|
1067
|
+
// rather than failing — the connection still lands and can be refreshed.
|
|
1068
|
+
// -----------------------------------------------------------------------
|
|
1069
|
+
resolveTools: ({ config, connection, getValue }) => Effect5.gen(function* () {
|
|
1070
|
+
const parsed = parseMcpIntegrationConfig(config);
|
|
1071
|
+
if (!parsed) return { tools: [] };
|
|
1072
|
+
const value = yield* getValue().pipe(Effect5.orElseSucceed(() => null));
|
|
1073
|
+
const built = yield* buildConnectorInput(parsed, value, allowStdio).pipe(
|
|
1074
|
+
Effect5.map((ci) => createMcpConnector(ci)),
|
|
1075
|
+
Effect5.result
|
|
1076
|
+
);
|
|
1077
|
+
const manifest = Result.isSuccess(built) ? yield* discoverTools(built.success).pipe(
|
|
1078
|
+
Effect5.map((m) => ({ ok: true, manifest: m })),
|
|
1079
|
+
Effect5.catch(() => Effect5.succeed({ ok: false, manifest: null })),
|
|
1080
|
+
Effect5.withSpan("mcp.plugin.discover_tools", {
|
|
1081
|
+
attributes: { "mcp.connection.name": String(connection.name) }
|
|
1082
|
+
})
|
|
1083
|
+
) : { ok: false, manifest: null };
|
|
1084
|
+
const entries = manifest.ok && manifest.manifest ? manifest.manifest.tools : [];
|
|
1085
|
+
return { tools: entries.map(toToolDef) };
|
|
1086
|
+
}).pipe(
|
|
1087
|
+
Effect5.withSpan("mcp.plugin.resolve_tools", {
|
|
1088
|
+
attributes: { "mcp.connection.name": String(connection.name) }
|
|
1089
|
+
})
|
|
1090
|
+
),
|
|
1091
|
+
invokeTool: ({ toolRow, credential, args, elicit }) => Effect5.gen(function* () {
|
|
1092
|
+
const parsed = parseMcpIntegrationConfig(credential.config);
|
|
1093
|
+
if (!parsed) {
|
|
1094
|
+
return yield* new McpConnectionError({
|
|
1095
|
+
transport: "auto",
|
|
1096
|
+
message: `MCP integration "${toolRow.integration}" has no usable config`
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
const stamp = readStamp(toolRow.annotations);
|
|
1100
|
+
if (!stamp) {
|
|
1101
|
+
return yield* new McpToolDiscoveryError({
|
|
1102
|
+
stage: "list_tools",
|
|
1103
|
+
message: `Tool "${toolRow.name}" is missing its MCP binding \u2014 refresh the connection`
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
const transport = parsed.transport === "stdio" ? "stdio" : parsed.remoteTransport ?? "auto";
|
|
1107
|
+
const connector = yield* buildConnectorInput(
|
|
1108
|
+
parsed,
|
|
1109
|
+
credential.value,
|
|
1110
|
+
allowStdio
|
|
1111
|
+
).pipe(Effect5.map((ci) => createMcpConnector(ci)));
|
|
1112
|
+
const raw = yield* invokeMcpTool({
|
|
1113
|
+
toolId: String(toolRow.name),
|
|
1114
|
+
toolName: stamp.toolName,
|
|
1115
|
+
args,
|
|
1116
|
+
transport,
|
|
1117
|
+
connector,
|
|
1118
|
+
elicit
|
|
1119
|
+
});
|
|
1120
|
+
const envelope = Option4.getOrUndefined(decodeMcpToolCallEnvelope(raw));
|
|
1121
|
+
if (envelope?.isError === true) {
|
|
1122
|
+
return ToolResult.fail({
|
|
1123
|
+
code: "mcp_tool_error",
|
|
1124
|
+
message: extractMcpErrorMessage(envelope.content),
|
|
1125
|
+
details: { content: envelope.content }
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return ToolResult.ok(raw);
|
|
1129
|
+
}).pipe(
|
|
1130
|
+
Effect5.catchTag(
|
|
1131
|
+
"McpConnectionError",
|
|
1132
|
+
({ message }) => Effect5.succeed(
|
|
1133
|
+
authToolFailure({
|
|
1134
|
+
code: "connection_rejected",
|
|
1135
|
+
message,
|
|
1136
|
+
source: { id: String(credential.integration) },
|
|
1137
|
+
credential: { kind: "upstream", label: String(credential.connection) }
|
|
1138
|
+
})
|
|
1139
|
+
)
|
|
1140
|
+
),
|
|
1141
|
+
Effect5.withSpan("mcp.plugin.invoke_tool", {
|
|
1142
|
+
attributes: {
|
|
1143
|
+
"mcp.tool.name": String(toolRow.name),
|
|
1144
|
+
"mcp.integration.slug": String(toolRow.integration)
|
|
1145
|
+
}
|
|
1146
|
+
})
|
|
1147
|
+
),
|
|
1148
|
+
detect: ({ ctx, url }) => Effect5.gen(function* () {
|
|
1149
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
1150
|
+
const trimmed = url.trim();
|
|
1151
|
+
if (!trimmed) return null;
|
|
1152
|
+
const parsed = yield* Effect5.try({
|
|
1153
|
+
try: () => new URL(trimmed),
|
|
1154
|
+
catch: (cause) => cause
|
|
1155
|
+
}).pipe(Effect5.option);
|
|
1156
|
+
if (Option4.isNone(parsed)) return null;
|
|
1157
|
+
const name = parsed.value.hostname || "mcp";
|
|
1158
|
+
const slug = deriveMcpNamespace({ endpoint: trimmed });
|
|
1159
|
+
const connector = createMcpConnector({ transport: "remote", endpoint: trimmed });
|
|
1160
|
+
const connected = yield* discoverTools(connector).pipe(
|
|
1161
|
+
Effect5.map(() => true),
|
|
1162
|
+
Effect5.catch(() => Effect5.succeed(false)),
|
|
1163
|
+
Effect5.withSpan("mcp.plugin.discover_tools")
|
|
1164
|
+
);
|
|
1165
|
+
if (connected) {
|
|
1166
|
+
return {
|
|
1167
|
+
kind: MCP_PLUGIN_ID,
|
|
1168
|
+
confidence: "high",
|
|
1169
|
+
endpoint: trimmed,
|
|
1170
|
+
name,
|
|
1171
|
+
slug
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const shape = yield* probeMcpEndpointShape(trimmed, { httpClientLayer });
|
|
1175
|
+
if (shape.kind === "mcp") {
|
|
1176
|
+
return {
|
|
1177
|
+
kind: MCP_PLUGIN_ID,
|
|
1178
|
+
confidence: "high",
|
|
1179
|
+
endpoint: trimmed,
|
|
1180
|
+
name,
|
|
1181
|
+
slug
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
if (urlMatchesToken(parsed.value, "mcp")) {
|
|
1185
|
+
return {
|
|
1186
|
+
kind: MCP_PLUGIN_ID,
|
|
1187
|
+
confidence: "low",
|
|
1188
|
+
endpoint: trimmed,
|
|
1189
|
+
name,
|
|
1190
|
+
slug
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
return null;
|
|
1194
|
+
}).pipe(
|
|
1195
|
+
Effect5.catch(() => Effect5.succeed(null)),
|
|
1196
|
+
Effect5.withSpan("mcp.plugin.detect", {
|
|
1197
|
+
attributes: { "mcp.endpoint": url }
|
|
1198
|
+
})
|
|
1199
|
+
),
|
|
1200
|
+
// Honour upstream destructiveHint from MCP ToolAnnotations using the stamp
|
|
1201
|
+
// persisted in each tool row's annotations.
|
|
1202
|
+
resolveAnnotations: ({ toolRows }) => Effect5.sync(() => {
|
|
1203
|
+
const out = {};
|
|
1204
|
+
for (const row of toolRows) {
|
|
1205
|
+
const stamp = readStamp(row.annotations);
|
|
1206
|
+
const ann = stamp?.upstream;
|
|
1207
|
+
if (ann?.destructiveHint === true) {
|
|
1208
|
+
out[String(row.name)] = {
|
|
1209
|
+
requiresApproval: true,
|
|
1210
|
+
approvalDescription: ann.title ?? stamp?.toolName ?? String(row.name)
|
|
1211
|
+
};
|
|
1212
|
+
} else {
|
|
1213
|
+
out[String(row.name)] = { requiresApproval: false };
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return out;
|
|
1217
|
+
}),
|
|
1218
|
+
describeAuthMethods: describeMcpAuthMethods,
|
|
1219
|
+
describeIntegrationDisplay: describeMcpIntegrationDisplay,
|
|
1220
|
+
integrationConfigure: {
|
|
1221
|
+
type: "mcp",
|
|
1222
|
+
configure: ({ ctx, integration, config }) => Effect5.gen(function* () {
|
|
1223
|
+
const next = parseMcpIntegrationConfig(config);
|
|
1224
|
+
if (!next) return;
|
|
1225
|
+
yield* ctx.core.integrations.update(integration, { config: next });
|
|
1226
|
+
})
|
|
1227
|
+
},
|
|
1228
|
+
staticSources: (self) => [
|
|
1229
|
+
{
|
|
1230
|
+
id: "mcp",
|
|
1231
|
+
kind: "executor",
|
|
1232
|
+
name: "MCP",
|
|
1233
|
+
tools: [
|
|
1234
|
+
tool({
|
|
1235
|
+
name: "probeEndpoint",
|
|
1236
|
+
description: "Probe a remote MCP endpoint before adding it. If the result requires OAuth, run the core OAuth handoff (`oauth.probe`, `oauth.start`) to mint a connection; otherwise create a connection with `connections.create` carrying the API key or header value.",
|
|
1237
|
+
inputSchema: McpProbeEndpointInputStandardSchema,
|
|
1238
|
+
outputSchema: McpProbeEndpointOutputStandardSchema,
|
|
1239
|
+
execute: (input) => self.probeEndpoint(input).pipe(
|
|
1240
|
+
Effect5.map(ToolResult.ok),
|
|
1241
|
+
Effect5.catchTag(
|
|
1242
|
+
"McpConnectionError",
|
|
1243
|
+
({ message, transport }) => Effect5.succeed(mcpToolFailure("mcp_connection_failed", message, { transport }))
|
|
1244
|
+
)
|
|
1245
|
+
)
|
|
1246
|
+
}),
|
|
1247
|
+
tool({
|
|
1248
|
+
name: "getServer",
|
|
1249
|
+
description: "Inspect a registered MCP integration, including transport, endpoint/command, and auth template. Use this before creating a connection (`connections.create` / `oauth.start`).",
|
|
1250
|
+
inputSchema: McpGetServerInputStandardSchema,
|
|
1251
|
+
outputSchema: McpGetServerOutputStandardSchema,
|
|
1252
|
+
execute: (input) => {
|
|
1253
|
+
const args = input;
|
|
1254
|
+
return Effect5.map(
|
|
1255
|
+
self.getServer(args.slug),
|
|
1256
|
+
(integration) => ToolResult.ok({ integration })
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}),
|
|
1260
|
+
tool({
|
|
1261
|
+
name: "addServer",
|
|
1262
|
+
description: "Register an MCP server in the catalog as an integration. Returns its `slug`. Then create a connection against it: for header/API-key auth call `connections.create` with the value; for OAuth-protected servers run `oauth.probe` + `oauth.start`. Tools are produced per-connection at connection create / refresh.",
|
|
1263
|
+
annotations: {
|
|
1264
|
+
requiresApproval: true,
|
|
1265
|
+
approvalDescription: "Add an MCP server"
|
|
1266
|
+
},
|
|
1267
|
+
inputSchema: McpAddServerInputStandardSchema,
|
|
1268
|
+
outputSchema: McpAddServerOutputStandardSchema,
|
|
1269
|
+
execute: (rawInput) => {
|
|
1270
|
+
const input = rawInput;
|
|
1271
|
+
return self.addServer(input).pipe(
|
|
1272
|
+
Effect5.map(ToolResult.ok),
|
|
1273
|
+
Effect5.catchTag(
|
|
1274
|
+
"IntegrationAlreadyExistsError",
|
|
1275
|
+
({ slug }) => Effect5.succeed(
|
|
1276
|
+
mcpToolFailure(
|
|
1277
|
+
"integration_already_exists",
|
|
1278
|
+
`Integration ${slug} already exists; update it instead of re-adding.`
|
|
1279
|
+
)
|
|
1280
|
+
)
|
|
1281
|
+
)
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
})
|
|
1285
|
+
]
|
|
1286
|
+
}
|
|
1287
|
+
]
|
|
1288
|
+
};
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
export {
|
|
1292
|
+
joinToolPath,
|
|
1293
|
+
extractManifestFromListToolsResult,
|
|
1294
|
+
deriveMcpNamespace,
|
|
1295
|
+
userFacingProbeMessage,
|
|
1296
|
+
mcpPlugin
|
|
1297
|
+
};
|
|
1298
|
+
//# sourceMappingURL=chunk-2TXHTMKM.js.map
|