apps 0.0.1-beta.84 → 0.0.1-beta.86
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 +10 -12
- package/framework-reference.json +1 -1
- package/js/{chunk-KGCTYL2S.js → chunk-3FHDX4KB.js} +24 -5
- package/js/{chunk-T2B6GUVY.js → chunk-3ZHRJYP5.js} +2 -2
- package/js/{chunk-JKH7ALFN.js → chunk-BKK5HKJL.js} +3 -3
- package/js/{chunk-HNCCRVIT.js → chunk-BLM5MCKC.js} +2 -2
- package/js/{chunk-OHBKJ54H.js → chunk-CJ5F6UDG.js} +6 -6
- package/js/{chunk-TE25A7D6.js → chunk-F52NGWRV.js} +2 -2
- package/js/chunk-HODA4JY6.js +714 -0
- package/js/{chunk-AIXYBMMI.js → chunk-LNNDKHO6.js} +3 -3
- package/js/{chunk-NG56RPCA.js → chunk-WUPLD4VN.js} +0 -10
- package/js/{chunk-PL5KOEZD.js → chunk-YOGK42MH.js} +1 -1
- package/js/client.js +1 -1
- package/js/contracts.js +1 -1
- package/js/effect.js +7 -7
- package/js/graphql.js +9 -9
- package/js/host.js +7 -7
- package/js/index.js +4 -4
- package/js/mcp/effect.js +8 -9
- package/js/mcp.js +14 -16
- package/js/openapi.js +5 -5
- package/js/skills.js +3 -3
- package/package.json +1 -5
- package/runtime.json +1 -1
- package/types/apps/src/contracts/mcp.d.ts +1 -10
- package/types/apps/src/implementation/router-catalog.d.ts +2 -2
- package/js/chunk-BX2S3T2R.js +0 -348
- package/js/chunk-IGOYMUWZ.js +0 -394
- package/js/mcp/stdio.js +0 -138
- package/types/apps/src/implementation/mcp-stdio.d.ts +0 -4
- package/types/apps/src/mcp-stdio.d.ts +0 -5
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import {
|
|
2
|
+
prepareElicitation
|
|
3
|
+
} from "./chunk-CJ5F6UDG.js";
|
|
4
|
+
import {
|
|
5
|
+
failOnNetworkRefusal
|
|
6
|
+
} from "./chunk-I6Q7ZPKJ.js";
|
|
7
|
+
import {
|
|
8
|
+
accountProviderError,
|
|
9
|
+
bodyUpstreamError,
|
|
10
|
+
fromPromise,
|
|
11
|
+
httpProviderError,
|
|
12
|
+
providerErrorDetail
|
|
13
|
+
} from "./chunk-W6XCQAXP.js";
|
|
14
|
+
import {
|
|
15
|
+
ElicitationFailed,
|
|
16
|
+
McpCredentialsUnverified,
|
|
17
|
+
McpError,
|
|
18
|
+
McpHealthInput,
|
|
19
|
+
McpServerHeader,
|
|
20
|
+
McpToolMetadata,
|
|
21
|
+
McpToolResult,
|
|
22
|
+
McpToolsOptions,
|
|
23
|
+
RouterIcon,
|
|
24
|
+
ToolResultObservation,
|
|
25
|
+
defaultElicitationLimits,
|
|
26
|
+
defaultMcpClientLimits,
|
|
27
|
+
mcpHealthReserveMs,
|
|
28
|
+
mcpSdkTimerCeilingMs
|
|
29
|
+
} from "./chunk-WUPLD4VN.js";
|
|
30
|
+
import {
|
|
31
|
+
NetworkRefused,
|
|
32
|
+
ProviderError
|
|
33
|
+
} from "./chunk-Q4IZAUIF.js";
|
|
34
|
+
import {
|
|
35
|
+
jsonSchemaDecoder,
|
|
36
|
+
nestJsonSchema,
|
|
37
|
+
preparedJsonSchemaDecoder
|
|
38
|
+
} from "./chunk-3FHDX4KB.js";
|
|
39
|
+
import {
|
|
40
|
+
captureTelemetry,
|
|
41
|
+
owned
|
|
42
|
+
} from "./chunk-QWQM3CLH.js";
|
|
43
|
+
import {
|
|
44
|
+
FetchHttpClient_exports
|
|
45
|
+
} from "./chunk-PD6I2SDG.js";
|
|
46
|
+
import {
|
|
47
|
+
JsonObject
|
|
48
|
+
} from "./chunk-H2CJWXA3.js";
|
|
49
|
+
import {
|
|
50
|
+
HttpClientRequest_exports,
|
|
51
|
+
HttpClient_exports
|
|
52
|
+
} from "./chunk-EOPQ727L.js";
|
|
53
|
+
import {
|
|
54
|
+
Clock_exports,
|
|
55
|
+
Deferred_exports,
|
|
56
|
+
Effect_exports,
|
|
57
|
+
Exit_exports,
|
|
58
|
+
Match_exports,
|
|
59
|
+
Option_exports,
|
|
60
|
+
Predicate_exports,
|
|
61
|
+
Queue_exports,
|
|
62
|
+
Redacted_exports,
|
|
63
|
+
Ref_exports,
|
|
64
|
+
Schema_exports,
|
|
65
|
+
Stream_exports
|
|
66
|
+
} from "./chunk-IFJU5SYQ.js";
|
|
67
|
+
|
|
68
|
+
// src/implementation/mcp.ts
|
|
69
|
+
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
70
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
71
|
+
import {
|
|
72
|
+
ErrorCode as ErrorCode3,
|
|
73
|
+
McpError as ProtocolError3,
|
|
74
|
+
ToolListChangedNotificationSchema
|
|
75
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
76
|
+
import { SSEClientTransport, SseError } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
77
|
+
import {
|
|
78
|
+
StreamableHTTPClientTransport,
|
|
79
|
+
StreamableHTTPError
|
|
80
|
+
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
81
|
+
|
|
82
|
+
// src/implementation/mcp-tools.ts
|
|
83
|
+
var resultFields = {
|
|
84
|
+
content: { type: "array", items: { type: "object" } },
|
|
85
|
+
structuredContent: { type: "object" },
|
|
86
|
+
isError: { type: "boolean" },
|
|
87
|
+
_meta: { type: "object" }
|
|
88
|
+
};
|
|
89
|
+
var definitionReference = /^#\/(?:\$defs|definitions)\/[^/]+$/;
|
|
90
|
+
var locating = /* @__PURE__ */ new Set(["$id", "$anchor", "$dynamicAnchor", "$recursiveAnchor"]);
|
|
91
|
+
var onlyDefinitionReferences = (value) => Array.isArray(value) ? value.every(onlyDefinitionReferences) : value === null || typeof value !== "object" ? true : Object.entries(value).every(
|
|
92
|
+
([key, item]) => locating.has(key) || key === "$dynamicRef" || key === "$recursiveRef" ? false : key === "$ref" ? typeof item === "string" && definitionReference.test(item) : onlyDefinitionReferences(item)
|
|
93
|
+
);
|
|
94
|
+
var placed = (structuredContent, pointer) => {
|
|
95
|
+
if (onlyDefinitionReferences(structuredContent)) {
|
|
96
|
+
const { $defs, definitions, $schema: _draft, ...schema } = structuredContent;
|
|
97
|
+
return {
|
|
98
|
+
schema,
|
|
99
|
+
root: {
|
|
100
|
+
...$defs === void 0 ? {} : { $defs },
|
|
101
|
+
...definitions === void 0 ? {} : { definitions }
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
return { schema: nestJsonSchema(structuredContent, pointer), root: {} };
|
|
107
|
+
} catch {
|
|
108
|
+
return { schema: structuredContent, root: {} };
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var resultSchema = (structuredContent) => {
|
|
112
|
+
if (structuredContent === void 0)
|
|
113
|
+
return { type: "object", properties: resultFields, required: ["content"] };
|
|
114
|
+
const { schema, root } = placed(structuredContent, "#/anyOf/0/properties/structuredContent");
|
|
115
|
+
return {
|
|
116
|
+
// The envelope's keywords mean the same in every draft, so the server's draft applies.
|
|
117
|
+
...structuredContent.$schema === void 0 ? {} : { $schema: structuredContent.$schema },
|
|
118
|
+
...root,
|
|
119
|
+
anyOf: [
|
|
120
|
+
{
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: { ...resultFields, structuredContent: schema, isError: { const: false } },
|
|
123
|
+
required: ["content", "structuredContent"]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: { ...resultFields, isError: { const: true } },
|
|
128
|
+
required: ["content", "isError"]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
var adaptMcpTool = (client, tool) => Effect_exports.gen(function* () {
|
|
134
|
+
const decoder = yield* jsonSchemaDecoder(tool.inputSchema).pipe(
|
|
135
|
+
Effect_exports.mapError(() => new McpError({ phase: "schema", reason: "invalid_response" }))
|
|
136
|
+
);
|
|
137
|
+
const output = preparedJsonSchemaDecoder(resultSchema(tool.outputSchema));
|
|
138
|
+
const { outputSchema: _upstream, ...metadata } = tool;
|
|
139
|
+
const adapted = {
|
|
140
|
+
...metadata,
|
|
141
|
+
description: tool.description ?? tool.title ?? tool.name,
|
|
142
|
+
input: decoder,
|
|
143
|
+
output: output.decoder,
|
|
144
|
+
...tool.annotations?.readOnlyHint === void 0 ? {} : { readOnly: tool.annotations.readOnlyHint },
|
|
145
|
+
run: (context, input) => Effect_exports.gen(function* () {
|
|
146
|
+
const arguments_ = yield* Schema_exports.decodeUnknownEffect(decoder)(input).pipe(
|
|
147
|
+
Effect_exports.flatMap(Schema_exports.decodeUnknownEffect(JsonObject)),
|
|
148
|
+
Effect_exports.mapError(() => new McpError({ phase: "call", reason: "invalid_input" }))
|
|
149
|
+
);
|
|
150
|
+
yield* output.compile.pipe(
|
|
151
|
+
Effect_exports.mapError(() => new McpError({ phase: "schema", reason: "invalid_response" }))
|
|
152
|
+
);
|
|
153
|
+
const result = yield* client.call(tool.name, arguments_, context);
|
|
154
|
+
if (result.isError === true) {
|
|
155
|
+
(yield* ToolResultObservation).failed();
|
|
156
|
+
yield* Effect_exports.annotateCurrentSpan({
|
|
157
|
+
"executor.outcome": "failed",
|
|
158
|
+
"error.type": "McpToolError"
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
})
|
|
163
|
+
};
|
|
164
|
+
return adapted;
|
|
165
|
+
});
|
|
166
|
+
var adaptMcpTools = (client) => Effect_exports.gen(function* () {
|
|
167
|
+
const { tools: metadata } = yield* client.list;
|
|
168
|
+
const entries = yield* Effect_exports.forEach(
|
|
169
|
+
metadata,
|
|
170
|
+
(tool) => adaptMcpTool(client, tool).pipe(Effect_exports.map((adapted) => [tool.name, adapted]))
|
|
171
|
+
);
|
|
172
|
+
return Object.fromEntries(entries);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// src/implementation/mcp-client.ts
|
|
176
|
+
import {
|
|
177
|
+
ErrorCode as ErrorCode2,
|
|
178
|
+
ListToolsResultSchema,
|
|
179
|
+
McpError as ProtocolError2
|
|
180
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
181
|
+
|
|
182
|
+
// src/implementation/mcp-call.ts
|
|
183
|
+
import {
|
|
184
|
+
ElicitRequestSchema,
|
|
185
|
+
ElicitResultSchema,
|
|
186
|
+
ErrorCode,
|
|
187
|
+
McpError as ProtocolError
|
|
188
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
189
|
+
function callBudget(timeoutMs) {
|
|
190
|
+
return Effect_exports.gen(function* () {
|
|
191
|
+
const clock = yield* Clock_exports.Clock;
|
|
192
|
+
const changes = yield* Queue_exports.unbounded();
|
|
193
|
+
let remaining = timeoutMs, updated = clock.currentTimeMillisUnsafe(), waiting = 0;
|
|
194
|
+
const account = () => {
|
|
195
|
+
const now = clock.currentTimeMillisUnsafe();
|
|
196
|
+
if (waiting === 0) remaining -= Math.max(0, now - updated);
|
|
197
|
+
updated = now;
|
|
198
|
+
return remaining;
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
waitForInput: (input) => Effect_exports.acquireUseRelease(
|
|
202
|
+
Effect_exports.sync(() => {
|
|
203
|
+
account();
|
|
204
|
+
waiting++;
|
|
205
|
+
}).pipe(Effect_exports.andThen(Queue_exports.offer(changes, void 0))),
|
|
206
|
+
() => input,
|
|
207
|
+
() => Effect_exports.sync(() => {
|
|
208
|
+
account();
|
|
209
|
+
waiting--;
|
|
210
|
+
}).pipe(Effect_exports.andThen(Queue_exports.offer(changes, void 0)))
|
|
211
|
+
),
|
|
212
|
+
expired: Effect_exports.gen(function* () {
|
|
213
|
+
while (true) {
|
|
214
|
+
const left = account();
|
|
215
|
+
if (waiting === 0 && left <= 0)
|
|
216
|
+
return yield* new McpError({ phase: "call", reason: "timeout" });
|
|
217
|
+
if (waiting > 0) yield* Queue_exports.take(changes);
|
|
218
|
+
else yield* Queue_exports.take(changes).pipe(Effect_exports.raceFirst(Effect_exports.sleep(Math.max(1, left))));
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
var mcpCall = (client, name, input, context, timeoutMs, failure2) => Effect_exports.scoped(
|
|
225
|
+
Effect_exports.gen(function* () {
|
|
226
|
+
const budget = yield* callBudget(timeoutMs);
|
|
227
|
+
const failed = yield* Deferred_exports.make();
|
|
228
|
+
const runtime = yield* Effect_exports.context();
|
|
229
|
+
const callbacks = /* @__PURE__ */ new Set();
|
|
230
|
+
const lifetime = yield* Effect_exports.acquireRelease(
|
|
231
|
+
Effect_exports.sync(() => new AbortController()),
|
|
232
|
+
(controller) => (
|
|
233
|
+
// oxlint-disable-next-line executor/authored-code-through-adapter -- the MCP SDK's elicitation callbacks
|
|
234
|
+
Effect_exports.promise(async () => {
|
|
235
|
+
controller.abort();
|
|
236
|
+
client.removeRequestHandler("elicitation/create");
|
|
237
|
+
await Promise.allSettled(callbacks);
|
|
238
|
+
})
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
client.setRequestHandler(ElicitRequestSchema, (request, extra) => {
|
|
242
|
+
const signal = AbortSignal.any([lifetime.signal, extra.signal]);
|
|
243
|
+
const interaction = Effect_exports.gen(function* () {
|
|
244
|
+
const form = yield* prepareElicitation({
|
|
245
|
+
...request.params,
|
|
246
|
+
mode: request.params.mode ?? "form"
|
|
247
|
+
});
|
|
248
|
+
const deliver = context.elicit;
|
|
249
|
+
if (deliver === void 0) return yield* new ElicitationFailed({ reason: "unavailable" });
|
|
250
|
+
const answer = yield* budget.waitForInput(
|
|
251
|
+
fromPromise(
|
|
252
|
+
deliver,
|
|
253
|
+
"elicitation"
|
|
254
|
+
)(form.request).pipe(
|
|
255
|
+
Effect_exports.mapError(
|
|
256
|
+
(error) => Option_exports.getOrElse(
|
|
257
|
+
Schema_exports.decodeUnknownOption(ElicitationFailed)(error),
|
|
258
|
+
() => new ElicitationFailed({ reason: "transport" })
|
|
259
|
+
)
|
|
260
|
+
),
|
|
261
|
+
Effect_exports.timeoutOrElse({
|
|
262
|
+
duration: defaultElicitationLimits.timeoutMs,
|
|
263
|
+
orElse: () => Effect_exports.fail(new ElicitationFailed({ reason: "expired" }))
|
|
264
|
+
})
|
|
265
|
+
)
|
|
266
|
+
).pipe(Effect_exports.withSpan("provider.mcp.elicitation"));
|
|
267
|
+
return yield* form.respond(answer).pipe(
|
|
268
|
+
Effect_exports.flatMap(
|
|
269
|
+
(response) => Effect_exports.try({
|
|
270
|
+
try: () => ElicitResultSchema.parse(response),
|
|
271
|
+
catch: () => new ElicitationFailed({ reason: "invalid-response" })
|
|
272
|
+
})
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}).pipe(Effect_exports.tapError((error) => Deferred_exports.fail(failed, error)));
|
|
276
|
+
const callback = Effect_exports.runPromiseWith(runtime)(interaction, { signal }).catch(() => {
|
|
277
|
+
if (!lifetime.signal.aborted)
|
|
278
|
+
Effect_exports.runSync(Deferred_exports.fail(failed, new ElicitationFailed({ reason: "transport" })));
|
|
279
|
+
throw new ProtocolError(ErrorCode.InternalError, "User input could not be delivered");
|
|
280
|
+
});
|
|
281
|
+
callbacks.add(callback);
|
|
282
|
+
return callback.finally(() => callbacks.delete(callback));
|
|
283
|
+
});
|
|
284
|
+
return yield* Effect_exports.tryPromise({
|
|
285
|
+
try: (signal) => client.callTool({ name, arguments: input }, void 0, {
|
|
286
|
+
signal: AbortSignal.any([signal, lifetime.signal]),
|
|
287
|
+
timeout: mcpSdkTimerCeilingMs
|
|
288
|
+
}),
|
|
289
|
+
catch: (error) => failure2("call", error)
|
|
290
|
+
}).pipe(
|
|
291
|
+
Effect_exports.raceFirst(Deferred_exports.await(failed)),
|
|
292
|
+
Effect_exports.raceFirst(budget.expired),
|
|
293
|
+
Effect_exports.flatMap(
|
|
294
|
+
(result) => Schema_exports.decodeUnknownEffect(McpToolResult)(result).pipe(
|
|
295
|
+
Effect_exports.mapError(() => new McpError({ phase: "call", reason: "invalid_response" }))
|
|
296
|
+
)
|
|
297
|
+
),
|
|
298
|
+
Effect_exports.tap(
|
|
299
|
+
(result) => Effect_exports.annotateCurrentSpan("mcp.tool.is_error", result.isError === true)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
})
|
|
303
|
+
).pipe(
|
|
304
|
+
owned("upstream", "provider.mcp.request", {
|
|
305
|
+
kind: "client",
|
|
306
|
+
attributes: {
|
|
307
|
+
"rpc.system.name": "jsonrpc",
|
|
308
|
+
"rpc.method": "tools/call",
|
|
309
|
+
"mcp.tool.name": name
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// src/implementation/mcp-client.ts
|
|
315
|
+
var clientCodes = /* @__PURE__ */ new Set([
|
|
316
|
+
ErrorCode2.RequestTimeout,
|
|
317
|
+
ErrorCode2.ConnectionClosed
|
|
318
|
+
]);
|
|
319
|
+
var answeredError = (error) => {
|
|
320
|
+
if (!(error instanceof ProtocolError2) || clientCodes.has(error.code)) return void 0;
|
|
321
|
+
const prefix = `MCP error ${error.code}: `;
|
|
322
|
+
return bodyUpstreamError({
|
|
323
|
+
error: {
|
|
324
|
+
code: error.code,
|
|
325
|
+
message: error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
var mcpJsonSchemaValidator = {
|
|
330
|
+
getValidator(document) {
|
|
331
|
+
const decoder = Effect_exports.runSync(jsonSchemaDecoder(document));
|
|
332
|
+
return (input) => {
|
|
333
|
+
const result = Effect_exports.runSyncExit(Schema_exports.decodeUnknownEffect(decoder)(input));
|
|
334
|
+
if (Exit_exports.isFailure(result))
|
|
335
|
+
return {
|
|
336
|
+
valid: false,
|
|
337
|
+
data: void 0,
|
|
338
|
+
errorMessage: "Value does not match the supported JSON Schema"
|
|
339
|
+
};
|
|
340
|
+
return { valid: true, data: result.value, errorMessage: void 0 };
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
function mcpClient(withClient2, timeoutMs, failure2) {
|
|
345
|
+
const list = withClient2(
|
|
346
|
+
"discover",
|
|
347
|
+
(client) => Effect_exports.gen(function* () {
|
|
348
|
+
const tools = /* @__PURE__ */ new Map();
|
|
349
|
+
const cursors = /* @__PURE__ */ new Set();
|
|
350
|
+
let cursor;
|
|
351
|
+
do {
|
|
352
|
+
const page = yield* Effect_exports.tryPromise({
|
|
353
|
+
// This session only reads metadata. listTools() also eagerly compiles
|
|
354
|
+
// every output validator; mcpRouter validates the selected tool on call.
|
|
355
|
+
try: (signal) => client.request(
|
|
356
|
+
{ method: "tools/list", params: cursor === void 0 ? {} : { cursor } },
|
|
357
|
+
ListToolsResultSchema,
|
|
358
|
+
{ signal, timeout: timeoutMs }
|
|
359
|
+
),
|
|
360
|
+
catch: (error) => failure2("discover", error)
|
|
361
|
+
}).pipe(
|
|
362
|
+
owned("upstream", "provider.mcp.request", {
|
|
363
|
+
kind: "client",
|
|
364
|
+
attributes: { "rpc.system.name": "jsonrpc", "rpc.method": "tools/list" }
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
const metadata = yield* Schema_exports.decodeUnknownEffect(Schema_exports.Array(McpToolMetadata))(
|
|
368
|
+
page.tools
|
|
369
|
+
).pipe(
|
|
370
|
+
Effect_exports.mapError(() => new McpError({ phase: "discover", reason: "invalid_response" }))
|
|
371
|
+
);
|
|
372
|
+
for (const tool of metadata) {
|
|
373
|
+
if (!tool.name || tools.has(tool.name) || tools.size >= defaultMcpClientLimits.maxTools)
|
|
374
|
+
return yield* new McpError({ phase: "discover", reason: "invalid_response" });
|
|
375
|
+
tools.set(tool.name, tool);
|
|
376
|
+
}
|
|
377
|
+
cursor = page.nextCursor;
|
|
378
|
+
if (cursor !== void 0) {
|
|
379
|
+
if (cursors.has(cursor) || cursors.size >= defaultMcpClientLimits.maxPaginationCursors)
|
|
380
|
+
return yield* new McpError({ phase: "discover", reason: "invalid_response" });
|
|
381
|
+
cursors.add(cursor);
|
|
382
|
+
}
|
|
383
|
+
} while (cursor !== void 0);
|
|
384
|
+
const info = client.getServerVersion();
|
|
385
|
+
const instructions = client.getInstructions();
|
|
386
|
+
const icons = info?.icons?.flatMap(
|
|
387
|
+
(icon) => Option_exports.toArray(Schema_exports.decodeUnknownOption(RouterIcon)(icon))
|
|
388
|
+
);
|
|
389
|
+
const server = Schema_exports.decodeUnknownOption(McpServerHeader)({
|
|
390
|
+
...info?.name === void 0 ? {} : { name: info.name },
|
|
391
|
+
...info?.title === void 0 ? {} : { title: info.title },
|
|
392
|
+
...info?.version === void 0 ? {} : { version: info.version },
|
|
393
|
+
...info?.description === void 0 ? {} : { description: info.description },
|
|
394
|
+
...info?.websiteUrl === void 0 ? {} : { websiteUrl: info.websiteUrl },
|
|
395
|
+
...icons === void 0 ? {} : { icons },
|
|
396
|
+
...instructions === void 0 ? {} : { instructions }
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
tools: [...tools.values()],
|
|
400
|
+
server: Option_exports.getOrElse(server, () => ({}))
|
|
401
|
+
};
|
|
402
|
+
})
|
|
403
|
+
).pipe(owned("upstream", "provider.mcp.discover"));
|
|
404
|
+
const check = withClient2(
|
|
405
|
+
"discover",
|
|
406
|
+
(client) => (
|
|
407
|
+
// oxlint-disable-next-line executor/authored-code-through-adapter -- MCP SDK
|
|
408
|
+
Effect_exports.tryPromise({
|
|
409
|
+
try: (signal) => client.request({ method: "tools/list", params: {} }, ListToolsResultSchema, {
|
|
410
|
+
signal,
|
|
411
|
+
timeout: timeoutMs
|
|
412
|
+
}),
|
|
413
|
+
catch: (error) => failure2("discover", error)
|
|
414
|
+
}).pipe(
|
|
415
|
+
Effect_exports.asVoid,
|
|
416
|
+
owned("upstream", "provider.mcp.request", {
|
|
417
|
+
kind: "client",
|
|
418
|
+
attributes: { "rpc.system.name": "jsonrpc", "rpc.method": "tools/list" }
|
|
419
|
+
})
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
).pipe(owned("upstream", "provider.mcp.check"));
|
|
423
|
+
const call = (name, input, context) => withClient2("call", (client) => mcpCall(client, name, input, context, timeoutMs, failure2)).pipe(
|
|
424
|
+
owned("upstream", "provider.mcp.call", { attributes: { "mcp.tool.name": name } })
|
|
425
|
+
);
|
|
426
|
+
return { list, check, call };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/implementation/mcp.ts
|
|
430
|
+
var unreadable = (error) => error instanceof SyntaxError || error instanceof Error && Predicate_exports.hasProperty(error, "issues") && Array.isArray(error.issues);
|
|
431
|
+
var failure = (phase, error) => {
|
|
432
|
+
if (Schema_exports.is(ProviderError)(error) || Schema_exports.is(McpError)(error) || Schema_exports.is(NetworkRefused)(error))
|
|
433
|
+
return error;
|
|
434
|
+
const code = error instanceof UnauthorizedError ? 401 : error instanceof StreamableHTTPError || error instanceof SseError ? error.code : void 0;
|
|
435
|
+
if (code !== void 0 && code >= 300 && code <= 599)
|
|
436
|
+
return httpProviderError(code) ?? new McpError({ phase, reason: "request", status: code });
|
|
437
|
+
const upstream = answeredError(error);
|
|
438
|
+
return new McpError({
|
|
439
|
+
phase,
|
|
440
|
+
reason: code !== void 0 || unreadable(error) ? "invalid_response" : error instanceof ProtocolError3 && error.code === ErrorCode3.RequestTimeout ? "timeout" : "request",
|
|
441
|
+
...upstream === void 0 ? {} : { upstream }
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
var errorBodyLimits = { maxBytes: 65536, readTimeoutMs: 2e3 };
|
|
445
|
+
var errorBody = (response) => Effect_exports.gen(function* () {
|
|
446
|
+
const json = /^application\/(?:[\w.-]+\+)?json$/i.test(
|
|
447
|
+
response.headers["content-type"]?.split(";")[0]?.trim() ?? ""
|
|
448
|
+
);
|
|
449
|
+
const length = response.headers["content-length"];
|
|
450
|
+
if (!json || length !== void 0 && Number(length) > errorBodyLimits.maxBytes)
|
|
451
|
+
return { body: Stream_exports.toReadableStream(response.stream), upstream: void 0 };
|
|
452
|
+
const unreadable2 = new McpError({ phase: "transport", reason: "invalid_response" });
|
|
453
|
+
const read = yield* response.stream.pipe(
|
|
454
|
+
Stream_exports.mapError(() => unreadable2),
|
|
455
|
+
Stream_exports.limitBytes(errorBodyLimits.maxBytes, () => Stream_exports.fail(unreadable2)),
|
|
456
|
+
Stream_exports.decodeText,
|
|
457
|
+
Stream_exports.mkString,
|
|
458
|
+
Effect_exports.timeout(errorBodyLimits.readTimeoutMs),
|
|
459
|
+
Effect_exports.option
|
|
460
|
+
);
|
|
461
|
+
if (Option_exports.isNone(read)) return { body: null, upstream: void 0 };
|
|
462
|
+
const parsed = Schema_exports.decodeUnknownOption(Schema_exports.fromJsonString(Schema_exports.Json))(read.value);
|
|
463
|
+
return {
|
|
464
|
+
body: read.value,
|
|
465
|
+
upstream: Option_exports.isSome(parsed) ? bodyUpstreamError(parsed.value) : void 0
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
var transportFetch = (connection, telemetry, rejected, answered) => (url, init) => Effect_exports.runPromiseWith(telemetry.context)(
|
|
469
|
+
Effect_exports.gen(function* () {
|
|
470
|
+
const target = yield* Effect_exports.try({
|
|
471
|
+
try: () => new URL(url),
|
|
472
|
+
catch: () => new McpError({ phase: "transport", reason: "invalid_response" })
|
|
473
|
+
});
|
|
474
|
+
if (connection.url.username || connection.url.password)
|
|
475
|
+
return yield* new McpError({ phase: "transport", reason: "invalid_input" });
|
|
476
|
+
if (target.origin !== connection.url.origin || target.username || target.password)
|
|
477
|
+
return yield* new McpError({ phase: "transport", reason: "invalid_response" });
|
|
478
|
+
const request = yield* Effect_exports.try({
|
|
479
|
+
try: () => {
|
|
480
|
+
const headers = new Headers(Redacted_exports.value(connection.headers));
|
|
481
|
+
new Headers(init?.headers).forEach((value, name) => headers.set(name, value));
|
|
482
|
+
if (!headers.has("user-agent")) headers.set("user-agent", "Executor");
|
|
483
|
+
return HttpClientRequest_exports.fromWeb(new Request(target, { ...init, headers }));
|
|
484
|
+
},
|
|
485
|
+
// The provider's headers cannot form an HTTP request, such as a name with a space.
|
|
486
|
+
catch: () => new McpError({ phase: "transport", reason: "invalid_input" })
|
|
487
|
+
});
|
|
488
|
+
const client = yield* HttpClient_exports.HttpClient;
|
|
489
|
+
const response = yield* client.execute(request);
|
|
490
|
+
yield* failOnNetworkRefusal(response).pipe(
|
|
491
|
+
Effect_exports.tapError((refused) => Deferred_exports.fail(rejected, refused))
|
|
492
|
+
);
|
|
493
|
+
if (response.status < 400)
|
|
494
|
+
return new Response(
|
|
495
|
+
[204, 205, 304].includes(response.status) ? null : Stream_exports.toReadableStream(response.stream),
|
|
496
|
+
{
|
|
497
|
+
status: response.status,
|
|
498
|
+
headers: response.headers
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
const { body, upstream } = yield* errorBody(response);
|
|
502
|
+
const provider = httpProviderError(response.status, response.headers);
|
|
503
|
+
if (provider !== void 0) {
|
|
504
|
+
const refused = providerErrorDetail(provider, { upstream });
|
|
505
|
+
yield* Deferred_exports.fail(rejected, refused);
|
|
506
|
+
return yield* refused;
|
|
507
|
+
}
|
|
508
|
+
const session = new Headers(init?.headers).has("mcp-session-id");
|
|
509
|
+
yield* Ref_exports.update(
|
|
510
|
+
answered,
|
|
511
|
+
(responses) => new Map(responses).set(response.status, { upstream, session })
|
|
512
|
+
);
|
|
513
|
+
return new Response(body, { status: response.status, headers: response.headers });
|
|
514
|
+
}).pipe(
|
|
515
|
+
Effect_exports.provide(FetchHttpClient_exports.layer),
|
|
516
|
+
Effect_exports.provideService(FetchHttpClient_exports.RequestInit, { redirect: "manual" }),
|
|
517
|
+
Effect_exports.provideService(
|
|
518
|
+
FetchHttpClient_exports.Fetch,
|
|
519
|
+
(url2, options) => globalThis.fetch(url2, {
|
|
520
|
+
...options,
|
|
521
|
+
signal: AbortSignal.any([
|
|
522
|
+
...options?.signal ? [options.signal] : [],
|
|
523
|
+
...init?.signal ? [init.signal] : []
|
|
524
|
+
])
|
|
525
|
+
})
|
|
526
|
+
),
|
|
527
|
+
Effect_exports.mapError((error) => failure("transport", error))
|
|
528
|
+
),
|
|
529
|
+
init?.signal ? { signal: init.signal } : {}
|
|
530
|
+
);
|
|
531
|
+
var explain = (error, phase, responses) => {
|
|
532
|
+
if (Schema_exports.is(ProviderError)(error)) return providerErrorDetail(error, { phase });
|
|
533
|
+
if (phase === "call" && Schema_exports.is(McpError)(error) && error.phase === "transport" && error.reason === "request")
|
|
534
|
+
return new McpError({
|
|
535
|
+
phase,
|
|
536
|
+
reason: error.reason,
|
|
537
|
+
...error.status === void 0 ? {} : { status: error.status },
|
|
538
|
+
...error.upstream === void 0 ? {} : { upstream: error.upstream }
|
|
539
|
+
});
|
|
540
|
+
if (!Schema_exports.is(McpError)(error) || error.status === void 0) return error;
|
|
541
|
+
const response = responses.get(error.status);
|
|
542
|
+
if (response === void 0 || (error.upstream !== void 0 || response.upstream === void 0) && !response.session)
|
|
543
|
+
return error;
|
|
544
|
+
const upstream = error.upstream ?? response.upstream;
|
|
545
|
+
return new McpError({
|
|
546
|
+
phase: error.phase,
|
|
547
|
+
reason: error.reason,
|
|
548
|
+
status: error.status,
|
|
549
|
+
...upstream === void 0 ? {} : { upstream },
|
|
550
|
+
...response.session ? { session: true } : {}
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
var explained = (phase, answered) => (error) => Ref_exports.get(answered).pipe(
|
|
554
|
+
Effect_exports.flatMap((responses) => Effect_exports.fail(explain(error, phase, responses)))
|
|
555
|
+
);
|
|
556
|
+
function withClient(connection, mode, use, changed) {
|
|
557
|
+
const attempt = (kind) => Effect_exports.scoped(
|
|
558
|
+
Effect_exports.gen(function* () {
|
|
559
|
+
const telemetry = yield* captureTelemetry;
|
|
560
|
+
const rejected = yield* Deferred_exports.make();
|
|
561
|
+
const answered = yield* Ref_exports.make(/* @__PURE__ */ new Map());
|
|
562
|
+
const pending = /* @__PURE__ */ new Set();
|
|
563
|
+
const { client, transport } = yield* Effect_exports.acquireRelease(
|
|
564
|
+
Effect_exports.sync(() => {
|
|
565
|
+
const fetch = transportFetch(connection, telemetry, rejected, answered);
|
|
566
|
+
const client2 = new Client(
|
|
567
|
+
{ name: "executor-apps", version: "0.1.0" },
|
|
568
|
+
{
|
|
569
|
+
jsonSchemaValidator: mcpJsonSchemaValidator,
|
|
570
|
+
capabilities: mode === "call" ? { elicitation: { form: {} } } : {}
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
if (changed !== void 0) {
|
|
574
|
+
client2.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
575
|
+
const task = Effect_exports.runPromiseWith(telemetry.context)(
|
|
576
|
+
changed.pipe(
|
|
577
|
+
Effect_exports.timeout("5 seconds"),
|
|
578
|
+
Effect_exports.catchCause(() => Effect_exports.logWarning("MCP catalog invalidation failed"))
|
|
579
|
+
)
|
|
580
|
+
);
|
|
581
|
+
pending.add(task);
|
|
582
|
+
return task.finally(() => pending.delete(task));
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
client: client2,
|
|
587
|
+
transport: kind === "http" ? new StreamableHTTPClientTransport(connection.url, {
|
|
588
|
+
fetch,
|
|
589
|
+
reconnectionOptions: {
|
|
590
|
+
maxRetries: 0,
|
|
591
|
+
initialReconnectionDelay: 1e3,
|
|
592
|
+
maxReconnectionDelay: 1e3,
|
|
593
|
+
reconnectionDelayGrowFactor: 1
|
|
594
|
+
}
|
|
595
|
+
}) : new SSEClientTransport(connection.url, { fetch })
|
|
596
|
+
};
|
|
597
|
+
}),
|
|
598
|
+
({ client: client2, transport: transport2 }) => Effect_exports.gen(function* () {
|
|
599
|
+
client2.removeNotificationHandler("notifications/tools/list_changed");
|
|
600
|
+
yield* Effect_exports.promise(async () => {
|
|
601
|
+
await Promise.allSettled(pending);
|
|
602
|
+
});
|
|
603
|
+
if (transport2 instanceof StreamableHTTPClientTransport && transport2.sessionId !== void 0) {
|
|
604
|
+
yield* Effect_exports.tryPromise(() => transport2.terminateSession()).pipe(
|
|
605
|
+
Effect_exports.timeout(defaultMcpClientLimits.cleanupTimeoutMs),
|
|
606
|
+
Effect_exports.ignore
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
yield* Effect_exports.tryPromise(() => client2.close()).pipe(
|
|
610
|
+
Effect_exports.timeout(defaultMcpClientLimits.cleanupTimeoutMs),
|
|
611
|
+
Effect_exports.ignore
|
|
612
|
+
);
|
|
613
|
+
}).pipe(owned("upstream", "provider.mcp.close"))
|
|
614
|
+
);
|
|
615
|
+
const wire = transport;
|
|
616
|
+
yield* Effect_exports.tryPromise({
|
|
617
|
+
try: (signal) => client.connect(wire, { signal, timeout: connection.timeoutMs }),
|
|
618
|
+
catch: (error) => failure("connect", error)
|
|
619
|
+
}).pipe(
|
|
620
|
+
Effect_exports.raceFirst(Deferred_exports.await(rejected)),
|
|
621
|
+
Effect_exports.catch(explained("connect", answered)),
|
|
622
|
+
Effect_exports.timeout(connection.timeoutMs),
|
|
623
|
+
owned("upstream", "provider.mcp.connect")
|
|
624
|
+
);
|
|
625
|
+
return yield* use(client).pipe(
|
|
626
|
+
Effect_exports.raceFirst(Deferred_exports.await(rejected)),
|
|
627
|
+
Effect_exports.catch(explained(mode, answered))
|
|
628
|
+
);
|
|
629
|
+
})
|
|
630
|
+
).pipe(
|
|
631
|
+
owned("upstream", "provider.mcp.session", {
|
|
632
|
+
attributes: {
|
|
633
|
+
"mcp.transport": kind,
|
|
634
|
+
"mcp.operation": mode,
|
|
635
|
+
"server.address": connection.url.hostname
|
|
636
|
+
}
|
|
637
|
+
})
|
|
638
|
+
);
|
|
639
|
+
return attempt("http").pipe(
|
|
640
|
+
// Fallback is only for negotiation. Tool calls are never automatically replayed.
|
|
641
|
+
Effect_exports.catch(
|
|
642
|
+
(error) => Schema_exports.is(McpError)(error) && error.phase === "connect" && (error.status === 404 || error.status === 405) ? attempt("sse") : Effect_exports.fail(error)
|
|
643
|
+
),
|
|
644
|
+
(operation) => mode === "discover" ? operation.pipe(Effect_exports.timeout(connection.timeoutMs)) : operation,
|
|
645
|
+
Effect_exports.catchTag(
|
|
646
|
+
"TimeoutError",
|
|
647
|
+
() => Effect_exports.fail(new McpError({ phase: "transport", reason: "timeout" }))
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
var mcpClientEffect = (input, changed) => Effect_exports.gen(function* () {
|
|
652
|
+
const options = yield* Schema_exports.decodeUnknownEffect(McpToolsOptions)(input).pipe(
|
|
653
|
+
Effect_exports.mapError(() => new McpError({ phase: "connect", reason: "invalid_input" }))
|
|
654
|
+
);
|
|
655
|
+
const url = new URL(options.url);
|
|
656
|
+
if (url.username || url.password || url.hash)
|
|
657
|
+
return yield* new McpError({ phase: "connect", reason: "invalid_input" });
|
|
658
|
+
const connection = {
|
|
659
|
+
url,
|
|
660
|
+
headers: Redacted_exports.make({ ...options.headers }),
|
|
661
|
+
timeoutMs: options.timeoutMs ?? defaultMcpClientLimits.timeoutMs
|
|
662
|
+
};
|
|
663
|
+
return mcpClient(
|
|
664
|
+
(mode, use) => withClient(connection, mode, use, changed).pipe(
|
|
665
|
+
Effect_exports.mapError(
|
|
666
|
+
(error) => options.accountId === void 0 ? error : accountProviderError(error, options.accountId)
|
|
667
|
+
)
|
|
668
|
+
),
|
|
669
|
+
connection.timeoutMs,
|
|
670
|
+
failure
|
|
671
|
+
);
|
|
672
|
+
});
|
|
673
|
+
var refusesCredentials = (error) => Schema_exports.is(ProviderError)(error) && Match_exports.value(error.reason).pipe(
|
|
674
|
+
Match_exports.whenOr("unauthorized", "forbidden", "rejected", () => true),
|
|
675
|
+
Match_exports.whenOr("rate_limited", "unavailable", () => false),
|
|
676
|
+
Match_exports.exhaustive
|
|
677
|
+
);
|
|
678
|
+
var mcpHealthEffect = (check, options) => Effect_exports.gen(function* () {
|
|
679
|
+
const { deadline, timeoutMs, ...parsed } = yield* Schema_exports.decodeUnknownEffect(McpHealthInput)({
|
|
680
|
+
...options,
|
|
681
|
+
accountId: check.account.id,
|
|
682
|
+
signal: check.signal,
|
|
683
|
+
deadline: check.deadline
|
|
684
|
+
}).pipe(Effect_exports.mapError(() => new McpError({ phase: "connect", reason: "invalid_input" })));
|
|
685
|
+
const end = Math.min(
|
|
686
|
+
(yield* Clock_exports.currentTimeMillis) + (timeoutMs ?? defaultMcpClientLimits.timeoutMs),
|
|
687
|
+
deadline === void 0 ? Number.POSITIVE_INFINITY : deadline - mcpHealthReserveMs
|
|
688
|
+
);
|
|
689
|
+
const remaining = Effect_exports.flatMap(
|
|
690
|
+
Clock_exports.currentTimeMillis,
|
|
691
|
+
(now) => end - now >= 1 ? Effect_exports.succeed(Math.floor(end - now)) : Effect_exports.fail(new McpError({ phase: "transport", reason: "timeout" }))
|
|
692
|
+
);
|
|
693
|
+
yield* (yield* mcpClientEffect({ ...parsed, timeoutMs: yield* remaining })).check;
|
|
694
|
+
return yield* remaining.pipe(
|
|
695
|
+
Effect_exports.flatMap(
|
|
696
|
+
(timeoutMs2) => mcpClientEffect({ url: parsed.url, signal: parsed.signal, timeoutMs: timeoutMs2 })
|
|
697
|
+
),
|
|
698
|
+
Effect_exports.flatMap((anonymous) => anonymous.check),
|
|
699
|
+
Effect_exports.matchEffect({
|
|
700
|
+
onSuccess: () => Effect_exports.fail(new McpCredentialsUnverified({ anonymous: "answered" })),
|
|
701
|
+
// Executor's network refused the attempt, so the server never answered it.
|
|
702
|
+
onFailure: (error) => Schema_exports.is(NetworkRefused)(error) ? Effect_exports.fail(error) : refusesCredentials(error) ? Effect_exports.void : Effect_exports.fail(new McpCredentialsUnverified({ anonymous: error }))
|
|
703
|
+
}),
|
|
704
|
+
owned("upstream", "provider.mcp.anonymous")
|
|
705
|
+
);
|
|
706
|
+
}).pipe(owned("upstream", "provider.mcp.health"));
|
|
707
|
+
var mcpToolsEffect = (input) => mcpClientEffect(input).pipe(Effect_exports.flatMap(adaptMcpTools));
|
|
708
|
+
|
|
709
|
+
export {
|
|
710
|
+
adaptMcpTool,
|
|
711
|
+
mcpClientEffect,
|
|
712
|
+
mcpHealthEffect,
|
|
713
|
+
mcpToolsEffect
|
|
714
|
+
};
|