@executor-js/plugin-mcp 0.0.1-beta.5 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/api/group.d.ts +55 -21
- package/dist/api/handlers.test.d.ts +1 -0
- package/dist/{chunk-DR65PT4S.js → chunk-X3JTTDWJ.js} +347 -344
- package/dist/chunk-X3JTTDWJ.js.map +1 -0
- package/dist/core.js +50 -2
- package/dist/core.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/promise.d.ts +1 -1
- package/dist/react/McpSourceSummary.d.ts +1 -1
- package/dist/sdk/binding-store.d.ts +11 -8
- package/dist/sdk/config-file-store.d.ts +10 -0
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/invoke.d.ts +1 -1
- package/dist/sdk/oauth.d.ts +21 -17
- package/dist/sdk/plugin.d.ts +13 -2
- package/dist/sdk/stored-source.d.ts +115 -0
- package/package.json +30 -28
- package/dist/chunk-DR65PT4S.js.map +0 -1
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// src/sdk/binding-store.ts
|
|
2
|
-
import { Effect, Schema as
|
|
3
|
-
import {
|
|
4
|
-
makeInMemoryScopedKv,
|
|
5
|
-
scopeKv
|
|
6
|
-
} from "@executor-js/sdk/core";
|
|
2
|
+
import { Effect as Effect2, Schema as Schema5 } from "effect";
|
|
3
|
+
import { makeInMemoryScopedKv, scopeKv } from "@executor-js/sdk";
|
|
7
4
|
|
|
8
5
|
// src/sdk/types.ts
|
|
9
6
|
import { Schema } from "effect";
|
|
@@ -51,13 +48,8 @@ var McpStdioSourceData = Schema.Struct({
|
|
|
51
48
|
/** Working directory */
|
|
52
49
|
cwd: Schema.optional(Schema.String)
|
|
53
50
|
});
|
|
54
|
-
var McpStoredSourceData = Schema.Union(
|
|
55
|
-
|
|
56
|
-
McpStdioSourceData
|
|
57
|
-
);
|
|
58
|
-
var McpToolBinding = class extends Schema.Class(
|
|
59
|
-
"McpToolBinding"
|
|
60
|
-
)({
|
|
51
|
+
var McpStoredSourceData = Schema.Union(McpRemoteSourceData, McpStdioSourceData);
|
|
52
|
+
var McpToolBinding = class extends Schema.Class("McpToolBinding")({
|
|
61
53
|
toolId: Schema.String,
|
|
62
54
|
toolName: Schema.String,
|
|
63
55
|
description: Schema.NullOr(Schema.String),
|
|
@@ -66,207 +58,58 @@ var McpToolBinding = class extends Schema.Class(
|
|
|
66
58
|
}) {
|
|
67
59
|
};
|
|
68
60
|
|
|
69
|
-
// src/sdk/
|
|
70
|
-
var StoredBindingEntry = Schema2.Struct({
|
|
71
|
-
namespace: Schema2.String,
|
|
72
|
-
binding: McpToolBinding,
|
|
73
|
-
sourceData: Schema2.Unknown
|
|
74
|
-
});
|
|
75
|
-
var encodeBindingEntry = Schema2.encodeSync(
|
|
76
|
-
Schema2.parseJson(StoredBindingEntry)
|
|
77
|
-
);
|
|
78
|
-
var decodeBindingEntry = Schema2.decodeUnknownSync(
|
|
79
|
-
Schema2.parseJson(StoredBindingEntry)
|
|
80
|
-
);
|
|
81
|
-
var makeStore = (bindings, sources) => ({
|
|
82
|
-
// ---- Bindings ----
|
|
83
|
-
get: (toolId) => Effect.gen(function* () {
|
|
84
|
-
const raw = yield* bindings.get(toolId);
|
|
85
|
-
if (!raw) return null;
|
|
86
|
-
const entry = decodeBindingEntry(raw);
|
|
87
|
-
return {
|
|
88
|
-
binding: entry.binding,
|
|
89
|
-
sourceData: entry.sourceData
|
|
90
|
-
};
|
|
91
|
-
}),
|
|
92
|
-
put: (toolId, namespace, binding, sourceData) => bindings.set(
|
|
93
|
-
toolId,
|
|
94
|
-
encodeBindingEntry({ namespace, binding, sourceData })
|
|
95
|
-
),
|
|
96
|
-
remove: (toolId) => bindings.delete(toolId).pipe(Effect.asVoid),
|
|
97
|
-
listByNamespace: (namespace) => Effect.gen(function* () {
|
|
98
|
-
const entries = yield* bindings.list();
|
|
99
|
-
const ids = [];
|
|
100
|
-
for (const e of entries) {
|
|
101
|
-
const entry = decodeBindingEntry(e.value);
|
|
102
|
-
if (entry.namespace === namespace) ids.push(e.key);
|
|
103
|
-
}
|
|
104
|
-
return ids;
|
|
105
|
-
}),
|
|
106
|
-
removeByNamespace: (namespace) => Effect.gen(function* () {
|
|
107
|
-
const entries = yield* bindings.list();
|
|
108
|
-
const ids = [];
|
|
109
|
-
for (const e of entries) {
|
|
110
|
-
const entry = decodeBindingEntry(e.value);
|
|
111
|
-
if (entry.namespace === namespace) {
|
|
112
|
-
ids.push(e.key);
|
|
113
|
-
yield* bindings.delete(e.key);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return ids;
|
|
117
|
-
}),
|
|
118
|
-
// ---- Sources (meta + config combined) ----
|
|
119
|
-
putSource: (source) => sources.set(source.namespace, JSON.stringify(source)),
|
|
120
|
-
removeSource: (namespace) => sources.delete(namespace).pipe(Effect.asVoid),
|
|
121
|
-
listSources: () => Effect.gen(function* () {
|
|
122
|
-
const entries = yield* sources.list();
|
|
123
|
-
return entries.map((e) => JSON.parse(e.value));
|
|
124
|
-
}),
|
|
125
|
-
getSourceConfig: (namespace) => Effect.gen(function* () {
|
|
126
|
-
const raw = yield* sources.get(namespace);
|
|
127
|
-
if (!raw) return null;
|
|
128
|
-
const source = JSON.parse(raw);
|
|
129
|
-
return source.config;
|
|
130
|
-
})
|
|
131
|
-
});
|
|
132
|
-
var makeKvBindingStore = (kv, namespace) => makeStore(
|
|
133
|
-
scopeKv(kv, `${namespace}.bindings`),
|
|
134
|
-
scopeKv(kv, `${namespace}.sources`)
|
|
135
|
-
);
|
|
136
|
-
var makeInMemoryBindingStore = () => makeStore(
|
|
137
|
-
makeInMemoryScopedKv(),
|
|
138
|
-
makeInMemoryScopedKv()
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// src/sdk/plugin.ts
|
|
142
|
-
import { Effect as Effect6, Exit, ScopedCache, Duration, Scope } from "effect";
|
|
61
|
+
// src/sdk/oauth.ts
|
|
143
62
|
import {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
ToolId,
|
|
148
|
-
SecretId
|
|
149
|
-
} from "@executor-js/sdk/core";
|
|
150
|
-
|
|
151
|
-
// src/sdk/connection.ts
|
|
152
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
153
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
154
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
155
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
156
|
-
import { Effect as Effect2 } from "effect";
|
|
63
|
+
auth
|
|
64
|
+
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
65
|
+
import { Effect, Schema as Schema3 } from "effect";
|
|
157
66
|
|
|
158
67
|
// src/sdk/errors.ts
|
|
159
|
-
import { Schema as
|
|
160
|
-
var McpConnectionError = class extends
|
|
68
|
+
import { Schema as Schema2 } from "effect";
|
|
69
|
+
var McpConnectionError = class extends Schema2.TaggedError()(
|
|
161
70
|
"McpConnectionError",
|
|
162
71
|
{
|
|
163
|
-
transport:
|
|
164
|
-
message:
|
|
72
|
+
transport: Schema2.String,
|
|
73
|
+
message: Schema2.String
|
|
165
74
|
}
|
|
166
75
|
) {
|
|
167
76
|
};
|
|
168
|
-
var McpToolDiscoveryError = class extends
|
|
77
|
+
var McpToolDiscoveryError = class extends Schema2.TaggedError()(
|
|
169
78
|
"McpToolDiscoveryError",
|
|
170
79
|
{
|
|
171
|
-
stage:
|
|
172
|
-
message:
|
|
80
|
+
stage: Schema2.Literal("connect", "list_tools"),
|
|
81
|
+
message: Schema2.String
|
|
173
82
|
}
|
|
174
83
|
) {
|
|
175
84
|
};
|
|
176
|
-
var McpInvocationError = class extends
|
|
85
|
+
var McpInvocationError = class extends Schema2.TaggedError()(
|
|
177
86
|
"McpInvocationError",
|
|
178
87
|
{
|
|
179
|
-
toolName:
|
|
180
|
-
message:
|
|
88
|
+
toolName: Schema2.String,
|
|
89
|
+
message: Schema2.String
|
|
181
90
|
}
|
|
182
91
|
) {
|
|
183
92
|
};
|
|
184
|
-
var McpOAuthError = class extends
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
message: Schema3.String
|
|
188
|
-
}
|
|
189
|
-
) {
|
|
93
|
+
var McpOAuthError = class extends Schema2.TaggedError()("McpOAuthError", {
|
|
94
|
+
message: Schema2.String
|
|
95
|
+
}) {
|
|
190
96
|
};
|
|
191
97
|
|
|
192
|
-
// src/sdk/
|
|
193
|
-
var
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
var createClient = () => new Client(
|
|
201
|
-
{ name: "executor-mcp", version: "0.1.0" },
|
|
202
|
-
{ capabilities: { elicitation: { form: {}, url: {} } } }
|
|
203
|
-
);
|
|
204
|
-
var connectionFromClient = (client) => ({
|
|
205
|
-
client,
|
|
206
|
-
close: () => client.close()
|
|
98
|
+
// src/sdk/oauth.ts
|
|
99
|
+
var JsonObject = Schema3.Record({ key: Schema3.String, value: Schema3.Unknown });
|
|
100
|
+
var McpOAuthDiscoveryState = Schema3.Struct({
|
|
101
|
+
resourceMetadataUrl: Schema3.NullOr(Schema3.String),
|
|
102
|
+
authorizationServerUrl: Schema3.NullOr(Schema3.String),
|
|
103
|
+
resourceMetadata: Schema3.NullOr(JsonObject),
|
|
104
|
+
authorizationServerMetadata: Schema3.NullOr(JsonObject),
|
|
105
|
+
clientInformation: Schema3.NullOr(JsonObject)
|
|
207
106
|
});
|
|
208
|
-
var
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
catch: (cause) => new McpConnectionError({
|
|
214
|
-
transport: input.transport,
|
|
215
|
-
message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
216
|
-
})
|
|
217
|
-
});
|
|
218
|
-
return connectionFromClient(client);
|
|
107
|
+
var McpOAuthSession = Schema3.Struct({
|
|
108
|
+
...McpOAuthDiscoveryState.fields,
|
|
109
|
+
endpoint: Schema3.String,
|
|
110
|
+
redirectUrl: Schema3.String,
|
|
111
|
+
codeVerifier: Schema3.String
|
|
219
112
|
});
|
|
220
|
-
var createMcpConnector = (input) => {
|
|
221
|
-
if (input.transport === "stdio") {
|
|
222
|
-
const command = input.command.trim();
|
|
223
|
-
if (!command) {
|
|
224
|
-
return new McpConnectionError({
|
|
225
|
-
transport: "stdio",
|
|
226
|
-
message: "MCP stdio transport requires a command"
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
return connectClient({
|
|
230
|
-
transport: "stdio",
|
|
231
|
-
createTransport: () => new StdioClientTransport({
|
|
232
|
-
command,
|
|
233
|
-
args: input.args ? [...input.args] : void 0,
|
|
234
|
-
env: input.env ? { ...process.env, ...input.env } : void 0,
|
|
235
|
-
cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
|
|
236
|
-
})
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
const headers = input.headers ?? {};
|
|
240
|
-
const remoteTransport = input.remoteTransport ?? "auto";
|
|
241
|
-
const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
|
|
242
|
-
const endpoint = buildEndpointUrl(
|
|
243
|
-
input.endpoint,
|
|
244
|
-
input.queryParams ?? {}
|
|
245
|
-
);
|
|
246
|
-
const connectStreamableHttp = connectClient({
|
|
247
|
-
transport: "streamable-http",
|
|
248
|
-
createTransport: () => new StreamableHTTPClientTransport(endpoint, {
|
|
249
|
-
requestInit,
|
|
250
|
-
authProvider: input.authProvider
|
|
251
|
-
})
|
|
252
|
-
});
|
|
253
|
-
const connectSse = connectClient({
|
|
254
|
-
transport: "sse",
|
|
255
|
-
createTransport: () => new SSEClientTransport(endpoint, {
|
|
256
|
-
requestInit,
|
|
257
|
-
authProvider: input.authProvider
|
|
258
|
-
})
|
|
259
|
-
});
|
|
260
|
-
if (remoteTransport === "streamable-http") return connectStreamableHttp;
|
|
261
|
-
if (remoteTransport === "sse") return connectSse;
|
|
262
|
-
return connectStreamableHttp.pipe(Effect2.catchAll(() => connectSse));
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// src/sdk/oauth.ts
|
|
266
|
-
import {
|
|
267
|
-
auth
|
|
268
|
-
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
269
|
-
import { Effect as Effect3 } from "effect";
|
|
270
113
|
var toJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
271
114
|
var CLIENT_METADATA = {
|
|
272
115
|
grant_types: ["authorization_code", "refresh_token"],
|
|
@@ -281,13 +124,13 @@ var extractDiscoveryState = (discoveryState, clientInformation) => ({
|
|
|
281
124
|
authorizationServerMetadata: toJsonObject(discoveryState?.authorizationServerMetadata),
|
|
282
125
|
clientInformation: toJsonObject(clientInformation)
|
|
283
126
|
});
|
|
284
|
-
var callAuth = (provider, opts) =>
|
|
127
|
+
var callAuth = (provider, opts) => Effect.tryPromise({
|
|
285
128
|
try: () => auth(provider, opts),
|
|
286
129
|
catch: (cause) => new McpOAuthError({
|
|
287
130
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
288
131
|
})
|
|
289
132
|
});
|
|
290
|
-
var startMcpOAuthAuthorization = (input) =>
|
|
133
|
+
var startMcpOAuthAuthorization = (input) => Effect.gen(function* () {
|
|
291
134
|
let authorizationUrl;
|
|
292
135
|
let codeVerifier;
|
|
293
136
|
let discoveryState;
|
|
@@ -333,7 +176,7 @@ var startMcpOAuthAuthorization = (input) => Effect3.gen(function* () {
|
|
|
333
176
|
...extractDiscoveryState(discoveryState, clientInformation)
|
|
334
177
|
};
|
|
335
178
|
});
|
|
336
|
-
var exchangeMcpOAuthCode = (input) =>
|
|
179
|
+
var exchangeMcpOAuthCode = (input) => Effect.gen(function* () {
|
|
337
180
|
const { session } = input;
|
|
338
181
|
let tokens;
|
|
339
182
|
let discoveryState = {
|
|
@@ -383,27 +226,215 @@ var exchangeMcpOAuthCode = (input) => Effect3.gen(function* () {
|
|
|
383
226
|
};
|
|
384
227
|
});
|
|
385
228
|
|
|
229
|
+
// src/sdk/stored-source.ts
|
|
230
|
+
import { Schema as Schema4 } from "effect";
|
|
231
|
+
var McpStoredSourceSchema = class extends Schema4.Class("McpStoredSource")({
|
|
232
|
+
namespace: Schema4.String,
|
|
233
|
+
name: Schema4.String,
|
|
234
|
+
config: McpStoredSourceData
|
|
235
|
+
}) {
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/sdk/binding-store.ts
|
|
239
|
+
var MCP_OAUTH_SESSION_TTL_MS = 15 * 60 * 1e3;
|
|
240
|
+
var StoredOAuthSession = Schema5.Struct({
|
|
241
|
+
session: McpOAuthSession,
|
|
242
|
+
expiresAt: Schema5.Number
|
|
243
|
+
});
|
|
244
|
+
var encodeOAuthSession = Schema5.encodeSync(Schema5.parseJson(StoredOAuthSession));
|
|
245
|
+
var decodeOAuthSession = Schema5.decodeUnknownSync(Schema5.parseJson(StoredOAuthSession));
|
|
246
|
+
var encodeSource = Schema5.encodeSync(Schema5.parseJson(McpStoredSourceSchema));
|
|
247
|
+
var decodeSource = Schema5.decodeUnknownSync(Schema5.parseJson(McpStoredSourceSchema));
|
|
248
|
+
var StoredBindingEntry = Schema5.Struct({
|
|
249
|
+
namespace: Schema5.String,
|
|
250
|
+
binding: McpToolBinding
|
|
251
|
+
});
|
|
252
|
+
var encodeBindingEntry = Schema5.encodeSync(Schema5.parseJson(StoredBindingEntry));
|
|
253
|
+
var decodeBindingEntry = Schema5.decodeUnknownSync(Schema5.parseJson(StoredBindingEntry));
|
|
254
|
+
var makeStore = (bindings, sources, oauthSessions) => ({
|
|
255
|
+
// ---- Bindings ----
|
|
256
|
+
get: (toolId) => Effect2.gen(function* () {
|
|
257
|
+
const raw = yield* bindings.get(toolId);
|
|
258
|
+
if (!raw) return null;
|
|
259
|
+
const entry = decodeBindingEntry(raw);
|
|
260
|
+
return {
|
|
261
|
+
binding: entry.binding,
|
|
262
|
+
namespace: entry.namespace
|
|
263
|
+
};
|
|
264
|
+
}),
|
|
265
|
+
put: (toolId, namespace, binding) => bindings.set([{ key: toolId, value: encodeBindingEntry({ namespace, binding }) }]),
|
|
266
|
+
remove: (toolId) => bindings.delete([toolId]).pipe(Effect2.asVoid),
|
|
267
|
+
listByNamespace: (namespace) => Effect2.gen(function* () {
|
|
268
|
+
const entries = yield* bindings.list();
|
|
269
|
+
const ids = [];
|
|
270
|
+
for (const e of entries) {
|
|
271
|
+
const entry = decodeBindingEntry(e.value);
|
|
272
|
+
if (entry.namespace === namespace) ids.push(e.key);
|
|
273
|
+
}
|
|
274
|
+
return ids;
|
|
275
|
+
}),
|
|
276
|
+
removeByNamespace: (namespace) => Effect2.gen(function* () {
|
|
277
|
+
const entries = yield* bindings.list();
|
|
278
|
+
const ids = [];
|
|
279
|
+
for (const e of entries) {
|
|
280
|
+
const entry = decodeBindingEntry(e.value);
|
|
281
|
+
if (entry.namespace === namespace) ids.push(e.key);
|
|
282
|
+
}
|
|
283
|
+
if (ids.length > 0) yield* bindings.delete(ids);
|
|
284
|
+
return ids;
|
|
285
|
+
}),
|
|
286
|
+
// ---- Sources (meta + config combined) ----
|
|
287
|
+
putSource: (source) => sources.set([{ key: source.namespace, value: encodeSource(source) }]),
|
|
288
|
+
removeSource: (namespace) => sources.delete([namespace]).pipe(Effect2.asVoid),
|
|
289
|
+
listSources: () => Effect2.gen(function* () {
|
|
290
|
+
const entries = yield* sources.list();
|
|
291
|
+
return entries.map((e) => decodeSource(e.value));
|
|
292
|
+
}),
|
|
293
|
+
getSource: (namespace) => Effect2.gen(function* () {
|
|
294
|
+
const raw = yield* sources.get(namespace);
|
|
295
|
+
if (!raw) return null;
|
|
296
|
+
return decodeSource(raw);
|
|
297
|
+
}),
|
|
298
|
+
getSourceConfig: (namespace) => Effect2.gen(function* () {
|
|
299
|
+
const raw = yield* sources.get(namespace);
|
|
300
|
+
if (!raw) return null;
|
|
301
|
+
return decodeSource(raw).config;
|
|
302
|
+
}),
|
|
303
|
+
// ---- Pending OAuth sessions (short-lived, between startOAuth and completeOAuth) ----
|
|
304
|
+
putOAuthSession: (sessionId, session) => oauthSessions.set([
|
|
305
|
+
{
|
|
306
|
+
key: sessionId,
|
|
307
|
+
value: encodeOAuthSession({
|
|
308
|
+
session,
|
|
309
|
+
expiresAt: Date.now() + MCP_OAUTH_SESSION_TTL_MS
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
]),
|
|
313
|
+
getOAuthSession: (sessionId) => Effect2.gen(function* () {
|
|
314
|
+
const raw = yield* oauthSessions.get(sessionId);
|
|
315
|
+
if (!raw) return null;
|
|
316
|
+
const entry = decodeOAuthSession(raw);
|
|
317
|
+
if (entry.expiresAt < Date.now()) {
|
|
318
|
+
yield* oauthSessions.delete([sessionId]);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return entry.session;
|
|
322
|
+
}),
|
|
323
|
+
deleteOAuthSession: (sessionId) => oauthSessions.delete([sessionId]).pipe(Effect2.asVoid)
|
|
324
|
+
});
|
|
325
|
+
var makeKvBindingStore = (kv, namespace) => makeStore(
|
|
326
|
+
scopeKv(kv, `${namespace}.bindings`),
|
|
327
|
+
scopeKv(kv, `${namespace}.sources`),
|
|
328
|
+
scopeKv(kv, `${namespace}.oauth-sessions`)
|
|
329
|
+
);
|
|
330
|
+
var makeInMemoryBindingStore = () => makeStore(makeInMemoryScopedKv(), makeInMemoryScopedKv(), makeInMemoryScopedKv());
|
|
331
|
+
|
|
332
|
+
// src/sdk/plugin.ts
|
|
333
|
+
import { Effect as Effect6, Exit, ScopedCache, Duration, Scope } from "effect";
|
|
334
|
+
import {
|
|
335
|
+
Source,
|
|
336
|
+
SourceDetectionResult,
|
|
337
|
+
definePlugin,
|
|
338
|
+
ToolId,
|
|
339
|
+
SecretId
|
|
340
|
+
} from "@executor-js/sdk";
|
|
341
|
+
|
|
342
|
+
// src/sdk/connection.ts
|
|
343
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
344
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
345
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
346
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
347
|
+
import { Effect as Effect3 } from "effect";
|
|
348
|
+
var buildEndpointUrl = (endpoint, queryParams) => {
|
|
349
|
+
const url = new URL(endpoint);
|
|
350
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
351
|
+
url.searchParams.set(key, value);
|
|
352
|
+
}
|
|
353
|
+
return url;
|
|
354
|
+
};
|
|
355
|
+
var createClient = () => new Client(
|
|
356
|
+
{ name: "executor-mcp", version: "0.1.0" },
|
|
357
|
+
{ capabilities: { elicitation: { form: {}, url: {} } } }
|
|
358
|
+
);
|
|
359
|
+
var connectionFromClient = (client) => ({
|
|
360
|
+
client,
|
|
361
|
+
close: () => client.close()
|
|
362
|
+
});
|
|
363
|
+
var connectClient = (input) => Effect3.gen(function* () {
|
|
364
|
+
const client = createClient();
|
|
365
|
+
const transportInstance = input.createTransport();
|
|
366
|
+
yield* Effect3.tryPromise({
|
|
367
|
+
try: () => client.connect(transportInstance),
|
|
368
|
+
catch: (cause) => new McpConnectionError({
|
|
369
|
+
transport: input.transport,
|
|
370
|
+
message: `Failed connecting via ${input.transport}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
371
|
+
})
|
|
372
|
+
});
|
|
373
|
+
return connectionFromClient(client);
|
|
374
|
+
});
|
|
375
|
+
var createMcpConnector = (input) => {
|
|
376
|
+
if (input.transport === "stdio") {
|
|
377
|
+
const command = input.command.trim();
|
|
378
|
+
if (!command) {
|
|
379
|
+
return new McpConnectionError({
|
|
380
|
+
transport: "stdio",
|
|
381
|
+
message: "MCP stdio transport requires a command"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return connectClient({
|
|
385
|
+
transport: "stdio",
|
|
386
|
+
createTransport: () => new StdioClientTransport({
|
|
387
|
+
command,
|
|
388
|
+
args: input.args ? [...input.args] : void 0,
|
|
389
|
+
env: input.env ? { ...process.env, ...input.env } : void 0,
|
|
390
|
+
cwd: input.cwd?.trim().length ? input.cwd.trim() : void 0
|
|
391
|
+
})
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const headers = input.headers ?? {};
|
|
395
|
+
const remoteTransport = input.remoteTransport ?? "auto";
|
|
396
|
+
const requestInit = Object.keys(headers).length > 0 ? { headers } : void 0;
|
|
397
|
+
const endpoint = buildEndpointUrl(input.endpoint, input.queryParams ?? {});
|
|
398
|
+
const connectStreamableHttp = connectClient({
|
|
399
|
+
transport: "streamable-http",
|
|
400
|
+
createTransport: () => new StreamableHTTPClientTransport(endpoint, {
|
|
401
|
+
requestInit,
|
|
402
|
+
authProvider: input.authProvider
|
|
403
|
+
})
|
|
404
|
+
});
|
|
405
|
+
const connectSse = connectClient({
|
|
406
|
+
transport: "sse",
|
|
407
|
+
createTransport: () => new SSEClientTransport(endpoint, {
|
|
408
|
+
requestInit,
|
|
409
|
+
authProvider: input.authProvider
|
|
410
|
+
})
|
|
411
|
+
});
|
|
412
|
+
if (remoteTransport === "streamable-http") return connectStreamableHttp;
|
|
413
|
+
if (remoteTransport === "sse") return connectSse;
|
|
414
|
+
return connectStreamableHttp.pipe(Effect3.catchAll(() => connectSse));
|
|
415
|
+
};
|
|
416
|
+
|
|
386
417
|
// src/sdk/discover.ts
|
|
387
418
|
import { Effect as Effect4 } from "effect";
|
|
388
419
|
|
|
389
420
|
// src/sdk/manifest.ts
|
|
390
|
-
import { Schema as
|
|
391
|
-
var ListedTool =
|
|
392
|
-
name:
|
|
393
|
-
description:
|
|
394
|
-
inputSchema:
|
|
395
|
-
parameters:
|
|
396
|
-
outputSchema:
|
|
421
|
+
import { Schema as Schema6 } from "effect";
|
|
422
|
+
var ListedTool = Schema6.Struct({
|
|
423
|
+
name: Schema6.String,
|
|
424
|
+
description: Schema6.optional(Schema6.NullOr(Schema6.String)),
|
|
425
|
+
inputSchema: Schema6.optional(Schema6.Unknown),
|
|
426
|
+
parameters: Schema6.optional(Schema6.Unknown),
|
|
427
|
+
outputSchema: Schema6.optional(Schema6.Unknown)
|
|
397
428
|
});
|
|
398
|
-
var ListToolsResult =
|
|
399
|
-
tools:
|
|
429
|
+
var ListToolsResult = Schema6.Struct({
|
|
430
|
+
tools: Schema6.Array(ListedTool)
|
|
400
431
|
});
|
|
401
|
-
var ServerInfo =
|
|
402
|
-
name:
|
|
403
|
-
version:
|
|
432
|
+
var ServerInfo = Schema6.Struct({
|
|
433
|
+
name: Schema6.optional(Schema6.String),
|
|
434
|
+
version: Schema6.optional(Schema6.String)
|
|
404
435
|
});
|
|
405
|
-
var decodeListToolsResult =
|
|
406
|
-
var decodeServerInfo =
|
|
436
|
+
var decodeListToolsResult = Schema6.decodeUnknownOption(ListToolsResult);
|
|
437
|
+
var decodeServerInfo = Schema6.decodeUnknownOption(ServerInfo);
|
|
407
438
|
var sanitize = (value) => {
|
|
408
439
|
const s = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
409
440
|
return s || "tool";
|
|
@@ -481,7 +512,7 @@ var discoverTools = (connector) => Effect4.gen(function* () {
|
|
|
481
512
|
});
|
|
482
513
|
|
|
483
514
|
// src/sdk/invoke.ts
|
|
484
|
-
import { Effect as Effect5, Schema as
|
|
515
|
+
import { Effect as Effect5, Schema as Schema7 } from "effect";
|
|
485
516
|
import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
486
517
|
import {
|
|
487
518
|
ToolInvocationResult,
|
|
@@ -490,7 +521,7 @@ import {
|
|
|
490
521
|
ElicitationResponse,
|
|
491
522
|
FormElicitation,
|
|
492
523
|
UrlElicitation
|
|
493
|
-
} from "@executor-js/sdk
|
|
524
|
+
} from "@executor-js/sdk";
|
|
494
525
|
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
495
526
|
var makeOAuthProvider = (accessToken, tokenType, refreshToken) => ({
|
|
496
527
|
get redirectUrl() {
|
|
@@ -527,21 +558,21 @@ var makeOAuthProvider = (accessToken, tokenType, refreshToken) => ({
|
|
|
527
558
|
},
|
|
528
559
|
discoveryState: () => void 0
|
|
529
560
|
});
|
|
530
|
-
var McpElicitParams =
|
|
531
|
-
|
|
532
|
-
mode:
|
|
533
|
-
message:
|
|
534
|
-
url:
|
|
535
|
-
elicitationId:
|
|
536
|
-
id:
|
|
561
|
+
var McpElicitParams = Schema7.Union(
|
|
562
|
+
Schema7.Struct({
|
|
563
|
+
mode: Schema7.Literal("url"),
|
|
564
|
+
message: Schema7.String,
|
|
565
|
+
url: Schema7.String,
|
|
566
|
+
elicitationId: Schema7.optional(Schema7.String),
|
|
567
|
+
id: Schema7.optional(Schema7.String)
|
|
537
568
|
}),
|
|
538
|
-
|
|
539
|
-
mode:
|
|
540
|
-
message:
|
|
541
|
-
requestedSchema:
|
|
569
|
+
Schema7.Struct({
|
|
570
|
+
mode: Schema7.optional(Schema7.Literal("form")),
|
|
571
|
+
message: Schema7.String,
|
|
572
|
+
requestedSchema: Schema7.Record({ key: Schema7.String, value: Schema7.Unknown })
|
|
542
573
|
})
|
|
543
574
|
);
|
|
544
|
-
var decodeElicitParams =
|
|
575
|
+
var decodeElicitParams = Schema7.decodeUnknownSync(McpElicitParams);
|
|
545
576
|
var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitation({
|
|
546
577
|
message: params.message,
|
|
547
578
|
url: params.url,
|
|
@@ -551,19 +582,16 @@ var toElicitationRequest = (params) => params.mode === "url" ? new UrlElicitatio
|
|
|
551
582
|
requestedSchema: params.requestedSchema
|
|
552
583
|
});
|
|
553
584
|
var installElicitationHandler = (client, toolId, args, handler) => {
|
|
554
|
-
client.setRequestHandler(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
);
|
|
585
|
+
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
586
|
+
const params = decodeElicitParams(request.params);
|
|
587
|
+
const response = await Effect5.runPromise(
|
|
588
|
+
handler({ toolId, args, request: toElicitationRequest(params) })
|
|
589
|
+
);
|
|
590
|
+
return {
|
|
591
|
+
action: response.action,
|
|
592
|
+
...response.action === "accept" && response.content ? { content: response.content } : {}
|
|
593
|
+
};
|
|
594
|
+
});
|
|
567
595
|
};
|
|
568
596
|
var resolveConnectorInput = (sourceData, secrets, scopeId) => {
|
|
569
597
|
if (sourceData.transport === "stdio") {
|
|
@@ -576,7 +604,7 @@ var resolveConnectorInput = (sourceData, secrets, scopeId) => {
|
|
|
576
604
|
});
|
|
577
605
|
}
|
|
578
606
|
return Effect5.gen(function* () {
|
|
579
|
-
const headers = { ...sourceData.headers
|
|
607
|
+
const headers = { ...sourceData.headers };
|
|
580
608
|
let authProvider;
|
|
581
609
|
const auth2 = sourceData.auth;
|
|
582
610
|
if (auth2.kind === "header") {
|
|
@@ -607,11 +635,7 @@ var resolveConnectorInput = (sourceData, secrets, scopeId) => {
|
|
|
607
635
|
Effect5.map((o) => o._tag === "Some" ? o.value : void 0)
|
|
608
636
|
);
|
|
609
637
|
}
|
|
610
|
-
authProvider = makeOAuthProvider(
|
|
611
|
-
accessToken,
|
|
612
|
-
auth2.tokenType ?? "Bearer",
|
|
613
|
-
refreshToken
|
|
614
|
-
);
|
|
638
|
+
authProvider = makeOAuthProvider(accessToken, auth2.tokenType ?? "Bearer", refreshToken);
|
|
615
639
|
}
|
|
616
640
|
return {
|
|
617
641
|
transport: "remote",
|
|
@@ -639,9 +663,7 @@ var useMcpConnection = (connection, toolId, toolName, args, handler) => Effect5.
|
|
|
639
663
|
var makeMcpInvoker = (opts) => {
|
|
640
664
|
const { connectionCache, pendingConnectors } = opts;
|
|
641
665
|
return {
|
|
642
|
-
resolveAnnotations: () => Effect5.succeed(
|
|
643
|
-
new ToolAnnotations({ requiresApproval: false })
|
|
644
|
-
),
|
|
666
|
+
resolveAnnotations: () => Effect5.succeed(new ToolAnnotations({ requiresApproval: false })),
|
|
645
667
|
invoke: (toolId, args, options) => Effect5.gen(function* () {
|
|
646
668
|
const entry = yield* opts.bindingStore.get(toolId);
|
|
647
669
|
if (!entry) {
|
|
@@ -651,13 +673,17 @@ var makeMcpInvoker = (opts) => {
|
|
|
651
673
|
cause: void 0
|
|
652
674
|
});
|
|
653
675
|
}
|
|
654
|
-
const
|
|
676
|
+
const sourceData = yield* opts.bindingStore.getSourceConfig(entry.namespace);
|
|
677
|
+
if (!sourceData) {
|
|
678
|
+
return yield* new ToolInvocationError({
|
|
679
|
+
toolId,
|
|
680
|
+
message: `No MCP source config found for namespace "${entry.namespace}"`,
|
|
681
|
+
cause: void 0
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
const { binding } = entry;
|
|
655
685
|
const cacheKey = connectionCacheKey(sourceData);
|
|
656
|
-
const connector = resolveConnectorInput(
|
|
657
|
-
sourceData,
|
|
658
|
-
opts.secrets,
|
|
659
|
-
opts.scopeId
|
|
660
|
-
).pipe(
|
|
686
|
+
const connector = resolveConnectorInput(sourceData, opts.secrets, opts.scopeId).pipe(
|
|
661
687
|
Effect5.flatMap((ci) => createMcpConnector(ci)),
|
|
662
688
|
Effect5.mapError(
|
|
663
689
|
(err) => new McpConnectionError({
|
|
@@ -686,7 +712,7 @@ var makeMcpInvoker = (opts) => {
|
|
|
686
712
|
).pipe(
|
|
687
713
|
// On failure, invalidate the cached connection and retry once
|
|
688
714
|
Effect5.catchAll(
|
|
689
|
-
(
|
|
715
|
+
() => Effect5.gen(function* () {
|
|
690
716
|
yield* connectionCache.invalidate(cacheKey);
|
|
691
717
|
pendingConnectors.set(cacheKey, connector);
|
|
692
718
|
const freshConnection = yield* connectionCache.get(cacheKey).pipe(
|
|
@@ -733,9 +759,7 @@ var makeMcpInvoker = (opts) => {
|
|
|
733
759
|
),
|
|
734
760
|
closeConnections: () => Effect5.sync(() => {
|
|
735
761
|
pendingConnectors.clear();
|
|
736
|
-
}).pipe(
|
|
737
|
-
Effect5.flatMap(() => connectionCache.invalidateAll)
|
|
738
|
-
)
|
|
762
|
+
}).pipe(Effect5.flatMap(() => connectionCache.invalidateAll))
|
|
739
763
|
};
|
|
740
764
|
};
|
|
741
765
|
|
|
@@ -821,7 +845,6 @@ var mcpDiscoveryError = (message) => new McpToolDiscoveryError({ stage: "list_to
|
|
|
821
845
|
var mcpPlugin = (options) => {
|
|
822
846
|
const bindingStore = options?.bindingStore ?? makeInMemoryBindingStore();
|
|
823
847
|
const addedSources = /* @__PURE__ */ new Map();
|
|
824
|
-
const oauthSessions = /* @__PURE__ */ new Map();
|
|
825
848
|
return definePlugin({
|
|
826
849
|
key: "mcp",
|
|
827
850
|
init: (ctx) => Effect6.gen(function* () {
|
|
@@ -857,9 +880,16 @@ var mcpPlugin = (options) => {
|
|
|
857
880
|
yield* ctx.tools.registerInvoker("mcp", invoker);
|
|
858
881
|
const savedSources = yield* bindingStore.listSources();
|
|
859
882
|
for (const s of savedSources) {
|
|
883
|
+
const isRemote = s.config.transport === "remote";
|
|
860
884
|
addedSources.set(
|
|
861
885
|
s.namespace,
|
|
862
|
-
new Source({
|
|
886
|
+
new Source({
|
|
887
|
+
id: s.namespace,
|
|
888
|
+
name: s.name,
|
|
889
|
+
kind: "mcp",
|
|
890
|
+
url: s.config.transport === "remote" ? s.config.endpoint : void 0,
|
|
891
|
+
canEdit: isRemote
|
|
892
|
+
})
|
|
863
893
|
);
|
|
864
894
|
}
|
|
865
895
|
const resolveConnectorInput2 = (sd) => {
|
|
@@ -874,30 +904,26 @@ var mcpPlugin = (options) => {
|
|
|
874
904
|
}
|
|
875
905
|
return Effect6.gen(function* () {
|
|
876
906
|
const headers = {
|
|
877
|
-
...sd.headers
|
|
907
|
+
...sd.headers
|
|
878
908
|
};
|
|
879
909
|
let authProvider;
|
|
880
910
|
const auth2 = sd.auth;
|
|
881
911
|
if (auth2.kind === "header") {
|
|
882
|
-
const val = yield* ctx.secrets.resolve(auth2.secretId, ctx.scope.id).pipe(
|
|
912
|
+
const val = yield* ctx.secrets.resolve(SecretId.make(auth2.secretId), ctx.scope.id).pipe(
|
|
883
913
|
Effect6.mapError(
|
|
884
|
-
() => remoteConnectionError(
|
|
885
|
-
`Failed to resolve secret "${auth2.secretId}"`
|
|
886
|
-
)
|
|
914
|
+
() => remoteConnectionError(`Failed to resolve secret "${auth2.secretId}"`)
|
|
887
915
|
)
|
|
888
916
|
);
|
|
889
917
|
headers[auth2.headerName] = auth2.prefix ? `${auth2.prefix}${val}` : val;
|
|
890
918
|
} else if (auth2.kind === "oauth2") {
|
|
891
|
-
const accessToken = yield* ctx.secrets.resolve(auth2.accessTokenSecretId, ctx.scope.id).pipe(
|
|
919
|
+
const accessToken = yield* ctx.secrets.resolve(SecretId.make(auth2.accessTokenSecretId), ctx.scope.id).pipe(
|
|
892
920
|
Effect6.mapError(
|
|
893
|
-
() => remoteConnectionError(
|
|
894
|
-
"Failed to resolve OAuth access token"
|
|
895
|
-
)
|
|
921
|
+
() => remoteConnectionError("Failed to resolve OAuth access token")
|
|
896
922
|
)
|
|
897
923
|
);
|
|
898
924
|
let refreshToken;
|
|
899
925
|
if (auth2.refreshTokenSecretId) {
|
|
900
|
-
refreshToken = yield* ctx.secrets.resolve(auth2.refreshTokenSecretId, ctx.scope.id).pipe(
|
|
926
|
+
refreshToken = yield* ctx.secrets.resolve(SecretId.make(auth2.refreshTokenSecretId), ctx.scope.id).pipe(
|
|
901
927
|
Effect6.option,
|
|
902
928
|
Effect6.map((o) => o._tag === "Some" ? o.value : void 0)
|
|
903
929
|
);
|
|
@@ -930,9 +956,7 @@ var mcpPlugin = (options) => {
|
|
|
930
956
|
detect: (url) => Effect6.gen(function* () {
|
|
931
957
|
const trimmed = url.trim();
|
|
932
958
|
if (!trimmed) return null;
|
|
933
|
-
const parsed = yield* Effect6.try(() => new URL(trimmed)).pipe(
|
|
934
|
-
Effect6.option
|
|
935
|
-
);
|
|
959
|
+
const parsed = yield* Effect6.try(() => new URL(trimmed)).pipe(Effect6.option);
|
|
936
960
|
if (parsed._tag === "None") return null;
|
|
937
961
|
const name = parsed.value.hostname || "mcp";
|
|
938
962
|
const namespace = deriveMcpNamespace({ endpoint: trimmed });
|
|
@@ -979,9 +1003,9 @@ var mcpPlugin = (options) => {
|
|
|
979
1003
|
Effect6.catchAll(() => Effect6.succeed(null))
|
|
980
1004
|
);
|
|
981
1005
|
if (!ci) return;
|
|
982
|
-
const manifest = yield* discoverTools(
|
|
983
|
-
|
|
984
|
-
)
|
|
1006
|
+
const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
|
|
1007
|
+
Effect6.catchAll(() => Effect6.succeed(null))
|
|
1008
|
+
);
|
|
985
1009
|
if (!manifest) return;
|
|
986
1010
|
const oldIds = yield* bindingStore.removeByNamespace(sourceId);
|
|
987
1011
|
if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
|
|
@@ -990,20 +1014,16 @@ var mcpPlugin = (options) => {
|
|
|
990
1014
|
(e) => bindingStore.put(
|
|
991
1015
|
ToolId.make(joinToolPath(sourceId, e.toolId)),
|
|
992
1016
|
sourceId,
|
|
993
|
-
toBinding(e)
|
|
994
|
-
sd
|
|
1017
|
+
toBinding(e)
|
|
995
1018
|
),
|
|
996
1019
|
{ discard: true }
|
|
997
1020
|
);
|
|
998
|
-
yield* ctx.tools.register(
|
|
999
|
-
manifest.tools.map((e) => toRegistration(e, sourceId))
|
|
1000
|
-
);
|
|
1021
|
+
yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, sourceId)));
|
|
1001
1022
|
})
|
|
1002
1023
|
});
|
|
1003
1024
|
const probeEndpoint = (endpoint) => Effect6.gen(function* () {
|
|
1004
1025
|
const trimmed = endpoint.trim();
|
|
1005
|
-
if (!trimmed)
|
|
1006
|
-
return yield* remoteConnectionError("Endpoint URL is required");
|
|
1026
|
+
if (!trimmed) return yield* remoteConnectionError("Endpoint URL is required");
|
|
1007
1027
|
const name = yield* Effect6.try(() => new URL(trimmed).hostname).pipe(
|
|
1008
1028
|
Effect6.orElseSucceed(() => "mcp")
|
|
1009
1029
|
);
|
|
@@ -1014,9 +1034,7 @@ var mcpPlugin = (options) => {
|
|
|
1014
1034
|
});
|
|
1015
1035
|
const result = yield* discoverTools(connector).pipe(
|
|
1016
1036
|
Effect6.map((m) => ({ ok: true, manifest: m })),
|
|
1017
|
-
Effect6.catchAll(
|
|
1018
|
-
() => Effect6.succeed({ ok: false, manifest: null })
|
|
1019
|
-
)
|
|
1037
|
+
Effect6.catchAll(() => Effect6.succeed({ ok: false, manifest: null }))
|
|
1020
1038
|
);
|
|
1021
1039
|
if (result.ok && result.manifest) {
|
|
1022
1040
|
return {
|
|
@@ -1056,20 +1074,15 @@ var mcpPlugin = (options) => {
|
|
|
1056
1074
|
const ci = yield* resolveConnectorInput2(sd);
|
|
1057
1075
|
const connector = createMcpConnector(ci);
|
|
1058
1076
|
const manifest = yield* discoverTools(connector).pipe(
|
|
1059
|
-
Effect6.mapError(
|
|
1060
|
-
(err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`)
|
|
1061
|
-
)
|
|
1062
|
-
);
|
|
1063
|
-
const registrations = manifest.tools.map(
|
|
1064
|
-
(e) => toRegistration(e, namespace)
|
|
1077
|
+
Effect6.mapError((err) => mcpDiscoveryError(`MCP discovery failed: ${err.message}`))
|
|
1065
1078
|
);
|
|
1079
|
+
const registrations = manifest.tools.map((e) => toRegistration(e, namespace));
|
|
1066
1080
|
yield* Effect6.forEach(
|
|
1067
1081
|
manifest.tools,
|
|
1068
1082
|
(e) => bindingStore.put(
|
|
1069
1083
|
ToolId.make(joinToolPath(namespace, e.toolId)),
|
|
1070
1084
|
namespace,
|
|
1071
|
-
toBinding(e)
|
|
1072
|
-
sd
|
|
1085
|
+
toBinding(e)
|
|
1073
1086
|
),
|
|
1074
1087
|
{ discard: true }
|
|
1075
1088
|
);
|
|
@@ -1085,7 +1098,9 @@ var mcpPlugin = (options) => {
|
|
|
1085
1098
|
new Source({
|
|
1086
1099
|
id: namespace,
|
|
1087
1100
|
name: sourceName,
|
|
1088
|
-
kind: "mcp"
|
|
1101
|
+
kind: "mcp",
|
|
1102
|
+
url: sd.transport === "remote" ? sd.endpoint : void 0,
|
|
1103
|
+
canEdit: config.transport === "remote"
|
|
1089
1104
|
})
|
|
1090
1105
|
);
|
|
1091
1106
|
return { toolCount: registrations.length, namespace };
|
|
@@ -1099,16 +1114,10 @@ var mcpPlugin = (options) => {
|
|
|
1099
1114
|
const refreshSource = (namespace) => Effect6.gen(function* () {
|
|
1100
1115
|
const sd = yield* bindingStore.getSourceConfig(namespace);
|
|
1101
1116
|
if (!sd)
|
|
1102
|
-
return yield* remoteConnectionError(
|
|
1103
|
-
`No stored config for MCP source "${namespace}"`
|
|
1104
|
-
);
|
|
1117
|
+
return yield* remoteConnectionError(`No stored config for MCP source "${namespace}"`);
|
|
1105
1118
|
const ci = yield* resolveConnectorInput2(sd);
|
|
1106
|
-
const manifest = yield* discoverTools(
|
|
1107
|
-
|
|
1108
|
-
).pipe(
|
|
1109
|
-
Effect6.mapError(
|
|
1110
|
-
(err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`)
|
|
1111
|
-
)
|
|
1119
|
+
const manifest = yield* discoverTools(createMcpConnector(ci)).pipe(
|
|
1120
|
+
Effect6.mapError((err) => mcpDiscoveryError(`MCP refresh failed: ${err.message}`))
|
|
1112
1121
|
);
|
|
1113
1122
|
const oldIds = yield* bindingStore.removeByNamespace(namespace);
|
|
1114
1123
|
if (oldIds.length > 0) yield* ctx.tools.unregister(oldIds);
|
|
@@ -1117,25 +1126,20 @@ var mcpPlugin = (options) => {
|
|
|
1117
1126
|
(e) => bindingStore.put(
|
|
1118
1127
|
ToolId.make(joinToolPath(namespace, e.toolId)),
|
|
1119
1128
|
namespace,
|
|
1120
|
-
toBinding(e)
|
|
1121
|
-
sd
|
|
1129
|
+
toBinding(e)
|
|
1122
1130
|
),
|
|
1123
1131
|
{ discard: true }
|
|
1124
1132
|
);
|
|
1125
|
-
yield* ctx.tools.register(
|
|
1126
|
-
manifest.tools.map((e) => toRegistration(e, namespace))
|
|
1127
|
-
);
|
|
1133
|
+
yield* ctx.tools.register(manifest.tools.map((e) => toRegistration(e, namespace)));
|
|
1128
1134
|
return { toolCount: manifest.tools.length };
|
|
1129
1135
|
});
|
|
1130
1136
|
const startOAuth = (input) => Effect6.gen(function* () {
|
|
1131
1137
|
const endpoint = input.endpoint.trim();
|
|
1132
|
-
if (!endpoint)
|
|
1133
|
-
return yield* mcpOAuthError("MCP OAuth requires an endpoint");
|
|
1138
|
+
if (!endpoint) return yield* mcpOAuthError("MCP OAuth requires an endpoint");
|
|
1134
1139
|
let fullEndpoint = endpoint;
|
|
1135
1140
|
if (input.queryParams && Object.keys(input.queryParams).length > 0) {
|
|
1136
1141
|
const u = new URL(endpoint);
|
|
1137
|
-
for (const [k, v] of Object.entries(input.queryParams))
|
|
1138
|
-
u.searchParams.set(k, v);
|
|
1142
|
+
for (const [k, v] of Object.entries(input.queryParams)) u.searchParams.set(k, v);
|
|
1139
1143
|
fullEndpoint = u.toString();
|
|
1140
1144
|
}
|
|
1141
1145
|
const sessionId = `mcp_oauth_${crypto.randomUUID()}`;
|
|
@@ -1143,12 +1147,8 @@ var mcpPlugin = (options) => {
|
|
|
1143
1147
|
endpoint: fullEndpoint,
|
|
1144
1148
|
redirectUrl: input.redirectUrl,
|
|
1145
1149
|
state: sessionId
|
|
1146
|
-
}).pipe(
|
|
1147
|
-
|
|
1148
|
-
(e) => mcpOAuthError(`OAuth start failed: ${e.message}`)
|
|
1149
|
-
)
|
|
1150
|
-
);
|
|
1151
|
-
oauthSessions.set(sessionId, {
|
|
1150
|
+
}).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth start failed: ${e.message}`)));
|
|
1151
|
+
yield* bindingStore.putOAuthSession(sessionId, {
|
|
1152
1152
|
endpoint: fullEndpoint,
|
|
1153
1153
|
redirectUrl: input.redirectUrl,
|
|
1154
1154
|
codeVerifier: started.codeVerifier,
|
|
@@ -1164,56 +1164,39 @@ var mcpPlugin = (options) => {
|
|
|
1164
1164
|
};
|
|
1165
1165
|
});
|
|
1166
1166
|
const completeOAuth = (input) => Effect6.gen(function* () {
|
|
1167
|
-
if (input.error)
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const session = oauthSessions.get(input.state);
|
|
1172
|
-
if (!session)
|
|
1173
|
-
return yield* mcpOAuthError(`OAuth session not found: ${input.state}`);
|
|
1167
|
+
if (input.error) return yield* mcpOAuthError(`OAuth error: ${input.error}`);
|
|
1168
|
+
if (!input.code) return yield* mcpOAuthError("Missing OAuth authorization code");
|
|
1169
|
+
const session = yield* bindingStore.getOAuthSession(input.state);
|
|
1170
|
+
if (!session) return yield* mcpOAuthError(`OAuth session not found: ${input.state}`);
|
|
1174
1171
|
const exchanged = yield* exchangeMcpOAuthCode({
|
|
1175
1172
|
session,
|
|
1176
1173
|
code: input.code
|
|
1177
|
-
}).pipe(
|
|
1178
|
-
Effect6.mapError(
|
|
1179
|
-
(e) => mcpOAuthError(`OAuth exchange failed: ${e.message}`)
|
|
1180
|
-
)
|
|
1181
|
-
);
|
|
1174
|
+
}).pipe(Effect6.mapError((e) => mcpOAuthError(`OAuth exchange failed: ${e.message}`)));
|
|
1182
1175
|
const accessTokenRef = yield* ctx.secrets.set({
|
|
1183
|
-
id: SecretId.make(
|
|
1184
|
-
`mcp-oauth-access-${input.state}`
|
|
1185
|
-
),
|
|
1176
|
+
id: SecretId.make(`mcp-oauth-access-${input.state}`),
|
|
1186
1177
|
scopeId: ctx.scope.id,
|
|
1187
1178
|
name: "MCP OAuth Access Token",
|
|
1188
1179
|
value: exchanged.tokens.access_token,
|
|
1189
1180
|
purpose: "oauth_access_token"
|
|
1190
1181
|
}).pipe(
|
|
1191
|
-
Effect6.mapError(
|
|
1192
|
-
(e) => mcpOAuthError(
|
|
1193
|
-
`Failed to store access token: ${String(e)}`
|
|
1194
|
-
)
|
|
1195
|
-
)
|
|
1182
|
+
Effect6.mapError((e) => mcpOAuthError(`Failed to store access token: ${String(e)}`))
|
|
1196
1183
|
);
|
|
1197
1184
|
let refreshTokenSecretId = null;
|
|
1198
1185
|
if (exchanged.tokens.refresh_token) {
|
|
1199
1186
|
const ref = yield* ctx.secrets.set({
|
|
1200
|
-
id: SecretId.make(
|
|
1201
|
-
`mcp-oauth-refresh-${input.state}`
|
|
1202
|
-
),
|
|
1187
|
+
id: SecretId.make(`mcp-oauth-refresh-${input.state}`),
|
|
1203
1188
|
scopeId: ctx.scope.id,
|
|
1204
1189
|
name: "MCP OAuth Refresh Token",
|
|
1205
1190
|
value: exchanged.tokens.refresh_token,
|
|
1206
1191
|
purpose: "oauth_refresh_token"
|
|
1207
1192
|
}).pipe(
|
|
1208
1193
|
Effect6.mapError(
|
|
1209
|
-
(e) => mcpOAuthError(
|
|
1210
|
-
`Failed to store refresh token: ${String(e)}`
|
|
1211
|
-
)
|
|
1194
|
+
(e) => mcpOAuthError(`Failed to store refresh token: ${String(e)}`)
|
|
1212
1195
|
)
|
|
1213
1196
|
);
|
|
1214
1197
|
refreshTokenSecretId = ref.id;
|
|
1215
1198
|
}
|
|
1216
|
-
|
|
1199
|
+
yield* bindingStore.deleteOAuthSession(input.state);
|
|
1217
1200
|
const expiresAt = typeof exchanged.tokens.expires_in === "number" ? Date.now() + exchanged.tokens.expires_in * 1e3 : null;
|
|
1218
1201
|
return {
|
|
1219
1202
|
accessTokenSecretId: accessTokenRef.id,
|
|
@@ -1223,6 +1206,24 @@ var mcpPlugin = (options) => {
|
|
|
1223
1206
|
scope: exchanged.tokens.scope ?? null
|
|
1224
1207
|
};
|
|
1225
1208
|
});
|
|
1209
|
+
const updateSource = (namespace, input) => Effect6.gen(function* () {
|
|
1210
|
+
const existing = yield* bindingStore.getSource(namespace);
|
|
1211
|
+
if (!existing || existing.config.transport !== "remote") return;
|
|
1212
|
+
const remote = existing.config;
|
|
1213
|
+
const updatedConfig = {
|
|
1214
|
+
...remote,
|
|
1215
|
+
...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
|
|
1216
|
+
...input.headers !== void 0 ? { headers: input.headers } : {},
|
|
1217
|
+
...input.auth !== void 0 ? { auth: input.auth } : {},
|
|
1218
|
+
...input.queryParams !== void 0 ? { queryParams: input.queryParams } : {}
|
|
1219
|
+
};
|
|
1220
|
+
yield* bindingStore.putSource({
|
|
1221
|
+
namespace,
|
|
1222
|
+
name: input.name?.trim() || existing.name,
|
|
1223
|
+
config: updatedConfig
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
const getSource = (namespace) => bindingStore.getSource(namespace);
|
|
1226
1227
|
return {
|
|
1227
1228
|
extension: {
|
|
1228
1229
|
probeEndpoint,
|
|
@@ -1230,7 +1231,9 @@ var mcpPlugin = (options) => {
|
|
|
1230
1231
|
removeSource,
|
|
1231
1232
|
refreshSource,
|
|
1232
1233
|
startOAuth,
|
|
1233
|
-
completeOAuth
|
|
1234
|
+
completeOAuth,
|
|
1235
|
+
getSource,
|
|
1236
|
+
updateSource
|
|
1234
1237
|
},
|
|
1235
1238
|
close: () => Effect6.gen(function* () {
|
|
1236
1239
|
yield* invoker.closeConnections();
|
|
@@ -1249,4 +1252,4 @@ export {
|
|
|
1249
1252
|
makeKvBindingStore,
|
|
1250
1253
|
mcpPlugin
|
|
1251
1254
|
};
|
|
1252
|
-
//# sourceMappingURL=chunk-
|
|
1255
|
+
//# sourceMappingURL=chunk-X3JTTDWJ.js.map
|