@executor-js/plugin-mcp 0.0.1-beta.6 → 0.0.2
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/README.md +19 -15
- package/dist/api/group.d.ts +114 -147
- package/dist/api/handlers.d.ts +2 -2
- package/dist/api/handlers.test.d.ts +1 -0
- package/dist/chunk-DJANY5EU.js +1325 -0
- package/dist/chunk-DJANY5EU.js.map +1 -0
- package/dist/core.js +8 -52
- package/dist/core.js.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/promise.d.ts +2 -6
- package/dist/react/AddMcpSource.d.ts +2 -0
- package/dist/react/McpSignInButton.d.ts +3 -0
- package/dist/react/atoms.d.ts +153 -0
- package/dist/react/client.d.ts +437 -3
- package/dist/react/index.d.ts +3 -2
- package/dist/react/source-plugin.d.ts +13 -1
- package/dist/sdk/binding-store.d.ts +74 -18
- package/dist/sdk/connection-pool.test.d.ts +1 -0
- package/dist/sdk/connection.d.ts +3 -1
- package/dist/sdk/cross-user-isolation.test.d.ts +1 -0
- package/dist/sdk/errors.d.ts +15 -23
- package/dist/sdk/index.d.ts +3 -3
- package/dist/sdk/invoke.d.ts +18 -17
- package/dist/sdk/manifest.d.ts +1 -0
- package/dist/sdk/per-user-auth-isolation.test.d.ts +1 -0
- package/dist/sdk/plugin.d.ts +109 -43
- package/dist/sdk/probe-shape.d.ts +39 -0
- package/dist/sdk/probe-shape.test.d.ts +1 -0
- package/dist/sdk/stdio-connector.d.ts +8 -0
- package/dist/sdk/stored-source.d.ts +31 -105
- package/dist/sdk/types.d.ts +77 -93
- package/dist/stdio-connector-KNHLETKM.js +12 -0
- package/dist/stdio-connector-KNHLETKM.js.map +1 -0
- package/package.json +11 -21
- package/dist/chunk-NJ4CITCV.js +0 -1203
- package/dist/chunk-NJ4CITCV.js.map +0 -1
- package/dist/react/McpSourceSummary.d.ts +0 -3
- package/dist/sdk/config-file-store.d.ts +0 -10
- package/dist/sdk/oauth.d.ts +0 -36
package/dist/chunk-NJ4CITCV.js
DELETED
|
@@ -1,1203 +0,0 @@
|
|
|
1
|
-
// src/sdk/binding-store.ts
|
|
2
|
-
import { Effect, Schema as Schema2 } from "effect";
|
|
3
|
-
import { makeInMemoryScopedKv, scopeKv } from "@executor-js/sdk";
|
|
4
|
-
|
|
5
|
-
// src/sdk/types.ts
|
|
6
|
-
import { Schema } from "effect";
|
|
7
|
-
var McpRemoteTransport = Schema.Literal("streamable-http", "sse", "auto");
|
|
8
|
-
var McpTransport = Schema.Literal("streamable-http", "sse", "stdio", "auto");
|
|
9
|
-
var McpConnectionAuth = Schema.Union(
|
|
10
|
-
Schema.Struct({ kind: Schema.Literal("none") }),
|
|
11
|
-
Schema.Struct({
|
|
12
|
-
kind: Schema.Literal("header"),
|
|
13
|
-
headerName: Schema.String,
|
|
14
|
-
secretId: Schema.String,
|
|
15
|
-
prefix: Schema.optional(Schema.String)
|
|
16
|
-
}),
|
|
17
|
-
Schema.Struct({
|
|
18
|
-
kind: Schema.Literal("oauth2"),
|
|
19
|
-
accessTokenSecretId: Schema.String,
|
|
20
|
-
refreshTokenSecretId: Schema.NullOr(Schema.String),
|
|
21
|
-
tokenType: Schema.optionalWith(Schema.String, { default: () => "Bearer" }),
|
|
22
|
-
expiresAt: Schema.NullOr(Schema.Number),
|
|
23
|
-
scope: Schema.NullOr(Schema.String)
|
|
24
|
-
})
|
|
25
|
-
);
|
|
26
|
-
var StringMap = Schema.Record({ key: Schema.String, value: Schema.String });
|
|
27
|
-
var McpRemoteSourceData = Schema.Struct({
|
|
28
|
-
transport: Schema.Literal("remote"),
|
|
29
|
-
/** The MCP server endpoint URL */
|
|
30
|
-
endpoint: Schema.String,
|
|
31
|
-
/** Transport preference for this remote source */
|
|
32
|
-
remoteTransport: Schema.optionalWith(McpRemoteTransport, { default: () => "auto" }),
|
|
33
|
-
/** Extra query params appended to the endpoint URL */
|
|
34
|
-
queryParams: Schema.optional(StringMap),
|
|
35
|
-
/** Extra headers sent on every request */
|
|
36
|
-
headers: Schema.optional(StringMap),
|
|
37
|
-
/** Auth configuration */
|
|
38
|
-
auth: McpConnectionAuth
|
|
39
|
-
});
|
|
40
|
-
var McpStdioSourceData = Schema.Struct({
|
|
41
|
-
transport: Schema.Literal("stdio"),
|
|
42
|
-
/** The command to run */
|
|
43
|
-
command: Schema.String,
|
|
44
|
-
/** Arguments to the command */
|
|
45
|
-
args: Schema.optional(Schema.Array(Schema.String)),
|
|
46
|
-
/** Environment variables */
|
|
47
|
-
env: Schema.optional(StringMap),
|
|
48
|
-
/** Working directory */
|
|
49
|
-
cwd: Schema.optional(Schema.String)
|
|
50
|
-
});
|
|
51
|
-
var McpStoredSourceData = Schema.Union(McpRemoteSourceData, McpStdioSourceData);
|
|
52
|
-
var McpToolBinding = class extends Schema.Class("McpToolBinding")({
|
|
53
|
-
toolId: Schema.String,
|
|
54
|
-
toolName: Schema.String,
|
|
55
|
-
description: Schema.NullOr(Schema.String),
|
|
56
|
-
inputSchema: Schema.optional(Schema.Unknown),
|
|
57
|
-
outputSchema: Schema.optional(Schema.Unknown)
|
|
58
|
-
}) {
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// src/sdk/binding-store.ts
|
|
62
|
-
var StoredBindingEntry = Schema2.Struct({
|
|
63
|
-
namespace: Schema2.String,
|
|
64
|
-
binding: McpToolBinding,
|
|
65
|
-
sourceData: Schema2.Unknown
|
|
66
|
-
});
|
|
67
|
-
var encodeBindingEntry = Schema2.encodeSync(Schema2.parseJson(StoredBindingEntry));
|
|
68
|
-
var decodeBindingEntry = Schema2.decodeUnknownSync(Schema2.parseJson(StoredBindingEntry));
|
|
69
|
-
var makeStore = (bindings, sources) => ({
|
|
70
|
-
// ---- Bindings ----
|
|
71
|
-
get: (toolId) => Effect.gen(function* () {
|
|
72
|
-
const raw = yield* bindings.get(toolId);
|
|
73
|
-
if (!raw) return null;
|
|
74
|
-
const entry = decodeBindingEntry(raw);
|
|
75
|
-
return {
|
|
76
|
-
binding: entry.binding,
|
|
77
|
-
sourceData: entry.sourceData
|
|
78
|
-
};
|
|
79
|
-
}),
|
|
80
|
-
put: (toolId, namespace, binding, sourceData) => bindings.set([{ key: toolId, value: encodeBindingEntry({ namespace, binding, sourceData }) }]),
|
|
81
|
-
remove: (toolId) => bindings.delete([toolId]).pipe(Effect.asVoid),
|
|
82
|
-
listByNamespace: (namespace) => Effect.gen(function* () {
|
|
83
|
-
const entries = yield* bindings.list();
|
|
84
|
-
const ids = [];
|
|
85
|
-
for (const e of entries) {
|
|
86
|
-
const entry = decodeBindingEntry(e.value);
|
|
87
|
-
if (entry.namespace === namespace) ids.push(e.key);
|
|
88
|
-
}
|
|
89
|
-
return ids;
|
|
90
|
-
}),
|
|
91
|
-
removeByNamespace: (namespace) => Effect.gen(function* () {
|
|
92
|
-
const entries = yield* bindings.list();
|
|
93
|
-
const ids = [];
|
|
94
|
-
for (const e of entries) {
|
|
95
|
-
const entry = decodeBindingEntry(e.value);
|
|
96
|
-
if (entry.namespace === namespace) ids.push(e.key);
|
|
97
|
-
}
|
|
98
|
-
if (ids.length > 0) yield* bindings.delete(ids);
|
|
99
|
-
return ids;
|
|
100
|
-
}),
|
|
101
|
-
// ---- Sources (meta + config combined) ----
|
|
102
|
-
putSource: (source) => sources.set([{ key: source.namespace, value: JSON.stringify(source) }]),
|
|
103
|
-
removeSource: (namespace) => sources.delete([namespace]).pipe(Effect.asVoid),
|
|
104
|
-
listSources: () => Effect.gen(function* () {
|
|
105
|
-
const entries = yield* sources.list();
|
|
106
|
-
return entries.map((e) => JSON.parse(e.value));
|
|
107
|
-
}),
|
|
108
|
-
getSource: (namespace) => Effect.gen(function* () {
|
|
109
|
-
const raw = yield* sources.get(namespace);
|
|
110
|
-
if (!raw) return null;
|
|
111
|
-
return JSON.parse(raw);
|
|
112
|
-
}),
|
|
113
|
-
getSourceConfig: (namespace) => Effect.gen(function* () {
|
|
114
|
-
const raw = yield* sources.get(namespace);
|
|
115
|
-
if (!raw) return null;
|
|
116
|
-
const source = JSON.parse(raw);
|
|
117
|
-
return source.config;
|
|
118
|
-
})
|
|
119
|
-
});
|
|
120
|
-
var makeKvBindingStore = (kv, namespace) => makeStore(scopeKv(kv, `${namespace}.bindings`), scopeKv(kv, `${namespace}.sources`));
|
|
121
|
-
var makeInMemoryBindingStore = () => makeStore(makeInMemoryScopedKv(), makeInMemoryScopedKv());
|
|
122
|
-
|
|
123
|
-
// src/sdk/plugin.ts
|
|
124
|
-
import { Effect as Effect6, Exit, ScopedCache, Duration, Scope } from "effect";
|
|
125
|
-
import {
|
|
126
|
-
Source,
|
|
127
|
-
SourceDetectionResult,
|
|
128
|
-
definePlugin,
|
|
129
|
-
ToolId,
|
|
130
|
-
SecretId
|
|
131
|
-
} from "@executor-js/sdk";
|
|
132
|
-
|
|
133
|
-
// src/sdk/connection.ts
|
|
134
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
135
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
136
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
137
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
138
|
-
import { Effect as Effect2 } from "effect";
|
|
139
|
-
|
|
140
|
-
// src/sdk/errors.ts
|
|
141
|
-
import { Schema as Schema3 } from "effect";
|
|
142
|
-
var McpConnectionError = class extends Schema3.TaggedError()(
|
|
143
|
-
"McpConnectionError",
|
|
144
|
-
{
|
|
145
|
-
transport: Schema3.String,
|
|
146
|
-
message: Schema3.String
|
|
147
|
-
}
|
|
148
|
-
) {
|
|
149
|
-
};
|
|
150
|
-
var McpToolDiscoveryError = class extends Schema3.TaggedError()(
|
|
151
|
-
"McpToolDiscoveryError",
|
|
152
|
-
{
|
|
153
|
-
stage: Schema3.Literal("connect", "list_tools"),
|
|
154
|
-
message: Schema3.String
|
|
155
|
-
}
|
|
156
|
-
) {
|
|
157
|
-
};
|
|
158
|
-
var McpInvocationError = class extends Schema3.TaggedError()(
|
|
159
|
-
"McpInvocationError",
|
|
160
|
-
{
|
|
161
|
-
toolName: Schema3.String,
|
|
162
|
-
message: Schema3.String
|
|
163
|
-
}
|
|
164
|
-
) {
|
|
165
|
-
};
|
|
166
|
-
var McpOAuthError = class extends Schema3.TaggedError()("McpOAuthError", {
|
|
167
|
-
message: Schema3.String
|
|
168
|
-
}) {
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// src/sdk/connection.ts
|
|
172
|
-
var buildEndpointUrl = (endpoint, queryParams) => {
|
|
173
|
-
const url = new URL(endpoint);
|
|
174
|
-
for (const [key, value] of Object.entries(queryParams)) {
|
|
175
|
-
url.searchParams.set(key, value);
|
|
176
|
-
}
|
|
177
|
-
return url;
|
|
178
|
-
};
|
|
179
|
-
var createClient = () => new Client(
|
|
180
|
-
{ name: "executor-mcp", version: "0.1.0" },
|
|
181
|
-
{ capabilities: { elicitation: { form: {}, url: {} } } }
|
|
182
|
-
);
|
|
183
|
-
var connectionFromClient = (client) => ({
|
|
184
|
-
client,
|
|
185
|
-
close: () => client.close()
|
|
186
|
-
});
|
|
187
|
-
var connectClient = (input) => Effect2.gen(function* () {
|
|
188
|
-
const client = createClient();
|
|
189
|
-
const transportInstance = input.createTransport();
|
|
190
|
-
yield* Effect2.tryPromise({
|
|
191
|
-
try: () => client.connect(transportInstance),
|
|
192
|
-
catch: (cause) => new McpConnectionError({
|
|
193
|
-
transport: input.transport,
|
|
194
|
-
message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
195
|
-
})
|
|
196
|
-
});
|
|
197
|
-
return connectionFromClient(client);
|
|
198
|
-
});
|
|
199
|
-
var createMcpConnector = (input) => {
|
|
200
|
-
if (input.transport === "stdio") {
|
|
201
|
-
const command = input.command.trim();
|
|
202
|
-
if (!command) {
|
|
203
|
-
return new McpConnectionError({
|
|
204
|
-
transport: "stdio",
|
|
205
|
-
message: "MCP stdio transport requires a command"
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return connectClient({
|
|
209
|
-
transport: "stdio",
|
|
210
|
-
createTransport: () => new StdioClientTransport({
|
|
211
|
-
command,
|
|
212
|
-
args: input.args ? [...input.args] : void 0,
|
|
213
|
-
env: input.env ? { ...process.env, ...input.env } : void 0,
|
|
214
|
-
cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
|
|
215
|
-
})
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
const headers = input.headers ?? {};
|
|
219
|
-
const remoteTransport = input.remoteTransport ?? "auto";
|
|
220
|
-
const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
|
|
221
|
-
const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
|
|
222
|
-
const connectStreamableHttp = connectClient({
|
|
223
|
-
transport: "streamable-http",
|
|
224
|
-
createTransport: () => new StreamableHTTPClientTransport(endpoint, {
|
|
225
|
-
requestInit,
|
|
226
|
-
authProvider: input.authProvider
|
|
227
|
-
})
|
|
228
|
-
});
|
|
229
|
-
const connectSse = connectClient({
|
|
230
|
-
transport: "sse",
|
|
231
|
-
createTransport: () => new SSEClientTransport(endpoint, {
|
|
232
|
-
requestInit,
|
|
233
|
-
authProvider: input.authProvider
|
|
234
|
-
})
|
|
235
|
-
});
|
|
236
|
-
if (remoteTransport === "streamable-http") return connectStreamableHttp;
|
|
237
|
-
if (remoteTransport === "sse") return connectSse;
|
|
238
|
-
return connectStreamableHttp.pipe(Effect2.catchAll(() => connectSse));
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// src/sdk/oauth.ts
|
|
242
|
-
import {
|
|
243
|
-
auth
|
|
244
|
-
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
245
|
-
import { Effect as Effect3 } from "effect";
|
|
246
|
-
var toJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
247
|
-
var CLIENT_METADATA = {
|
|
248
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
249
|
-
response_types: ["code"],
|
|
250
|
-
token_endpoint_auth_method: "none",
|
|
251
|
-
client_name: "Executor"
|
|
252
|
-
};
|
|
253
|
-
var extractDiscoveryState = (discoveryState, clientInformation) => ({
|
|
254
|
-
resourceMetadataUrl: discoveryState?.resourceMetadataUrl ?? null,
|
|
255
|
-
authorizationServerUrl: discoveryState?.authorizationServerUrl ?? null,
|
|
256
|
-
resourceMetadata: toJsonObject(discoveryState?.resourceMetadata),
|
|
257
|
-
authorizationServerMetadata: toJsonObject(discoveryState?.authorizationServerMetadata),
|
|
258
|
-
clientInformation: toJsonObject(clientInformation)
|
|
259
|
-
});
|
|
260
|
-
var callAuth = (provider, opts) => Effect3.tryPromise({
|
|
261
|
-
try: () => auth(provider, opts),
|
|
262
|
-
catch: (cause) => new McpOAuthError({
|
|
263
|
-
message: cause instanceof Error ? cause.message : String(cause)
|
|
264
|
-
})
|
|
265
|
-
});
|
|
266
|
-
var startMcpOAuthAuthorization = (input) => Effect3.gen(function* () {
|
|
267
|
-
let authorizationUrl;
|
|
268
|
-
let codeVerifier;
|
|
269
|
-
let discoveryState;
|
|
270
|
-
let clientInformation;
|
|
271
|
-
const provider = {
|
|
272
|
-
get redirectUrl() {
|
|
273
|
-
return input.redirectUrl;
|
|
274
|
-
},
|
|
275
|
-
get clientMetadata() {
|
|
276
|
-
return { ...CLIENT_METADATA, redirect_uris: [input.redirectUrl] };
|
|
277
|
-
},
|
|
278
|
-
state: () => input.state,
|
|
279
|
-
clientInformation: () => clientInformation,
|
|
280
|
-
saveClientInformation: (ci) => {
|
|
281
|
-
clientInformation = ci;
|
|
282
|
-
},
|
|
283
|
-
tokens: () => void 0,
|
|
284
|
-
saveTokens: () => void 0,
|
|
285
|
-
redirectToAuthorization: (url) => {
|
|
286
|
-
authorizationUrl = url;
|
|
287
|
-
},
|
|
288
|
-
saveCodeVerifier: (cv) => {
|
|
289
|
-
codeVerifier = cv;
|
|
290
|
-
},
|
|
291
|
-
codeVerifier: () => {
|
|
292
|
-
if (!codeVerifier) throw new Error("Code verifier not captured");
|
|
293
|
-
return codeVerifier;
|
|
294
|
-
},
|
|
295
|
-
saveDiscoveryState: (s) => {
|
|
296
|
-
discoveryState = s;
|
|
297
|
-
},
|
|
298
|
-
discoveryState: () => discoveryState
|
|
299
|
-
};
|
|
300
|
-
const result = yield* callAuth(provider, { serverUrl: input.endpoint });
|
|
301
|
-
if (result !== "REDIRECT" || !authorizationUrl || !codeVerifier) {
|
|
302
|
-
return yield* new McpOAuthError({
|
|
303
|
-
message: "OAuth flow did not produce an authorization redirect"
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
return {
|
|
307
|
-
authorizationUrl: authorizationUrl.toString(),
|
|
308
|
-
codeVerifier,
|
|
309
|
-
...extractDiscoveryState(discoveryState, clientInformation)
|
|
310
|
-
};
|
|
311
|
-
});
|
|
312
|
-
var exchangeMcpOAuthCode = (input) => Effect3.gen(function* () {
|
|
313
|
-
const { session } = input;
|
|
314
|
-
let tokens;
|
|
315
|
-
let discoveryState = {
|
|
316
|
-
authorizationServerUrl: session.authorizationServerUrl ?? new URL("/", session.endpoint).toString(),
|
|
317
|
-
resourceMetadataUrl: session.resourceMetadataUrl ?? void 0,
|
|
318
|
-
resourceMetadata: session.resourceMetadata,
|
|
319
|
-
authorizationServerMetadata: session.authorizationServerMetadata
|
|
320
|
-
};
|
|
321
|
-
let clientInformation = session.clientInformation;
|
|
322
|
-
const provider = {
|
|
323
|
-
get redirectUrl() {
|
|
324
|
-
return session.redirectUrl;
|
|
325
|
-
},
|
|
326
|
-
get clientMetadata() {
|
|
327
|
-
return { ...CLIENT_METADATA, redirect_uris: [session.redirectUrl] };
|
|
328
|
-
},
|
|
329
|
-
clientInformation: () => clientInformation,
|
|
330
|
-
saveClientInformation: (ci) => {
|
|
331
|
-
clientInformation = ci;
|
|
332
|
-
},
|
|
333
|
-
tokens: () => void 0,
|
|
334
|
-
saveTokens: (t) => {
|
|
335
|
-
tokens = t;
|
|
336
|
-
},
|
|
337
|
-
redirectToAuthorization: () => {
|
|
338
|
-
throw new Error("Unexpected redirect during code exchange");
|
|
339
|
-
},
|
|
340
|
-
saveCodeVerifier: () => void 0,
|
|
341
|
-
codeVerifier: () => session.codeVerifier,
|
|
342
|
-
saveDiscoveryState: (s) => {
|
|
343
|
-
discoveryState = s;
|
|
344
|
-
},
|
|
345
|
-
discoveryState: () => discoveryState
|
|
346
|
-
};
|
|
347
|
-
const result = yield* callAuth(provider, {
|
|
348
|
-
serverUrl: session.endpoint,
|
|
349
|
-
authorizationCode: input.code
|
|
350
|
-
});
|
|
351
|
-
if (result !== "AUTHORIZED" || !tokens) {
|
|
352
|
-
return yield* new McpOAuthError({
|
|
353
|
-
message: "OAuth exchange did not produce tokens"
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
tokens,
|
|
358
|
-
...extractDiscoveryState(discoveryState, clientInformation)
|
|
359
|
-
};
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// src/sdk/discover.ts
|
|
363
|
-
import { Effect as Effect4 } from "effect";
|
|
364
|
-
|
|
365
|
-
// src/sdk/manifest.ts
|
|
366
|
-
import { Schema as Schema4 } from "effect";
|
|
367
|
-
var ListedTool = Schema4.Struct({
|
|
368
|
-
name: Schema4.String,
|
|
369
|
-
description: Schema4.optional(Schema4.NullOr(Schema4.String)),
|
|
370
|
-
inputSchema: Schema4.optional(Schema4.Unknown),
|
|
371
|
-
parameters: Schema4.optional(Schema4.Unknown),
|
|
372
|
-
outputSchema: Schema4.optional(Schema4.Unknown)
|
|
373
|
-
});
|
|
374
|
-
var ListToolsResult = Schema4.Struct({
|
|
375
|
-
tools: Schema4.Array(ListedTool)
|
|
376
|
-
});
|
|
377
|
-
var ServerInfo = Schema4.Struct({
|
|
378
|
-
name: Schema4.optional(Schema4.String),
|
|
379
|
-
version: Schema4.optional(Schema4.String)
|
|
380
|
-
});
|
|
381
|
-
var decodeListToolsResult = Schema4.decodeUnknownOption(ListToolsResult);
|
|
382
|
-
var decodeServerInfo = Schema4.decodeUnknownOption(ServerInfo);
|
|
383
|
-
var sanitize = (value) => {
|
|
384
|
-
const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
385
|
-
return s || "tool";
|
|
386
|
-
};
|
|
387
|
-
var uniqueId = (value, seen) => {
|
|
388
|
-
const base = sanitize(value);
|
|
389
|
-
const n = (seen.get(base) ?? 0) + 1;
|
|
390
|
-
seen.set(base, n);
|
|
391
|
-
return n === 1 ? base : `${base}_${n}`;
|
|
392
|
-
};
|
|
393
|
-
var joinToolPath = (namespace, toolId) => namespace?.trim() ? `${namespace}.${toolId}` : toolId;
|
|
394
|
-
var extractManifestFromListToolsResult = (listToolsResult, metadata) => {
|
|
395
|
-
const seen = /* @__PURE__ */ new Map();
|
|
396
|
-
const listed = decodeListToolsResult(listToolsResult).pipe(
|
|
397
|
-
(opt) => opt._tag === "Some" ? opt.value.tools : []
|
|
398
|
-
);
|
|
399
|
-
const server = decodeServerInfo(metadata?.serverInfo).pipe(
|
|
400
|
-
(opt) => opt._tag === "Some" ? { name: opt.value.name ?? null, version: opt.value.version ?? null } : null
|
|
401
|
-
);
|
|
402
|
-
const tools = listed.flatMap((tool) => {
|
|
403
|
-
const toolName = tool.name.trim();
|
|
404
|
-
if (!toolName) return [];
|
|
405
|
-
return [
|
|
406
|
-
{
|
|
407
|
-
toolId: uniqueId(toolName, seen),
|
|
408
|
-
toolName,
|
|
409
|
-
description: tool.description ?? null,
|
|
410
|
-
inputSchema: tool.inputSchema ?? tool.parameters,
|
|
411
|
-
outputSchema: tool.outputSchema
|
|
412
|
-
}
|
|
413
|
-
];
|
|
414
|
-
});
|
|
415
|
-
return { server, tools };
|
|
416
|
-
};
|
|
417
|
-
var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
418
|
-
var hostnameOf = (url) => {
|
|
419
|
-
try {
|
|
420
|
-
return new URL(url).hostname;
|
|
421
|
-
} catch {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
var basenameOf = (path) => path.trim().split(/[\\/]/).pop() ?? path.trim();
|
|
426
|
-
var deriveMcpNamespace = (input) => {
|
|
427
|
-
if (input.name?.trim()) return slugify(input.name) || "mcp";
|
|
428
|
-
const fromEndpoint = input.endpoint?.trim() ? hostnameOf(input.endpoint) : null;
|
|
429
|
-
if (fromEndpoint) return slugify(fromEndpoint) || "mcp";
|
|
430
|
-
if (input.command?.trim()) return slugify(basenameOf(input.command)) || "mcp";
|
|
431
|
-
return "mcp";
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
// src/sdk/discover.ts
|
|
435
|
-
var discoverTools = (connector) => Effect4.gen(function* () {
|
|
436
|
-
const connection = yield* connector.pipe(
|
|
437
|
-
Effect4.mapError(
|
|
438
|
-
(err) => new McpToolDiscoveryError({
|
|
439
|
-
stage: "connect",
|
|
440
|
-
message: `Failed connecting to MCP server: ${err.message}`
|
|
441
|
-
})
|
|
442
|
-
)
|
|
443
|
-
);
|
|
444
|
-
const listResult = yield* Effect4.tryPromise({
|
|
445
|
-
try: () => connection.client.listTools(),
|
|
446
|
-
catch: (cause) => new McpToolDiscoveryError({
|
|
447
|
-
stage: "list_tools",
|
|
448
|
-
message: `Failed listing MCP tools: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
449
|
-
})
|
|
450
|
-
});
|
|
451
|
-
const manifest = extractManifestFromListToolsResult(listResult, {
|
|
452
|
-
serverInfo: connection.client.getServerVersion?.()
|
|
453
|
-
});
|
|
454
|
-
yield* Effect4.promise(() => connection.close().catch(() => {
|
|
455
|
-
}));
|
|
456
|
-
return manifest;
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// src/sdk/invoke.ts
|
|
460
|
-
import { Effect as Effect5, Schema as Schema5 } from "effect";
|
|
461
|
-
import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
462
|
-
import {
|
|
463
|
-
ToolInvocationResult,
|
|
464
|
-
ToolInvocationError,
|
|
465
|
-
ToolAnnotations,
|
|
466
|
-
ElicitationResponse,
|
|
467
|
-
FormElicitation,
|
|
468
|
-
UrlElicitation
|
|
469
|
-
} from "@executor-js/sdk";
|
|
470
|
-
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
471
|
-
var makeOAuthProvider = (accessToken, tokenType, refreshToken) => ({
|
|
472
|
-
get redirectUrl() {
|
|
473
|
-
return "http://localhost/oauth/callback";
|
|
474
|
-
},
|
|
475
|
-
get clientMetadata() {
|
|
476
|
-
return {
|
|
477
|
-
redirect_uris: ["http://localhost/oauth/callback"],
|
|
478
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
479
|
-
response_types: ["code"],
|
|
480
|
-
token_endpoint_auth_method: "none",
|
|
481
|
-
client_name: "Executor"
|
|
482
|
-
};
|
|
483
|
-
},
|
|
484
|
-
clientInformation: () => void 0,
|
|
485
|
-
saveClientInformation: () => {
|
|
486
|
-
},
|
|
487
|
-
tokens: async () => ({
|
|
488
|
-
access_token: accessToken,
|
|
489
|
-
token_type: tokenType,
|
|
490
|
-
...refreshToken ? { refresh_token: refreshToken } : {}
|
|
491
|
-
}),
|
|
492
|
-
saveTokens: async () => {
|
|
493
|
-
},
|
|
494
|
-
redirectToAuthorization: async () => {
|
|
495
|
-
throw new Error("MCP OAuth re-authorization required");
|
|
496
|
-
},
|
|
497
|
-
saveCodeVerifier: () => {
|
|
498
|
-
},
|
|
499
|
-
codeVerifier: () => {
|
|
500
|
-
throw new Error("No active PKCE verifier");
|
|
501
|
-
},
|
|
502
|
-
saveDiscoveryState: () => {
|
|
503
|
-
},
|
|
504
|
-
discoveryState: () => void 0
|
|
505
|
-
});
|
|
506
|
-
var McpElicitParams = Schema5.Union(
|
|
507
|
-
Schema5.Struct({
|
|
508
|
-
mode: Schema5.Literal("url"),
|
|
509
|
-
message: Schema5.String,
|
|
510
|
-
url: Schema5.String,
|
|
511
|
-
elicitationId: Schema5.optional(Schema5.String),
|
|
512
|
-
id: Schema5.optional(Schema5.String)
|
|
513
|
-
}),
|
|
514
|
-
Schema5.Struct({
|
|
515
|
-
mode: Schema5.optional(Schema5.Literal("form")),
|
|
516
|
-
message: Schema5.String,
|
|
517
|
-
requestedSchema: Schema5.Record({ key: Schema5.String, value: Schema5.Unknown })
|
|
518
|
-
})
|
|
519
|
-
);
|
|
520
|
-
var decodeElicitParams = Schema5.decodeUnknownSync(McpElicitParams);
|
|
521
|
-
var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitation({
|
|
522
|
-
message: params.message,
|
|
523
|
-
url: params.url,
|
|
524
|
-
elicitationId: params.elicitationId ?? params.id ?? ""
|
|
525
|
-
}) : new FormElicitation({
|
|
526
|
-
message: params.message,
|
|
527
|
-
requestedSchema: params.requestedSchema
|
|
528
|
-
});
|
|
529
|
-
var installElicitationHandler = (client, toolId, args, handler) => {
|
|
530
|
-
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
531
|
-
const params = decodeElicitParams(request.params);
|
|
532
|
-
const response = await Effect5.runPromise(
|
|
533
|
-
handler({ toolId, args, request: toElicitationRequest(params) })
|
|
534
|
-
);
|
|
535
|
-
return {
|
|
536
|
-
action: response.action,
|
|
537
|
-
...response.action === "accept" && response.content ? { content: response.content } : {}
|
|
538
|
-
};
|
|
539
|
-
});
|
|
540
|
-
};
|
|
541
|
-
var resolveConnectorInput = (sourceData, secrets, scopeId) => {
|
|
542
|
-
if (sourceData.transport === "stdio") {
|
|
543
|
-
return Effect5.succeed({
|
|
544
|
-
transport: "stdio",
|
|
545
|
-
command: sourceData.command,
|
|
546
|
-
args: sourceData.args,
|
|
547
|
-
env: sourceData.env,
|
|
548
|
-
cwd: sourceData.cwd
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
return Effect5.gen(function* () {
|
|
552
|
-
const headers = { ...sourceData.headers };
|
|
553
|
-
let authProvider;
|
|
554
|
-
const auth2 = sourceData.auth;
|
|
555
|
-
if (auth2.kind === "header") {
|
|
556
|
-
const secretValue = yield* secrets.resolve(auth2.secretId, scopeId).pipe(
|
|
557
|
-
Effect5.mapError(
|
|
558
|
-
() => new ToolInvocationError({
|
|
559
|
-
toolId: "",
|
|
560
|
-
message: `Failed to resolve secret "${auth2.secretId}" for MCP auth`,
|
|
561
|
-
cause: void 0
|
|
562
|
-
})
|
|
563
|
-
)
|
|
564
|
-
);
|
|
565
|
-
headers[auth2.headerName] = auth2.prefix ? `${auth2.prefix}${secretValue}` : secretValue;
|
|
566
|
-
} else if (auth2.kind === "oauth2") {
|
|
567
|
-
const accessToken = yield* secrets.resolve(auth2.accessTokenSecretId, scopeId).pipe(
|
|
568
|
-
Effect5.mapError(
|
|
569
|
-
() => new ToolInvocationError({
|
|
570
|
-
toolId: "",
|
|
571
|
-
message: "Failed to resolve OAuth access token for MCP auth",
|
|
572
|
-
cause: void 0
|
|
573
|
-
})
|
|
574
|
-
)
|
|
575
|
-
);
|
|
576
|
-
let refreshToken;
|
|
577
|
-
if (auth2.refreshTokenSecretId) {
|
|
578
|
-
refreshToken = yield* secrets.resolve(auth2.refreshTokenSecretId, scopeId).pipe(
|
|
579
|
-
Effect5.option,
|
|
580
|
-
Effect5.map((o) => o._tag === "Some" ? o.value : void 0)
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
authProvider = makeOAuthProvider(accessToken, auth2.tokenType ?? "Bearer", refreshToken);
|
|
584
|
-
}
|
|
585
|
-
return {
|
|
586
|
-
transport: "remote",
|
|
587
|
-
endpoint: sourceData.endpoint,
|
|
588
|
-
remoteTransport: sourceData.remoteTransport,
|
|
589
|
-
queryParams: sourceData.queryParams,
|
|
590
|
-
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
591
|
-
authProvider
|
|
592
|
-
};
|
|
593
|
-
});
|
|
594
|
-
};
|
|
595
|
-
var connectionCacheKey = (sourceData) => sourceData.transport === "stdio" ? `stdio:${sourceData.command}` : `remote:${sourceData.endpoint}`;
|
|
596
|
-
var resolveElicitationHandler = (options) => options.onElicitation === "accept-all" ? () => Effect5.succeed(new ElicitationResponse({ action: "accept" })) : options.onElicitation;
|
|
597
|
-
var useMcpConnection = (connection, toolId, toolName, args, handler) => Effect5.gen(function* () {
|
|
598
|
-
installElicitationHandler(connection.client, toolId, args, handler);
|
|
599
|
-
return yield* Effect5.tryPromise({
|
|
600
|
-
try: () => connection.client.callTool({ name: toolName, arguments: args }),
|
|
601
|
-
catch: (cause) => new ToolInvocationError({
|
|
602
|
-
toolId,
|
|
603
|
-
message: `MCP tool call failed for ${toolName}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
604
|
-
cause
|
|
605
|
-
})
|
|
606
|
-
});
|
|
607
|
-
}).pipe(Effect5.withSpan(`mcp.callTool.${toolName}`));
|
|
608
|
-
var makeMcpInvoker = (opts) => {
|
|
609
|
-
const { connectionCache, pendingConnectors } = opts;
|
|
610
|
-
return {
|
|
611
|
-
resolveAnnotations: () => Effect5.succeed(new ToolAnnotations({ requiresApproval: false })),
|
|
612
|
-
invoke: (toolId, args, options) => Effect5.gen(function* () {
|
|
613
|
-
const entry = yield* opts.bindingStore.get(toolId);
|
|
614
|
-
if (!entry) {
|
|
615
|
-
return yield* new ToolInvocationError({
|
|
616
|
-
toolId,
|
|
617
|
-
message: `No MCP binding found for tool "${toolId}"`,
|
|
618
|
-
cause: void 0
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
const { binding, sourceData } = entry;
|
|
622
|
-
const cacheKey = connectionCacheKey(sourceData);
|
|
623
|
-
const connector = resolveConnectorInput(sourceData, opts.secrets, opts.scopeId).pipe(
|
|
624
|
-
Effect5.flatMap((ci) => createMcpConnector(ci)),
|
|
625
|
-
Effect5.mapError(
|
|
626
|
-
(err) => new McpConnectionError({
|
|
627
|
-
transport: "auto",
|
|
628
|
-
message: err instanceof Error ? err.message : String(err)
|
|
629
|
-
})
|
|
630
|
-
)
|
|
631
|
-
);
|
|
632
|
-
pendingConnectors.set(cacheKey, connector);
|
|
633
|
-
const connection = yield* connectionCache.get(cacheKey).pipe(
|
|
634
|
-
Effect5.mapError(
|
|
635
|
-
(err) => new ToolInvocationError({
|
|
636
|
-
toolId,
|
|
637
|
-
message: `Failed connecting to MCP server: ${err instanceof Error ? err.message : String(err)}`,
|
|
638
|
-
cause: err
|
|
639
|
-
})
|
|
640
|
-
)
|
|
641
|
-
);
|
|
642
|
-
const elicitationHandler = resolveElicitationHandler(options);
|
|
643
|
-
return yield* useMcpConnection(
|
|
644
|
-
connection,
|
|
645
|
-
toolId,
|
|
646
|
-
binding.toolName,
|
|
647
|
-
asRecord(args),
|
|
648
|
-
elicitationHandler
|
|
649
|
-
).pipe(
|
|
650
|
-
// On failure, invalidate the cached connection and retry once
|
|
651
|
-
Effect5.catchAll(
|
|
652
|
-
() => Effect5.gen(function* () {
|
|
653
|
-
yield* connectionCache.invalidate(cacheKey);
|
|
654
|
-
pendingConnectors.set(cacheKey, connector);
|
|
655
|
-
const freshConnection = yield* connectionCache.get(cacheKey).pipe(
|
|
656
|
-
Effect5.mapError(
|
|
657
|
-
(retryErr) => new ToolInvocationError({
|
|
658
|
-
toolId,
|
|
659
|
-
message: `Failed reconnecting: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
|
|
660
|
-
cause: retryErr
|
|
661
|
-
})
|
|
662
|
-
)
|
|
663
|
-
);
|
|
664
|
-
return yield* useMcpConnection(
|
|
665
|
-
freshConnection,
|
|
666
|
-
toolId,
|
|
667
|
-
binding.toolName,
|
|
668
|
-
asRecord(args),
|
|
669
|
-
elicitationHandler
|
|
670
|
-
);
|
|
671
|
-
})
|
|
672
|
-
)
|
|
673
|
-
);
|
|
674
|
-
}).pipe(
|
|
675
|
-
Effect5.scoped,
|
|
676
|
-
Effect5.map((callResult) => {
|
|
677
|
-
const resultRecord = asRecord(callResult);
|
|
678
|
-
const isError = resultRecord.isError === true;
|
|
679
|
-
return new ToolInvocationResult({
|
|
680
|
-
data: isError ? null : callResult ?? null,
|
|
681
|
-
error: isError ? callResult : null
|
|
682
|
-
});
|
|
683
|
-
}),
|
|
684
|
-
Effect5.catchAll((err) => {
|
|
685
|
-
if (typeof err === "object" && err !== null && "_tag" in err && err._tag === "ToolInvocationError") {
|
|
686
|
-
return Effect5.fail(err);
|
|
687
|
-
}
|
|
688
|
-
return Effect5.fail(
|
|
689
|
-
new ToolInvocationError({
|
|
690
|
-
toolId,
|
|
691
|
-
message: `MCP invocation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
692
|
-
cause: err
|
|
693
|
-
})
|
|
694
|
-
);
|
|
695
|
-
})
|
|
696
|
-
),
|
|
697
|
-
closeConnections: () => Effect5.sync(() => {
|
|
698
|
-
pendingConnectors.clear();
|
|
699
|
-
}).pipe(Effect5.flatMap(() => connectionCache.invalidateAll))
|
|
700
|
-
};
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
// src/sdk/plugin.ts
|
|
704
|
-
var toRegistration = (entry, namespace) => ({
|
|
705
|
-
id: ToolId.make(joinToolPath(namespace, entry.toolId)),
|
|
706
|
-
pluginKey: "mcp",
|
|
707
|
-
sourceId: namespace,
|
|
708
|
-
name: entry.toolName,
|
|
709
|
-
description: entry.description ?? `MCP tool: ${entry.toolName}`,
|
|
710
|
-
inputSchema: entry.inputSchema,
|
|
711
|
-
outputSchema: entry.outputSchema
|
|
712
|
-
});
|
|
713
|
-
var toBinding = (entry) => new McpToolBinding({
|
|
714
|
-
toolId: entry.toolId,
|
|
715
|
-
toolName: entry.toolName,
|
|
716
|
-
description: entry.description,
|
|
717
|
-
inputSchema: entry.inputSchema,
|
|
718
|
-
outputSchema: entry.outputSchema
|
|
719
|
-
});
|
|
720
|
-
var toStoredSourceData = (config) => {
|
|
721
|
-
if (config.transport === "stdio") {
|
|
722
|
-
return {
|
|
723
|
-
transport: "stdio",
|
|
724
|
-
command: config.command,
|
|
725
|
-
args: config.args,
|
|
726
|
-
env: config.env,
|
|
727
|
-
cwd: config.cwd
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
return {
|
|
731
|
-
transport: "remote",
|
|
732
|
-
endpoint: config.endpoint,
|
|
733
|
-
remoteTransport: config.remoteTransport ?? "auto",
|
|
734
|
-
queryParams: config.queryParams,
|
|
735
|
-
headers: config.headers,
|
|
736
|
-
auth: config.auth ?? { kind: "none" }
|
|
737
|
-
};
|
|
738
|
-
};
|
|
739
|
-
var normalizeNamespace = (config) => config.namespace ?? deriveMcpNamespace({
|
|
740
|
-
name: config.name,
|
|
741
|
-
endpoint: config.transport === "remote" ? config.endpoint : void 0,
|
|
742
|
-
command: config.transport === "stdio" ? config.command : void 0
|
|
743
|
-
});
|
|
744
|
-
var makeOAuthProvider2 = (accessToken, tokenType, refreshToken) => ({
|
|
745
|
-
get redirectUrl() {
|
|
746
|
-
return "http://localhost/oauth/callback";
|
|
747
|
-
},
|
|
748
|
-
get clientMetadata() {
|
|
749
|
-
return {
|
|
750
|
-
redirect_uris: ["http://localhost/oauth/callback"],
|
|
751
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
752
|
-
response_types: ["code"],
|
|
753
|
-
token_endpoint_auth_method: "none",
|
|
754
|
-
client_name: "Executor"
|
|
755
|
-
};
|
|
756
|
-
},
|
|
757
|
-
clientInformation: () => void 0,
|
|
758
|
-
saveClientInformation: () => {
|
|
759
|
-
},
|
|
760
|
-
tokens: async () => ({
|
|
761
|
-
access_token: accessToken,
|
|
762
|
-
token_type: tokenType,
|
|
763
|
-
...refreshToken ? { refresh_token: refreshToken } : {}
|
|
764
|
-
}),
|
|
765
|
-
saveTokens: async () => {
|
|
766
|
-
},
|
|
767
|
-
redirectToAuthorization: async () => {
|
|
768
|
-
throw new Error("MCP OAuth re-authorization required");
|
|
769
|
-
},
|
|
770
|
-
saveCodeVerifier: () => {
|
|
771
|
-
},
|
|
772
|
-
codeVerifier: () => {
|
|
773
|
-
throw new Error("No active PKCE verifier");
|
|
774
|
-
},
|
|
775
|
-
saveDiscoveryState: () => {
|
|
776
|
-
},
|
|
777
|
-
discoveryState: () => void 0
|
|
778
|
-
});
|
|
779
|
-
var remoteConnectionError = (message) => new McpConnectionError({ transport: "remote", message });
|
|
780
|
-
var mcpOAuthError = (message) => new McpOAuthError({ message });
|
|
781
|
-
var mcpDiscoveryError = (message) => new McpToolDiscoveryError({ stage: "list_tools", message });
|
|
782
|
-
var mcpPlugin = (options) => {
|
|
783
|
-
const bindingStore = options?.bindingStore ?? makeInMemoryBindingStore();
|
|
784
|
-
const addedSources = /* @__PURE__ */ new Map();
|
|
785
|
-
const oauthSessions = /* @__PURE__ */ new Map();
|
|
786
|
-
return definePlugin({
|
|
787
|
-
key: "mcp",
|
|
788
|
-
init: (ctx) => Effect6.gen(function* () {
|
|
789
|
-
const cacheScope = yield* Scope.make();
|
|
790
|
-
const pendingConnectors = /* @__PURE__ */ new Map();
|
|
791
|
-
const connectionCache = yield* ScopedCache.make({
|
|
792
|
-
lookup: (key) => Effect6.acquireRelease(
|
|
793
|
-
Effect6.suspend(() => {
|
|
794
|
-
const connector = pendingConnectors.get(key);
|
|
795
|
-
if (!connector) {
|
|
796
|
-
return Effect6.fail(
|
|
797
|
-
new McpConnectionError({
|
|
798
|
-
transport: "auto",
|
|
799
|
-
message: `No pending connector for key: ${key}`
|
|
800
|
-
})
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
return connector;
|
|
804
|
-
}),
|
|
805
|
-
(connection) => Effect6.promise(() => connection.close().catch(() => {
|
|
806
|
-
}))
|
|
807
|
-
),
|
|
808
|
-
capacity: 64,
|
|
809
|
-
timeToLive: Duration.minutes(5)
|
|
810
|
-
}).pipe(Scope.extend(cacheScope));
|
|
811
|
-
const invoker = makeMcpInvoker({
|
|
812
|
-
bindingStore,
|
|
813
|
-
secrets: ctx.secrets,
|
|
814
|
-
scopeId: ctx.scope.id,
|
|
815
|
-
connectionCache,
|
|
816
|
-
pendingConnectors
|
|
817
|
-
});
|
|
818
|
-
yield* ctx.tools.registerInvoker("mcp", invoker);
|
|
819
|
-
const savedSources = yield* bindingStore.listSources();
|
|
820
|
-
for (const s of savedSources) {
|
|
821
|
-
const isRemote = s.config.transport === "remote";
|
|
822
|
-
addedSources.set(
|
|
823
|
-
s.namespace,
|
|
824
|
-
new Source({
|
|
825
|
-
id: s.namespace,
|
|
826
|
-
name: s.name,
|
|
827
|
-
kind: "mcp",
|
|
828
|
-
canEdit: isRemote
|
|
829
|
-
})
|
|
830
|
-
);
|
|
831
|
-
}
|
|
832
|
-
const resolveConnectorInput2 = (sd) => {
|
|
833
|
-
if (sd.transport === "stdio") {
|
|
834
|
-
return Effect6.succeed({
|
|
835
|
-
transport: "stdio",
|
|
836
|
-
command: sd.command,
|
|
837
|
-
args: sd.args,
|
|
838
|
-
env: sd.env,
|
|
839
|
-
cwd: sd.cwd
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
return Effect6.gen(function* () {
|
|
843
|
-
const headers = {
|
|
844
|
-
...sd.headers
|
|
845
|
-
};
|
|
846
|
-
let authProvider;
|
|
847
|
-
const auth2 = sd.auth;
|
|
848
|
-
if (auth2.kind === "header") {
|
|
849
|
-
const val = yield* ctx.secrets.resolve(SecretId.make(auth2.secretId), ctx.scope.id).pipe(
|
|
850
|
-
Effect6.mapError(
|
|
851
|
-
() => remoteConnectionError(`Failed to resolve secret "${auth2.secretId}"`)
|
|
852
|
-
)
|
|
853
|
-
);
|
|
854
|
-
headers[auth2.headerName] = auth2.prefix ? `${auth2.prefix}${val}` : val;
|
|
855
|
-
} else if (auth2.kind === "oauth2") {
|
|
856
|
-
const accessToken = yield* ctx.secrets.resolve(SecretId.make(auth2.accessTokenSecretId), ctx.scope.id).pipe(
|
|
857
|
-
Effect6.mapError(
|
|
858
|
-
() => remoteConnectionError("Failed to resolve OAuth access token")
|
|
859
|
-
)
|
|
860
|
-
);
|
|
861
|
-
let refreshToken;
|
|
862
|
-
if (auth2.refreshTokenSecretId) {
|
|
863
|
-
refreshToken = yield* ctx.secrets.resolve(SecretId.make(auth2.refreshTokenSecretId), ctx.scope.id).pipe(
|
|
864
|
-
Effect6.option,
|
|
865
|
-
Effect6.map((o) => o._tag === "Some" ? o.value : void 0)
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
authProvider = makeOAuthProvider2(
|
|
869
|
-
accessToken,
|
|
870
|
-
auth2.tokenType ?? "Bearer",
|
|
871
|
-
refreshToken
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
return {
|
|
875
|
-
transport: "remote",
|
|
876
|
-
endpoint: sd.endpoint,
|
|
877
|
-
remoteTransport: sd.remoteTransport,
|
|
878
|
-
queryParams: sd.queryParams,
|
|
879
|
-
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
880
|
-
authProvider
|
|
881
|
-
};
|
|
882
|
-
});
|
|
883
|
-
};
|
|
884
|
-
yield* ctx.sources.addManager({
|
|
885
|
-
kind: "mcp",
|
|
886
|
-
list: () => Effect6.sync(() => [...addedSources.values()]),
|
|
887
|
-
remove: (sourceId) => Effect6.gen(function* () {
|
|
888
|
-
yield* bindingStore.removeByNamespace(sourceId);
|
|
889
|
-
yield* bindingStore.removeSource(sourceId);
|
|
890
|
-
yield* ctx.tools.unregisterBySource(sourceId);
|
|
891
|
-
addedSources.delete(sourceId);
|
|
892
|
-
}),
|
|
893
|
-
detect: (url) => Effect6.gen(function* () {
|
|
894
|
-
const trimmed = url.trim();
|
|
895
|
-
if (!trimmed) return null;
|
|
896
|
-
const parsed = yield* Effect6.try(() => new URL(trimmed)).pipe(Effect6.option);
|
|
897
|
-
if (parsed._tag === "None") return null;
|
|
898
|
-
const name = parsed.value.hostname || "mcp";
|
|
899
|
-
const namespace = deriveMcpNamespace({ endpoint: trimmed });
|
|
900
|
-
const connector = createMcpConnector({
|
|
901
|
-
transport: "remote",
|
|
902
|
-
endpoint: trimmed
|
|
903
|
-
});
|
|
904
|
-
const connected = yield* discoverTools(connector).pipe(
|
|
905
|
-
Effect6.map(() => true),
|
|
906
|
-
Effect6.catchAll(() => Effect6.succeed(false))
|
|
907
|
-
);
|
|
908
|
-
if (connected) {
|
|
909
|
-
return new SourceDetectionResult({
|
|
910
|
-
kind: "mcp",
|
|
911
|
-
confidence: "high",
|
|
912
|
-
endpoint: trimmed,
|
|
913
|
-
name,
|
|
914
|
-
namespace
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
const hasOAuth = yield* startMcpOAuthAuthorization({
|
|
918
|
-
endpoint: trimmed,
|
|
919
|
-
redirectUrl: "http://127.0.0.1/executor/discovery/oauth/probe",
|
|
920
|
-
state: "probe"
|
|
921
|
-
}).pipe(
|
|
922
|
-
Effect6.map(() => true),
|
|
923
|
-
Effect6.catchAll(() => Effect6.succeed(false))
|
|
924
|
-
);
|
|
925
|
-
if (hasOAuth) {
|
|
926
|
-
return new SourceDetectionResult({
|
|
927
|
-
kind: "mcp",
|
|
928
|
-
confidence: "high",
|
|
929
|
-
endpoint: trimmed,
|
|
930
|
-
name,
|
|
931
|
-
namespace
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
return null;
|
|
935
|
-
}),
|
|
936
|
-
refresh: (sourceId) => Effect6.gen(function* () {
|
|
937
|
-
const sd = yield* bindingStore.getSourceConfig(sourceId);
|
|
938
|
-
if (!sd || !addedSources.has(sourceId)) return;
|
|
939
|
-
const ci = yield* resolveConnectorInput2(sd).pipe(
|
|
940
|
-
Effect6.catchAll(() => Effect6.succeed(null))
|
|
941
|
-
);
|
|
942
|
-
if (!ci) return;
|
|
943
|
-
const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
|
|
944
|
-
Effect6.catchAll(() => Effect6.succeed(null))
|
|
945
|
-
);
|
|
946
|
-
if (!manifest) return;
|
|
947
|
-
const oldIds = yield* bindingStore.removeByNamespace(sourceId);
|
|
948
|
-
if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
|
|
949
|
-
yield* Effect6.forEach(
|
|
950
|
-
manifest.tools,
|
|
951
|
-
(e) => bindingStore.put(
|
|
952
|
-
ToolId.make(joinToolPath(sourceId, e.toolId)),
|
|
953
|
-
sourceId,
|
|
954
|
-
toBinding(e),
|
|
955
|
-
sd
|
|
956
|
-
),
|
|
957
|
-
{ discard: true }
|
|
958
|
-
);
|
|
959
|
-
yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, sourceId)));
|
|
960
|
-
})
|
|
961
|
-
});
|
|
962
|
-
const probeEndpoint = (endpoint) => Effect6.gen(function* () {
|
|
963
|
-
const trimmed = endpoint.trim();
|
|
964
|
-
if (!trimmed) return yield* remoteConnectionError("Endpoint URL is required");
|
|
965
|
-
const name = yield* Effect6.try(() => new URL(trimmed).hostname).pipe(
|
|
966
|
-
Effect6.orElseSucceed(() => "mcp")
|
|
967
|
-
);
|
|
968
|
-
const namespace = deriveMcpNamespace({ endpoint: trimmed });
|
|
969
|
-
const connector = createMcpConnector({
|
|
970
|
-
transport: "remote",
|
|
971
|
-
endpoint: trimmed
|
|
972
|
-
});
|
|
973
|
-
const result = yield* discoverTools(connector).pipe(
|
|
974
|
-
Effect6.map((m) => ({ ok: true, manifest: m })),
|
|
975
|
-
Effect6.catchAll(() => Effect6.succeed({ ok: false, manifest: null }))
|
|
976
|
-
);
|
|
977
|
-
if (result.ok && result.manifest) {
|
|
978
|
-
return {
|
|
979
|
-
connected: true,
|
|
980
|
-
requiresOAuth: false,
|
|
981
|
-
name: result.manifest.server?.name ?? name,
|
|
982
|
-
namespace,
|
|
983
|
-
toolCount: result.manifest.tools.length,
|
|
984
|
-
serverName: result.manifest.server?.name ?? null
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
const hasOAuth = yield* startMcpOAuthAuthorization({
|
|
988
|
-
endpoint: trimmed,
|
|
989
|
-
redirectUrl: "http://127.0.0.1/executor/discovery/oauth/probe",
|
|
990
|
-
state: "probe"
|
|
991
|
-
}).pipe(
|
|
992
|
-
Effect6.map(() => true),
|
|
993
|
-
Effect6.catchAll(() => Effect6.succeed(false))
|
|
994
|
-
);
|
|
995
|
-
if (hasOAuth) {
|
|
996
|
-
return {
|
|
997
|
-
connected: false,
|
|
998
|
-
requiresOAuth: true,
|
|
999
|
-
name,
|
|
1000
|
-
namespace,
|
|
1001
|
-
toolCount: null,
|
|
1002
|
-
serverName: null
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
return yield* remoteConnectionError(
|
|
1006
|
-
"Could not connect to MCP endpoint and no OAuth was detected"
|
|
1007
|
-
);
|
|
1008
|
-
});
|
|
1009
|
-
const addSource = (config) => Effect6.gen(function* () {
|
|
1010
|
-
const namespace = normalizeNamespace(config);
|
|
1011
|
-
const sd = toStoredSourceData(config);
|
|
1012
|
-
const ci = yield* resolveConnectorInput2(sd);
|
|
1013
|
-
const connector = createMcpConnector(ci);
|
|
1014
|
-
const manifest = yield* discoverTools(connector).pipe(
|
|
1015
|
-
Effect6.mapError((err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`))
|
|
1016
|
-
);
|
|
1017
|
-
const registrations = manifest.tools.map((e) => toRegistration(e, namespace));
|
|
1018
|
-
yield* Effect6.forEach(
|
|
1019
|
-
manifest.tools,
|
|
1020
|
-
(e) => bindingStore.put(
|
|
1021
|
-
ToolId.make(joinToolPath(namespace, e.toolId)),
|
|
1022
|
-
namespace,
|
|
1023
|
-
toBinding(e),
|
|
1024
|
-
sd
|
|
1025
|
-
),
|
|
1026
|
-
{ discard: true }
|
|
1027
|
-
);
|
|
1028
|
-
yield* ctx.tools.register(registrations);
|
|
1029
|
-
const sourceName = manifest.server?.name ?? config.name ?? namespace;
|
|
1030
|
-
yield* bindingStore.putSource({
|
|
1031
|
-
namespace,
|
|
1032
|
-
name: sourceName,
|
|
1033
|
-
config: sd
|
|
1034
|
-
});
|
|
1035
|
-
addedSources.set(
|
|
1036
|
-
namespace,
|
|
1037
|
-
new Source({
|
|
1038
|
-
id: namespace,
|
|
1039
|
-
name: sourceName,
|
|
1040
|
-
kind: "mcp",
|
|
1041
|
-
canEdit: config.transport === "remote"
|
|
1042
|
-
})
|
|
1043
|
-
);
|
|
1044
|
-
return { toolCount: registrations.length, namespace };
|
|
1045
|
-
});
|
|
1046
|
-
const removeSource = (namespace) => Effect6.gen(function* () {
|
|
1047
|
-
const ids = yield* bindingStore.removeByNamespace(namespace);
|
|
1048
|
-
if (ids.length > 0) yield* ctx.tools.unregister(ids);
|
|
1049
|
-
yield* bindingStore.removeSource(namespace);
|
|
1050
|
-
addedSources.delete(namespace);
|
|
1051
|
-
});
|
|
1052
|
-
const refreshSource = (namespace) => Effect6.gen(function* () {
|
|
1053
|
-
const sd = yield* bindingStore.getSourceConfig(namespace);
|
|
1054
|
-
if (!sd)
|
|
1055
|
-
return yield* remoteConnectionError(`No stored config for MCP source "${namespace}"`);
|
|
1056
|
-
const ci = yield* resolveConnectorInput2(sd);
|
|
1057
|
-
const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
|
|
1058
|
-
Effect6.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`))
|
|
1059
|
-
);
|
|
1060
|
-
const oldIds = yield* bindingStore.removeByNamespace(namespace);
|
|
1061
|
-
if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
|
|
1062
|
-
yield* Effect6.forEach(
|
|
1063
|
-
manifest.tools,
|
|
1064
|
-
(e) => bindingStore.put(
|
|
1065
|
-
ToolId.make(joinToolPath(namespace, e.toolId)),
|
|
1066
|
-
namespace,
|
|
1067
|
-
toBinding(e),
|
|
1068
|
-
sd
|
|
1069
|
-
),
|
|
1070
|
-
{ discard: true }
|
|
1071
|
-
);
|
|
1072
|
-
yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, namespace)));
|
|
1073
|
-
return { toolCount: manifest.tools.length };
|
|
1074
|
-
});
|
|
1075
|
-
const startOAuth = (input) => Effect6.gen(function* () {
|
|
1076
|
-
const endpoint = input.endpoint.trim();
|
|
1077
|
-
if (!endpoint) return yield* mcpOAuthError("MCP OAuth requires an endpoint");
|
|
1078
|
-
let fullEndpoint = endpoint;
|
|
1079
|
-
if (input.queryParams && Object.keys(input.queryParams).length > 0) {
|
|
1080
|
-
const u = new URL(endpoint);
|
|
1081
|
-
for (const [k, v] of Object.entries(input.queryParams)) u.searchParams.set(k, v);
|
|
1082
|
-
fullEndpoint = u.toString();
|
|
1083
|
-
}
|
|
1084
|
-
const sessionId = `mcp_oauth_${crypto.randomUUID()}`;
|
|
1085
|
-
const started = yield* startMcpOAuthAuthorization({
|
|
1086
|
-
endpoint: fullEndpoint,
|
|
1087
|
-
redirectUrl: input.redirectUrl,
|
|
1088
|
-
state: sessionId
|
|
1089
|
-
}).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth start failed: ${e.message}`)));
|
|
1090
|
-
oauthSessions.set(sessionId, {
|
|
1091
|
-
endpoint: fullEndpoint,
|
|
1092
|
-
redirectUrl: input.redirectUrl,
|
|
1093
|
-
codeVerifier: started.codeVerifier,
|
|
1094
|
-
resourceMetadataUrl: started.resourceMetadataUrl,
|
|
1095
|
-
authorizationServerUrl: started.authorizationServerUrl,
|
|
1096
|
-
resourceMetadata: started.resourceMetadata,
|
|
1097
|
-
authorizationServerMetadata: started.authorizationServerMetadata,
|
|
1098
|
-
clientInformation: started.clientInformation
|
|
1099
|
-
});
|
|
1100
|
-
return {
|
|
1101
|
-
sessionId,
|
|
1102
|
-
authorizationUrl: started.authorizationUrl
|
|
1103
|
-
};
|
|
1104
|
-
});
|
|
1105
|
-
const completeOAuth = (input) => Effect6.gen(function* () {
|
|
1106
|
-
if (input.error) return yield* mcpOAuthError(`OAuth error: ${input.error}`);
|
|
1107
|
-
if (!input.code) return yield* mcpOAuthError("Missing OAuth authorization code");
|
|
1108
|
-
const session = oauthSessions.get(input.state);
|
|
1109
|
-
if (!session) return yield* mcpOAuthError(`OAuth session not found: ${input.state}`);
|
|
1110
|
-
const exchanged = yield* exchangeMcpOAuthCode({
|
|
1111
|
-
session,
|
|
1112
|
-
code: input.code
|
|
1113
|
-
}).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth exchange failed: ${e.message}`)));
|
|
1114
|
-
const accessTokenRef = yield* ctx.secrets.set({
|
|
1115
|
-
id: SecretId.make(`mcp-oauth-access-${input.state}`),
|
|
1116
|
-
scopeId: ctx.scope.id,
|
|
1117
|
-
name: "MCP OAuth Access Token",
|
|
1118
|
-
value: exchanged.tokens.access_token,
|
|
1119
|
-
purpose: "oauth_access_token"
|
|
1120
|
-
}).pipe(
|
|
1121
|
-
Effect6.mapError((e) => mcpOAuthError(`Failed to store access token: ${String(e)}`))
|
|
1122
|
-
);
|
|
1123
|
-
let refreshTokenSecretId = null;
|
|
1124
|
-
if (exchanged.tokens.refresh_token) {
|
|
1125
|
-
const ref = yield* ctx.secrets.set({
|
|
1126
|
-
id: SecretId.make(`mcp-oauth-refresh-${input.state}`),
|
|
1127
|
-
scopeId: ctx.scope.id,
|
|
1128
|
-
name: "MCP OAuth Refresh Token",
|
|
1129
|
-
value: exchanged.tokens.refresh_token,
|
|
1130
|
-
purpose: "oauth_refresh_token"
|
|
1131
|
-
}).pipe(
|
|
1132
|
-
Effect6.mapError(
|
|
1133
|
-
(e) => mcpOAuthError(`Failed to store refresh token: ${String(e)}`)
|
|
1134
|
-
)
|
|
1135
|
-
);
|
|
1136
|
-
refreshTokenSecretId = ref.id;
|
|
1137
|
-
}
|
|
1138
|
-
oauthSessions.delete(input.state);
|
|
1139
|
-
const expiresAt = typeof exchanged.tokens.expires_in === "number" ? Date.now() + exchanged.tokens.expires_in * 1e3 : null;
|
|
1140
|
-
return {
|
|
1141
|
-
accessTokenSecretId: accessTokenRef.id,
|
|
1142
|
-
refreshTokenSecretId,
|
|
1143
|
-
tokenType: exchanged.tokens.token_type ?? "Bearer",
|
|
1144
|
-
expiresAt,
|
|
1145
|
-
scope: exchanged.tokens.scope ?? null
|
|
1146
|
-
};
|
|
1147
|
-
});
|
|
1148
|
-
const updateSource = (namespace, input) => Effect6.gen(function* () {
|
|
1149
|
-
const existingConfig = yield* bindingStore.getSourceConfig(namespace);
|
|
1150
|
-
if (!existingConfig || existingConfig.transport !== "remote") return;
|
|
1151
|
-
const remote = existingConfig;
|
|
1152
|
-
const updatedConfig = {
|
|
1153
|
-
...remote,
|
|
1154
|
-
...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
|
|
1155
|
-
...input.headers !== void 0 ? { headers: input.headers } : {},
|
|
1156
|
-
...input.auth !== void 0 ? { auth: input.auth } : {},
|
|
1157
|
-
...input.queryParams !== void 0 ? { queryParams: input.queryParams } : {}
|
|
1158
|
-
};
|
|
1159
|
-
const sources = yield* bindingStore.listSources();
|
|
1160
|
-
const existingMeta = sources.find((s) => s.namespace === namespace);
|
|
1161
|
-
yield* bindingStore.putSource({
|
|
1162
|
-
namespace,
|
|
1163
|
-
name: existingMeta?.name ?? namespace,
|
|
1164
|
-
config: updatedConfig
|
|
1165
|
-
});
|
|
1166
|
-
const toolIds = yield* bindingStore.listByNamespace(namespace);
|
|
1167
|
-
for (const toolId of toolIds) {
|
|
1168
|
-
const entry = yield* bindingStore.get(toolId);
|
|
1169
|
-
if (entry) {
|
|
1170
|
-
yield* bindingStore.put(toolId, namespace, entry.binding, updatedConfig);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
const getSource = (namespace) => bindingStore.getSource(namespace);
|
|
1175
|
-
return {
|
|
1176
|
-
extension: {
|
|
1177
|
-
probeEndpoint,
|
|
1178
|
-
addSource,
|
|
1179
|
-
removeSource,
|
|
1180
|
-
refreshSource,
|
|
1181
|
-
startOAuth,
|
|
1182
|
-
completeOAuth,
|
|
1183
|
-
getSource,
|
|
1184
|
-
updateSource
|
|
1185
|
-
},
|
|
1186
|
-
close: () => Effect6.gen(function* () {
|
|
1187
|
-
yield* invoker.closeConnections();
|
|
1188
|
-
yield* Scope.close(cacheScope, Exit.void);
|
|
1189
|
-
for (const sourceId of addedSources.keys()) {
|
|
1190
|
-
yield* ctx.tools.unregisterBySource(sourceId);
|
|
1191
|
-
}
|
|
1192
|
-
addedSources.clear();
|
|
1193
|
-
})
|
|
1194
|
-
};
|
|
1195
|
-
})
|
|
1196
|
-
});
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
export {
|
|
1200
|
-
makeKvBindingStore,
|
|
1201
|
-
mcpPlugin
|
|
1202
|
-
};
|
|
1203
|
-
//# sourceMappingURL=chunk-NJ4CITCV.js.map
|