tubeless 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/mcp-http.d.ts +9 -0
- package/dist/agent/mcp-http.js +129 -0
- package/dist/agent/mcp-http.js.map +1 -0
- package/dist/agent/mcp-schema.d.ts +12 -0
- package/dist/agent/mcp-schema.js +264 -0
- package/dist/agent/mcp-schema.js.map +1 -0
- package/dist/agent/mcp-session.d.ts +38 -0
- package/dist/agent/mcp-session.js +123 -0
- package/dist/agent/mcp-session.js.map +1 -0
- package/dist/agent/mcp-stdio.d.ts +11 -0
- package/dist/agent/mcp-stdio.js +97 -0
- package/dist/agent/mcp-stdio.js.map +1 -0
- package/dist/agent/mcp.d.ts +37 -0
- package/dist/agent/mcp.js +148 -0
- package/dist/agent/mcp.js.map +1 -0
- package/dist/cli/cli-command.js +3 -5
- package/dist/cli/cli-command.js.map +1 -1
- package/dist/cli/cli-exit.d.ts +5 -0
- package/dist/cli/cli-exit.js +13 -0
- package/dist/cli/cli-exit.js.map +1 -1
- package/dist/cli/cli-parser.js +74 -35
- package/dist/cli/cli-parser.js.map +1 -1
- package/dist/cli/cli-pipeline-command.js +2 -2
- package/dist/cli/cli-pipeline-command.js.map +1 -1
- package/dist/cloudflare/cloudflare-access.js +1 -1
- package/dist/cloudflare/cloudflare-access.js.map +1 -1
- package/dist/cloudflare/cloudflare-studio-client.js +1 -1
- package/dist/cloudflare/cloudflare-studio.js.map +1 -1
- package/dist/cloudflare/cloudflare.js +1 -1
- package/dist/cloudflare/cloudflare.js.map +1 -1
- package/dist/core/pipeline-plan.js +36 -3
- package/dist/core/pipeline-plan.js.map +1 -1
- package/dist/hosting/hosting-commands.d.ts +1 -1
- package/dist/hosting/hosting-commands.js +2 -5
- package/dist/hosting/hosting-commands.js.map +1 -1
- package/dist/hosting/hosting-studio.d.ts +1 -1
- package/dist/hosting/hosting-studio.js +3 -3
- package/dist/hosting/hosting-studio.js.map +1 -1
- package/dist/project/pipeline-project.js +5 -3
- package/dist/project/pipeline-project.js.map +1 -1
- package/dist/project/project-compiler.js +5 -4
- package/dist/project/project-compiler.js.map +1 -1
- package/dist/studio/browser/app.d.ts +32 -0
- package/dist/studio/browser/app.js +315 -0
- package/dist/studio/browser/app.js.map +1 -0
- package/dist/studio/{run-store-ui-artifacts.d.ts → browser/components/artifacts.d.ts} +1 -1
- package/dist/studio/{run-store-ui-artifacts.js → browser/components/artifacts.js} +2 -2
- package/dist/studio/browser/components/artifacts.js.map +1 -0
- package/dist/studio/{run-store-ui-common.d.ts → browser/components/common.d.ts} +1 -1
- package/dist/studio/{run-store-ui-common.js → browser/components/common.js} +1 -1
- package/dist/studio/browser/components/common.js.map +1 -0
- package/dist/studio/{run-store-ui-debugging.d.ts → browser/components/debugging.d.ts} +6 -1
- package/dist/studio/{run-store-ui-debugging.js → browser/components/debugging.js} +6 -2
- package/dist/studio/browser/components/debugging.js.map +1 -0
- package/dist/studio/{run-store-ui-definitions.d.ts → browser/components/definitions.d.ts} +4 -4
- package/dist/studio/{run-store-ui-definitions.js → browser/components/definitions.js} +5 -5
- package/dist/studio/browser/components/definitions.js.map +1 -0
- package/dist/studio/{run-store-ui-launch.d.ts → browser/components/launch.d.ts} +6 -5
- package/dist/studio/{run-store-ui-launch.js → browser/components/launch.js} +4 -4
- package/dist/studio/browser/components/launch.js.map +1 -0
- package/dist/studio/{run-store-ui-metadata.d.ts → browser/components/metadata.d.ts} +1 -1
- package/dist/studio/{run-store-ui-metadata.js → browser/components/metadata.js} +1 -1
- package/dist/studio/browser/components/metadata.js.map +1 -0
- package/dist/studio/browser/components/pipeline.d.ts +27 -0
- package/dist/studio/browser/components/pipeline.js +108 -0
- package/dist/studio/browser/components/pipeline.js.map +1 -0
- package/dist/studio/browser/components/pipelines.d.ts +4 -0
- package/dist/studio/{run-store-ui-pipelines.js → browser/components/pipelines.js} +3 -10
- package/dist/studio/browser/components/pipelines.js.map +1 -0
- package/dist/studio/browser/components/rail.d.ts +34 -0
- package/dist/studio/browser/components/rail.js +69 -0
- package/dist/studio/browser/components/rail.js.map +1 -0
- package/dist/studio/browser/components/runs.d.ts +92 -0
- package/dist/studio/browser/components/runs.js +246 -0
- package/dist/studio/browser/components/runs.js.map +1 -0
- package/dist/studio/{run-store-ui-steps.d.ts → browser/components/steps.d.ts} +5 -2
- package/dist/studio/{run-store-ui-steps.js → browser/components/steps.js} +10 -4
- package/dist/studio/browser/components/steps.js.map +1 -0
- package/dist/studio/{run-store-ui-data-controller.d.ts → browser/data-controller.d.ts} +8 -4
- package/dist/studio/{run-store-ui-data-controller.js → browser/data-controller.js} +20 -14
- package/dist/studio/browser/data-controller.js.map +1 -0
- package/dist/studio/browser/entry.js +4 -0
- package/dist/studio/browser/graph/dependencies.d.ts +15 -0
- package/dist/studio/browser/graph/dependencies.js +43 -0
- package/dist/studio/browser/graph/dependencies.js.map +1 -0
- package/dist/studio/{run-store-ui-graph.d.ts → browser/graph/graph.d.ts} +11 -10
- package/dist/studio/browser/graph/graph.js +286 -0
- package/dist/studio/browser/graph/graph.js.map +1 -0
- package/dist/studio/browser/graph/inspector.d.ts +28 -0
- package/dist/studio/browser/graph/inspector.js +66 -0
- package/dist/studio/browser/graph/inspector.js.map +1 -0
- package/dist/studio/{run-store-ui-graph-layout.js → browser/graph/layout.js} +1 -1
- package/dist/studio/browser/graph/layout.js.map +1 -0
- package/dist/studio/browser/graph/scene.d.ts +87 -0
- package/dist/studio/browser/graph/scene.js +262 -0
- package/dist/studio/browser/graph/scene.js.map +1 -0
- package/dist/studio/{run-store-ui-resource.js → browser/resource.js} +1 -1
- package/dist/studio/browser/resource.js.map +1 -0
- package/dist/studio/{run-store-ui-client-transport.d.ts → browser/transport.d.ts} +7 -5
- package/dist/studio/{run-store-ui-client-transport.js → browser/transport.js} +5 -3
- package/dist/studio/browser/transport.js.map +1 -0
- package/dist/studio/{run-store-ui-api.d.ts → server/api.d.ts} +3 -3
- package/dist/studio/{run-store-ui-api.js → server/api.js} +8 -5
- package/dist/studio/server/api.js.map +1 -0
- package/dist/studio/{run-store-ui-children.d.ts → server/children.d.ts} +2 -2
- package/dist/studio/{run-store-ui-children.js → server/children.js} +2 -2
- package/dist/studio/server/children.js.map +1 -0
- package/dist/studio/{run-store-ui-page-node.js → server/client-script.js} +3 -3
- package/dist/studio/server/client-script.js.map +1 -0
- package/dist/studio/{run-store-ui-handler.d.ts → server/handler.d.ts} +1 -1
- package/dist/studio/{run-store-ui-handler.js → server/handler.js} +3 -3
- package/dist/studio/server/handler.js.map +1 -0
- package/dist/studio/server/history.d.ts +21 -0
- package/dist/studio/server/history.js +149 -0
- package/dist/studio/server/history.js.map +1 -0
- package/dist/studio/{run-store-ui-hosting.js → server/hosting.js} +2 -2
- package/dist/studio/server/hosting.js.map +1 -0
- package/dist/studio/{run-store-ui-http.js → server/http.js} +1 -1
- package/dist/studio/server/http.js.map +1 -0
- package/dist/studio/{run-store-ui.d.ts → server/listener.d.ts} +4 -4
- package/dist/studio/{run-store-ui.js → server/listener.js} +7 -7
- package/dist/studio/server/listener.js.map +1 -0
- package/dist/studio/{run-store-ui-node.js → server/node-http.js} +1 -1
- package/dist/studio/server/node-http.js.map +1 -0
- package/dist/studio/{run-store-ui-page.js → server/page.js} +103 -49
- package/dist/studio/server/page.js.map +1 -0
- package/dist/studio/server/run-details.d.ts +15 -0
- package/dist/studio/server/run-details.js +51 -0
- package/dist/studio/server/run-details.js.map +1 -0
- package/dist/studio/{run-store-ui-state.d.ts → server/state.d.ts} +3 -8
- package/dist/studio/server/state.js +96 -0
- package/dist/studio/server/state.js.map +1 -0
- package/dist/studio/shared/limits.d.ts +7 -0
- package/dist/studio/shared/limits.js +8 -0
- package/dist/studio/shared/limits.js.map +1 -0
- package/dist/studio/{run-store-ui-protocol.d.ts → shared/protocol.d.ts} +2 -2
- package/dist/studio/{run-store-ui-protocol.js → shared/protocol.js} +2 -2
- package/dist/studio/shared/protocol.js.map +1 -0
- package/dist/studio/shared/route.d.ts +21 -0
- package/dist/studio/shared/route.js +51 -0
- package/dist/studio/shared/route.js.map +1 -0
- package/dist/studio/{run-store-ui-schema.d.ts → shared/schema.d.ts} +37 -15
- package/dist/studio/{run-store-ui-schema.js → shared/schema.js} +17 -8
- package/dist/studio/shared/schema.js.map +1 -0
- package/dist/utilities/suggest.d.ts +14 -0
- package/dist/utilities/suggest.js +74 -0
- package/dist/utilities/suggest.js.map +1 -0
- package/dist/workbench/{workbench-agent.d.ts → agent/command.d.ts} +1 -1
- package/dist/workbench/{workbench-agent.js → agent/command.js} +7 -7
- package/dist/workbench/agent/command.js.map +1 -0
- package/dist/workbench/{workbench-agent-commands.js → agent/commands.js} +1 -1
- package/dist/workbench/agent/commands.js.map +1 -0
- package/dist/workbench/{workbench-agent-environment.d.ts → agent/environment.d.ts} +1 -1
- package/dist/workbench/{workbench-agent-environment.js → agent/environment.js} +2 -2
- package/dist/workbench/agent/environment.js.map +1 -0
- package/dist/workbench/{workbench-agent-history.d.ts → agent/history.d.ts} +1 -1
- package/dist/workbench/{workbench-agent-history.js → agent/history.js} +2 -2
- package/dist/workbench/agent/history.js.map +1 -0
- package/dist/workbench/{workbench-agent-input.d.ts → agent/input.d.ts} +1 -1
- package/dist/workbench/{workbench-agent-input.js → agent/input.js} +3 -3
- package/dist/workbench/agent/input.js.map +1 -0
- package/dist/workbench/{workbench-agent-prompt.d.ts → agent/prompt.d.ts} +2 -2
- package/dist/workbench/{workbench-agent-prompt.js → agent/prompt.js} +7 -7
- package/dist/workbench/agent/prompt.js.map +1 -0
- package/dist/workbench/{workbench-agent-session.d.ts → agent/session.d.ts} +2 -2
- package/dist/workbench/{workbench-agent-session.js → agent/session.js} +7 -7
- package/dist/workbench/agent/session.js.map +1 -0
- package/dist/workbench/cloud/auth.d.ts +3 -0
- package/dist/workbench/{workbench-auth.js → cloud/auth.js} +7 -7
- package/dist/workbench/cloud/auth.js.map +1 -0
- package/dist/workbench/{cloud-client.d.ts → cloud/client.d.ts} +1 -1
- package/dist/workbench/{cloud-client.js → cloud/client.js} +4 -4
- package/dist/workbench/cloud/client.js.map +1 -0
- package/dist/workbench/cloud/command.d.ts +3 -0
- package/dist/workbench/{workbench-cloud.js → cloud/command.js} +9 -9
- package/dist/workbench/cloud/command.js.map +1 -0
- package/dist/workbench/{cloud-config.js → cloud/config.js} +1 -1
- package/dist/workbench/cloud/config.js.map +1 -0
- package/dist/workbench/{cloud-credentials.js → cloud/credentials.js} +3 -3
- package/dist/workbench/cloud/credentials.js.map +1 -0
- package/dist/workbench/{cloud-protocol.js → cloud/protocol.js} +1 -1
- package/dist/workbench/cloud/protocol.js.map +1 -0
- package/dist/workbench/{workbench-cloud-shared.d.ts → cloud/shared.d.ts} +2 -2
- package/dist/workbench/{workbench-cloud-shared.js → cloud/shared.js} +3 -3
- package/dist/workbench/cloud/shared.js.map +1 -0
- package/dist/workbench/workbench-commands.js +53 -8
- package/dist/workbench/workbench-commands.js.map +1 -1
- package/dist/workbench/workbench-completion.d.ts +12 -0
- package/dist/workbench/workbench-completion.js +413 -0
- package/dist/workbench/workbench-completion.js.map +1 -0
- package/dist/workbench/workbench-history.js +131 -40
- package/dist/workbench/workbench-history.js.map +1 -1
- package/dist/workbench/workbench-launch-session.d.ts +1 -1
- package/dist/workbench/workbench-launch-session.js.map +1 -1
- package/dist/workbench/workbench-project-loader.js +14 -3
- package/dist/workbench/workbench-project-loader.js.map +1 -1
- package/dist/workbench/workbench-run.js +4 -11
- package/dist/workbench/workbench-run.js.map +1 -1
- package/dist/workbench/workbench-shared.d.ts +2 -0
- package/dist/workbench/workbench-shared.js +4 -0
- package/dist/workbench/workbench-shared.js.map +1 -1
- package/dist/workbench/workbench-studio-launcher.d.ts +1 -1
- package/dist/workbench/workbench-studio-launcher.js.map +1 -1
- package/dist/workbench/workbench-subcommand.js +23 -1
- package/dist/workbench/workbench-subcommand.js.map +1 -1
- package/dist/workbench/workbench-ui.js +2 -2
- package/dist/workbench/workbench-ui.js.map +1 -1
- package/dist/workbench/workbench.js +74 -49
- package/dist/workbench/workbench.js.map +1 -1
- package/docs/agent-guide.md +18 -0
- package/docs/agents.md +65 -0
- package/docs/api-reference.md +19 -6
- package/docs/api-report.json +14 -0
- package/docs/cli.md +56 -14
- package/docs/llms.txt +5 -2
- package/docs/recipes.md +3 -0
- package/docs/studio.md +57 -37
- package/examples/agent-mcp.ts +33 -0
- package/package.json +7 -2
- package/skills/tubeless/SKILL.md +9 -0
- package/dist/studio/run-store-ui-api.js.map +0 -1
- package/dist/studio/run-store-ui-artifacts.js.map +0 -1
- package/dist/studio/run-store-ui-children.js.map +0 -1
- package/dist/studio/run-store-ui-client-browser.js +0 -4
- package/dist/studio/run-store-ui-client-transport.js.map +0 -1
- package/dist/studio/run-store-ui-client.d.ts +0 -20
- package/dist/studio/run-store-ui-client.js +0 -284
- package/dist/studio/run-store-ui-client.js.map +0 -1
- package/dist/studio/run-store-ui-common.js.map +0 -1
- package/dist/studio/run-store-ui-data-controller.js.map +0 -1
- package/dist/studio/run-store-ui-debugging.js.map +0 -1
- package/dist/studio/run-store-ui-definitions.js.map +0 -1
- package/dist/studio/run-store-ui-graph-layout.js.map +0 -1
- package/dist/studio/run-store-ui-graph.js +0 -594
- package/dist/studio/run-store-ui-graph.js.map +0 -1
- package/dist/studio/run-store-ui-handler.js.map +0 -1
- package/dist/studio/run-store-ui-hosting.js.map +0 -1
- package/dist/studio/run-store-ui-http.js.map +0 -1
- package/dist/studio/run-store-ui-launch.js.map +0 -1
- package/dist/studio/run-store-ui-metadata.js.map +0 -1
- package/dist/studio/run-store-ui-node.js.map +0 -1
- package/dist/studio/run-store-ui-page-node.js.map +0 -1
- package/dist/studio/run-store-ui-page.js.map +0 -1
- package/dist/studio/run-store-ui-pipelines.d.ts +0 -9
- package/dist/studio/run-store-ui-pipelines.js.map +0 -1
- package/dist/studio/run-store-ui-protocol.js.map +0 -1
- package/dist/studio/run-store-ui-resource.js.map +0 -1
- package/dist/studio/run-store-ui-runs.d.ts +0 -31
- package/dist/studio/run-store-ui-runs.js +0 -163
- package/dist/studio/run-store-ui-runs.js.map +0 -1
- package/dist/studio/run-store-ui-schema.js.map +0 -1
- package/dist/studio/run-store-ui-state.js +0 -207
- package/dist/studio/run-store-ui-state.js.map +0 -1
- package/dist/studio/run-store-ui-steps.js.map +0 -1
- package/dist/studio/run-store-ui.js.map +0 -1
- package/dist/workbench/cloud-client.js.map +0 -1
- package/dist/workbench/cloud-config.js.map +0 -1
- package/dist/workbench/cloud-credentials.js.map +0 -1
- package/dist/workbench/cloud-protocol.js.map +0 -1
- package/dist/workbench/workbench-agent-commands.js.map +0 -1
- package/dist/workbench/workbench-agent-environment.js.map +0 -1
- package/dist/workbench/workbench-agent-history.js.map +0 -1
- package/dist/workbench/workbench-agent-input.js.map +0 -1
- package/dist/workbench/workbench-agent-prompt.js.map +0 -1
- package/dist/workbench/workbench-agent-session.js.map +0 -1
- package/dist/workbench/workbench-agent.js.map +0 -1
- package/dist/workbench/workbench-auth.d.ts +0 -3
- package/dist/workbench/workbench-auth.js.map +0 -1
- package/dist/workbench/workbench-cloud-shared.js.map +0 -1
- package/dist/workbench/workbench-cloud.d.ts +0 -3
- package/dist/workbench/workbench-cloud.js.map +0 -1
- /package/dist/studio/{run-store-ui-graph-layout.d.ts → browser/graph/layout.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-resource.d.ts → browser/resource.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-page-node.d.ts → server/client-script.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-hosting.d.ts → server/hosting.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-http.d.ts → server/http.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-node.d.ts → server/node-http.d.ts} +0 -0
- /package/dist/studio/{run-store-ui-page.d.ts → server/page.d.ts} +0 -0
- /package/dist/workbench/{workbench-agent-commands.d.ts → agent/commands.d.ts} +0 -0
- /package/dist/workbench/{cloud-config.d.ts → cloud/config.d.ts} +0 -0
- /package/dist/workbench/{cloud-credentials.d.ts → cloud/credentials.d.ts} +0 -0
- /package/dist/workbench/{cloud-protocol.d.ts → cloud/protocol.d.ts} +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type McpTransport, type McpTransportEvents } from "./mcp-session.js";
|
|
2
|
+
/** A remote server reached over Streamable HTTP. */
|
|
3
|
+
export interface McpHttpOptions {
|
|
4
|
+
readonly url: string | URL;
|
|
5
|
+
/** Sent with every request, for example an Authorization header. */
|
|
6
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
7
|
+
}
|
|
8
|
+
/** Streamable HTTP: each message is a POST whose JSON or event-stream body carries replies. */
|
|
9
|
+
export declare function httpTransport(label: string, options: McpHttpOptions, events: Pick<McpTransportEvents, "receive">): McpTransport;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { isRecord, MAX_MESSAGE_BYTES, parseMessage, } from "./mcp-session.js";
|
|
2
|
+
/** Preserve CRLF pairs when a network chunk ends between their two bytes. */
|
|
3
|
+
async function* eventText(body) {
|
|
4
|
+
let trailingCR = "";
|
|
5
|
+
for await (const chunk of text(body)) {
|
|
6
|
+
const next = trailingCR + chunk;
|
|
7
|
+
trailingCR = next.endsWith("\r") ? "\r" : "";
|
|
8
|
+
yield (trailingCR ? next.slice(0, -1) : next).replace(/\r\n?/g, "\n");
|
|
9
|
+
}
|
|
10
|
+
if (trailingCR)
|
|
11
|
+
yield "\n";
|
|
12
|
+
}
|
|
13
|
+
/** Decode a bounded response body as text chunks. */
|
|
14
|
+
async function* text(body) {
|
|
15
|
+
const reader = body.getReader();
|
|
16
|
+
const decoder = new TextDecoder();
|
|
17
|
+
let bytes = 0;
|
|
18
|
+
try {
|
|
19
|
+
while (true) {
|
|
20
|
+
const chunk = await reader.read();
|
|
21
|
+
if (chunk.done)
|
|
22
|
+
break;
|
|
23
|
+
bytes += chunk.value.byteLength;
|
|
24
|
+
if (bytes > MAX_MESSAGE_BYTES)
|
|
25
|
+
throw new Error("MCP response exceeds 16 MiB");
|
|
26
|
+
yield decoder.decode(chunk.value, { stream: true });
|
|
27
|
+
}
|
|
28
|
+
yield decoder.decode();
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await reader.cancel().catch(() => { });
|
|
32
|
+
reader.releaseLock();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Yield the JSON-RPC messages carried by a JSON body or by `data` fields of an event stream. */
|
|
36
|
+
async function* messages(response) {
|
|
37
|
+
if (!response.body)
|
|
38
|
+
return;
|
|
39
|
+
if (!(response.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
40
|
+
let body = "";
|
|
41
|
+
for await (const chunk of text(response.body))
|
|
42
|
+
body += chunk;
|
|
43
|
+
if (body.trim())
|
|
44
|
+
yield parseMessage(body);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let buffer = "";
|
|
48
|
+
for await (const chunk of eventText(response.body)) {
|
|
49
|
+
buffer += chunk;
|
|
50
|
+
let boundary;
|
|
51
|
+
while ((boundary = buffer.indexOf("\n\n")) >= 0) {
|
|
52
|
+
const data = buffer
|
|
53
|
+
.slice(0, boundary)
|
|
54
|
+
.split("\n")
|
|
55
|
+
.filter((line) => line.startsWith("data:"))
|
|
56
|
+
.map((line) => line.slice(line.startsWith("data: ") ? 6 : 5))
|
|
57
|
+
.join("\n");
|
|
58
|
+
buffer = buffer.slice(boundary + 2);
|
|
59
|
+
if (data)
|
|
60
|
+
yield parseMessage(data);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Streamable HTTP: each message is a POST whose JSON or event-stream body carries replies. */
|
|
65
|
+
export function httpTransport(label, options, events) {
|
|
66
|
+
const url = new URL(options.url);
|
|
67
|
+
if (!["https:", "http:"].includes(url.protocol))
|
|
68
|
+
throw new Error(`MCP server ${label} requires an HTTP or HTTPS URL`);
|
|
69
|
+
const lifetime = new AbortController();
|
|
70
|
+
let closing;
|
|
71
|
+
let session;
|
|
72
|
+
let version;
|
|
73
|
+
const headers = () => ({
|
|
74
|
+
...options.headers,
|
|
75
|
+
Accept: "application/json, text/event-stream",
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
...(session === undefined ? {} : { "Mcp-Session-Id": session }),
|
|
78
|
+
...(version === undefined ? {} : { "MCP-Protocol-Version": version }),
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
async send(message, signal) {
|
|
82
|
+
if (lifetime.signal.aborted)
|
|
83
|
+
throw new Error(`MCP server ${label} is closed`);
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: headers(),
|
|
87
|
+
body: JSON.stringify(message),
|
|
88
|
+
signal: AbortSignal.any([signal, lifetime.signal]),
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
await response.body?.cancel().catch(() => { });
|
|
92
|
+
throw new Error(`MCP server ${label} failed (HTTP ${response.status})`);
|
|
93
|
+
}
|
|
94
|
+
session = response.headers.get("mcp-session-id") ?? session;
|
|
95
|
+
let answered = message.id === undefined;
|
|
96
|
+
for await (const reply of messages(response)) {
|
|
97
|
+
if (reply.id === message.id && reply.method === undefined) {
|
|
98
|
+
answered = true;
|
|
99
|
+
// Later requests carry the version this initialize response negotiated.
|
|
100
|
+
if (message.method === "initialize" && isRecord(reply.result))
|
|
101
|
+
version = String(reply.result.protocolVersion);
|
|
102
|
+
}
|
|
103
|
+
events.receive(reply);
|
|
104
|
+
if (answered && message.id !== undefined)
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (!answered)
|
|
108
|
+
throw new Error(`MCP server ${label} closed the stream without a response`);
|
|
109
|
+
},
|
|
110
|
+
close() {
|
|
111
|
+
closing ??= (async () => {
|
|
112
|
+
lifetime.abort();
|
|
113
|
+
if (session === undefined)
|
|
114
|
+
return;
|
|
115
|
+
// Servers may refuse explicit termination with 405; the session then expires on its own.
|
|
116
|
+
await fetch(url, {
|
|
117
|
+
method: "DELETE",
|
|
118
|
+
headers: headers(),
|
|
119
|
+
signal: AbortSignal.timeout(5_000),
|
|
120
|
+
})
|
|
121
|
+
.then((response) => response.body?.cancel())
|
|
122
|
+
.catch(() => { });
|
|
123
|
+
session = undefined;
|
|
124
|
+
})();
|
|
125
|
+
return closing;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=mcp-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-http.js","sourceRoot":"","sources":["../../src/agent/mcp-http.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,YAAY,GAGb,MAAM,kBAAkB,CAAC;AAS1B,6EAA6E;AAC7E,KAAK,SAAS,CAAC,CAAC,SAAS,CAAC,IAAgC;IACxD,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,UAAU,GAAG,KAAK,CAAC;QAChC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,UAAU;QAAE,MAAM,IAAI,CAAC;AAC7B,CAAC;AAED,qDAAqD;AACrD,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,IAAgC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,IAAI;gBAAE,MAAM;YACtB,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;YAChC,IAAI,KAAK,GAAG,iBAAiB;gBAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC9E,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAkB;IACzC,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAO;IAC3B,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAChF,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,IAAI,IAAI,KAAK,CAAC;QAC7D,IAAI,IAAI,CAAC,IAAI,EAAE;YAAE,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IACD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC;QAChB,IAAI,QAAgB,CAAC;QACrB,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,GAAG,MAAM;iBAChB,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;iBAClB,KAAK,CAAC,IAAI,CAAC;iBACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;iBAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;iBAC5D,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;YACpC,IAAI,IAAI;gBAAE,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,aAAa,CAC3B,KAAa,EACb,OAAuB,EACvB,MAA2C;IAE3C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,gCAAgC,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,IAAI,OAAkC,CAAC;IACvC,IAAI,OAA2B,CAAC;IAChC,IAAI,OAA2B,CAAC;IAChC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;QACrB,GAAG,OAAO,CAAC,OAAO;QAClB,MAAM,EAAE,qCAAqC;QAC7C,cAAc,EAAE,kBAAkB;QAClC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QAC/D,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC;KACtE,CAAC,CAAC;IACH,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM;YACxB,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,YAAY,CAAC,CAAC;YAC9E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,OAAO,EAAE;gBAClB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;gBAC7B,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;aACnD,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBAC9C,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,iBAAiB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC1E,CAAC;YACD,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC;YAC5D,IAAI,QAAQ,GAAG,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC;YACxC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7C,IAAI,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBAC1D,QAAQ,GAAG,IAAI,CAAC;oBAChB,wEAAwE;oBACxE,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;wBAC3D,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;gBACnD,CAAC;gBACD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACtB,IAAI,QAAQ,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;oBAAE,MAAM;YAClD,CAAC;YACD,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,uCAAuC,CAAC,CAAC;QAC7F,CAAC;QACD,KAAK;YACH,OAAO,KAAK,CAAC,KAAK,IAAI,EAAE;gBACtB,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACjB,IAAI,OAAO,KAAK,SAAS;oBAAE,OAAO;gBAClC,yFAAyF;gBACzF,MAAM,KAAK,CAAC,GAAG,EAAE;oBACf,MAAM,EAAE,QAAQ;oBAChB,OAAO,EAAE,OAAO,EAAE;oBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;iBACnC,CAAC;qBACC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;qBAC3C,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACnB,OAAO,GAAG,SAAS,CAAC;YACtB,CAAC,CAAC,EAAE,CAAC;YACL,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import {\n isRecord,\n MAX_MESSAGE_BYTES,\n parseMessage,\n type McpTransport,\n type McpTransportEvents,\n} from \"./mcp-session.js\";\n\n/** A remote server reached over Streamable HTTP. */\nexport interface McpHttpOptions {\n readonly url: string | URL;\n /** Sent with every request, for example an Authorization header. */\n readonly headers?: Readonly<Record<string, string>>;\n}\n\n/** Preserve CRLF pairs when a network chunk ends between their two bytes. */\nasync function* eventText(body: ReadableStream<Uint8Array>) {\n let trailingCR = \"\";\n for await (const chunk of text(body)) {\n const next = trailingCR + chunk;\n trailingCR = next.endsWith(\"\\r\") ? \"\\r\" : \"\";\n yield (trailingCR ? next.slice(0, -1) : next).replace(/\\r\\n?/g, \"\\n\");\n }\n if (trailingCR) yield \"\\n\";\n}\n\n/** Decode a bounded response body as text chunks. */\nasync function* text(body: ReadableStream<Uint8Array>) {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let bytes = 0;\n try {\n while (true) {\n const chunk = await reader.read();\n if (chunk.done) break;\n bytes += chunk.value.byteLength;\n if (bytes > MAX_MESSAGE_BYTES) throw new Error(\"MCP response exceeds 16 MiB\");\n yield decoder.decode(chunk.value, { stream: true });\n }\n yield decoder.decode();\n } finally {\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n\n/** Yield the JSON-RPC messages carried by a JSON body or by `data` fields of an event stream. */\nasync function* messages(response: Response) {\n if (!response.body) return;\n if (!(response.headers.get(\"content-type\") ?? \"\").includes(\"text/event-stream\")) {\n let body = \"\";\n for await (const chunk of text(response.body)) body += chunk;\n if (body.trim()) yield parseMessage(body);\n return;\n }\n let buffer = \"\";\n for await (const chunk of eventText(response.body)) {\n buffer += chunk;\n let boundary: number;\n while ((boundary = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const data = buffer\n .slice(0, boundary)\n .split(\"\\n\")\n .filter((line) => line.startsWith(\"data:\"))\n .map((line) => line.slice(line.startsWith(\"data: \") ? 6 : 5))\n .join(\"\\n\");\n buffer = buffer.slice(boundary + 2);\n if (data) yield parseMessage(data);\n }\n }\n}\n\n/** Streamable HTTP: each message is a POST whose JSON or event-stream body carries replies. */\nexport function httpTransport(\n label: string,\n options: McpHttpOptions,\n events: Pick<McpTransportEvents, \"receive\">\n): McpTransport {\n const url = new URL(options.url);\n if (![\"https:\", \"http:\"].includes(url.protocol))\n throw new Error(`MCP server ${label} requires an HTTP or HTTPS URL`);\n const lifetime = new AbortController();\n let closing: Promise<void> | undefined;\n let session: string | undefined;\n let version: string | undefined;\n const headers = () => ({\n ...options.headers,\n Accept: \"application/json, text/event-stream\",\n \"Content-Type\": \"application/json\",\n ...(session === undefined ? {} : { \"Mcp-Session-Id\": session }),\n ...(version === undefined ? {} : { \"MCP-Protocol-Version\": version }),\n });\n return {\n async send(message, signal) {\n if (lifetime.signal.aborted) throw new Error(`MCP server ${label} is closed`);\n const response = await fetch(url, {\n method: \"POST\",\n headers: headers(),\n body: JSON.stringify(message),\n signal: AbortSignal.any([signal, lifetime.signal]),\n });\n if (!response.ok) {\n await response.body?.cancel().catch(() => {});\n throw new Error(`MCP server ${label} failed (HTTP ${response.status})`);\n }\n session = response.headers.get(\"mcp-session-id\") ?? session;\n let answered = message.id === undefined;\n for await (const reply of messages(response)) {\n if (reply.id === message.id && reply.method === undefined) {\n answered = true;\n // Later requests carry the version this initialize response negotiated.\n if (message.method === \"initialize\" && isRecord(reply.result))\n version = String(reply.result.protocolVersion);\n }\n events.receive(reply);\n if (answered && message.id !== undefined) break;\n }\n if (!answered) throw new Error(`MCP server ${label} closed the stream without a response`);\n },\n close() {\n closing ??= (async () => {\n lifetime.abort();\n if (session === undefined) return;\n // Servers may refuse explicit termination with 405; the session then expires on its own.\n await fetch(url, {\n method: \"DELETE\",\n headers: headers(),\n signal: AbortSignal.timeout(5_000),\n })\n .then((response) => response.body?.cancel())\n .catch(() => {});\n session = undefined;\n })();\n return closing;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "../core/pipeline-types.js";
|
|
2
|
+
/** Adapt an MCP tool input schema to a strict model descriptor and an argument decoder. */
|
|
3
|
+
export declare function mcpInputSchema(inputSchema: unknown): StandardSchemaV1<Record<string, unknown>>;
|
|
4
|
+
/** Content and optional structured data returned by an MCP tool call. */
|
|
5
|
+
export interface McpToolResult {
|
|
6
|
+
readonly content: readonly Readonly<Record<string, unknown> & {
|
|
7
|
+
type: string;
|
|
8
|
+
}>[];
|
|
9
|
+
readonly structuredContent?: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
/** Validate a raw `tools/call` result into the content the agent observes. */
|
|
12
|
+
export declare const mcpResultSchema: StandardSchemaV1<unknown, McpToolResult>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { canonicalJsonValue } from "../utilities/canonical-json.js";
|
|
2
|
+
import { isRecord } from "./mcp-session.js";
|
|
3
|
+
/** Charge source data and every expanded copy before retaining its descriptor. */
|
|
4
|
+
function expansionBudget() {
|
|
5
|
+
let nodes = 0;
|
|
6
|
+
let characters = 0;
|
|
7
|
+
const charge = (value, depth) => {
|
|
8
|
+
if (++nodes > 10_000 || depth > 64)
|
|
9
|
+
throw new Error("MCP input schema exceeds expansion limits");
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
characters += value.length;
|
|
12
|
+
if (characters > 1_048_576)
|
|
13
|
+
throw new Error("MCP input schema exceeds expansion limits");
|
|
14
|
+
}
|
|
15
|
+
else if (Array.isArray(value)) {
|
|
16
|
+
for (const child of value)
|
|
17
|
+
charge(child, depth + 1);
|
|
18
|
+
}
|
|
19
|
+
else if (isRecord(value)) {
|
|
20
|
+
for (const [key, child] of Object.entries(value)) {
|
|
21
|
+
charge(key, depth + 1);
|
|
22
|
+
charge(child, depth + 1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return charge;
|
|
27
|
+
}
|
|
28
|
+
const jsonEqual = (left, right) => JSON.stringify(canonicalJsonValue(left)) === JSON.stringify(canonicalJsonValue(right));
|
|
29
|
+
const objectSchema = (schema) => schema.type === "object" ||
|
|
30
|
+
(Array.isArray(schema.type) && schema.type.includes("object")) ||
|
|
31
|
+
Object.hasOwn(schema, "properties");
|
|
32
|
+
// These constructs cannot be rewritten and decoded with the same structural rules.
|
|
33
|
+
const UNSUPPORTED = [
|
|
34
|
+
"oneOf",
|
|
35
|
+
"allOf",
|
|
36
|
+
"prefixItems",
|
|
37
|
+
"not",
|
|
38
|
+
"if",
|
|
39
|
+
"then",
|
|
40
|
+
"else",
|
|
41
|
+
"contains",
|
|
42
|
+
"additionalItems",
|
|
43
|
+
"patternProperties",
|
|
44
|
+
"dependentSchemas",
|
|
45
|
+
"dependencies",
|
|
46
|
+
"unevaluatedProperties",
|
|
47
|
+
"unevaluatedItems",
|
|
48
|
+
"$dynamicRef",
|
|
49
|
+
"$recursiveRef",
|
|
50
|
+
];
|
|
51
|
+
const REFERENCE_ANNOTATIONS = [
|
|
52
|
+
"title",
|
|
53
|
+
"description",
|
|
54
|
+
"default",
|
|
55
|
+
"examples",
|
|
56
|
+
"deprecated",
|
|
57
|
+
"readOnly",
|
|
58
|
+
"writeOnly",
|
|
59
|
+
"$comment",
|
|
60
|
+
"$schema",
|
|
61
|
+
"$defs",
|
|
62
|
+
"definitions",
|
|
63
|
+
];
|
|
64
|
+
/** Inline references and rewrite supported schema positions in one traversal. */
|
|
65
|
+
function strict(schema, context, seen = [], depth = 0) {
|
|
66
|
+
const { root, optional, charge } = context;
|
|
67
|
+
charge(null, depth);
|
|
68
|
+
if (!isRecord(schema))
|
|
69
|
+
throw new Error("MCP input schemas must be JSON Schema objects");
|
|
70
|
+
for (const keyword of UNSUPPORTED)
|
|
71
|
+
if (Object.hasOwn(schema, keyword))
|
|
72
|
+
throw new Error(`Unsupported MCP schema keyword ${keyword}`);
|
|
73
|
+
if (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
|
|
74
|
+
throw new Error("MCP input schemas do not support free-form objects");
|
|
75
|
+
if (Array.isArray(schema.items))
|
|
76
|
+
throw new Error("MCP input schemas do not support tuple items");
|
|
77
|
+
const { $ref, ...rest } = schema;
|
|
78
|
+
if (typeof $ref === "string") {
|
|
79
|
+
if (Object.keys(rest).some((key) => !REFERENCE_ANNOTATIONS.includes(key)))
|
|
80
|
+
throw new Error("MCP schema references do not support validation siblings");
|
|
81
|
+
const match = /^#\/(\$defs|definitions)\/(.+)$/.exec($ref);
|
|
82
|
+
let target = root;
|
|
83
|
+
for (const part of $ref.slice(2).split("/")) {
|
|
84
|
+
const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
85
|
+
target = isRecord(target) && Object.hasOwn(target, key) ? target[key] : undefined;
|
|
86
|
+
}
|
|
87
|
+
if (!match || !isRecord(target))
|
|
88
|
+
throw new Error(`Unsupported MCP schema reference ${$ref}`);
|
|
89
|
+
if (seen.includes($ref))
|
|
90
|
+
throw new Error(`Recursive MCP schema reference ${$ref}`);
|
|
91
|
+
return strict({ ...target, ...rest }, context, [...seen, $ref], depth + 1);
|
|
92
|
+
}
|
|
93
|
+
const { $defs: _defs, definitions: _definitions, $schema: _dialect, ...body } = rest;
|
|
94
|
+
const { properties: _properties, items: _items, anyOf: _anyOf, ...literals } = body;
|
|
95
|
+
charge(literals, depth);
|
|
96
|
+
const result = { ...body };
|
|
97
|
+
if (schema.items !== undefined)
|
|
98
|
+
result.items = strict(schema.items, context, seen, depth + 1);
|
|
99
|
+
if (Array.isArray(schema.anyOf))
|
|
100
|
+
result.anyOf = schema.anyOf.map((child) => strict(child, context, seen, depth + 1));
|
|
101
|
+
if (!objectSchema(schema))
|
|
102
|
+
return result;
|
|
103
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
104
|
+
const required = schema.required ?? [];
|
|
105
|
+
if (!Array.isArray(required) ||
|
|
106
|
+
required.some((name) => typeof name !== "string" || !Object.hasOwn(properties, name)))
|
|
107
|
+
throw new Error("MCP required names must be declared in properties");
|
|
108
|
+
result.properties = Object.fromEntries(Object.entries(properties).map(([name, child]) => {
|
|
109
|
+
charge(name, depth + 1);
|
|
110
|
+
const field = strict(child, context, seen, depth + 1);
|
|
111
|
+
if (required.includes(name))
|
|
112
|
+
return [name, field];
|
|
113
|
+
if (acceptsNull(field, optional))
|
|
114
|
+
throw new Error(`Optional MCP property ${name} already accepts null; omission would be ambiguous`);
|
|
115
|
+
const wrapper = { anyOf: [field, { type: "null" }] };
|
|
116
|
+
optional.add(wrapper);
|
|
117
|
+
return [name, wrapper];
|
|
118
|
+
}));
|
|
119
|
+
result.required = Object.keys(properties);
|
|
120
|
+
result.additionalProperties = false;
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function typeMatches(type, value) {
|
|
124
|
+
if (Array.isArray(type))
|
|
125
|
+
return type.some((option) => typeMatches(option, value));
|
|
126
|
+
switch (type) {
|
|
127
|
+
case "object":
|
|
128
|
+
return isRecord(value);
|
|
129
|
+
case "array":
|
|
130
|
+
return Array.isArray(value);
|
|
131
|
+
case "string":
|
|
132
|
+
return typeof value === "string";
|
|
133
|
+
case "integer":
|
|
134
|
+
return Number.isSafeInteger(value);
|
|
135
|
+
case "number":
|
|
136
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
137
|
+
case "boolean":
|
|
138
|
+
return typeof value === "boolean";
|
|
139
|
+
case "null":
|
|
140
|
+
return value === null;
|
|
141
|
+
default:
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check structure against the strict descriptor and drop the nulls it substitutes for
|
|
147
|
+
* omitted optional properties. The MCP server remains the authority for its full schema.
|
|
148
|
+
*/
|
|
149
|
+
function decode(schema, value, path, optional) {
|
|
150
|
+
if (!isRecord(schema))
|
|
151
|
+
return value;
|
|
152
|
+
if (schema.type !== undefined && !typeMatches(schema.type, value))
|
|
153
|
+
throw new Error(`${path} must be ${JSON.stringify(schema.type)}`);
|
|
154
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((option) => jsonEqual(option, value)))
|
|
155
|
+
throw new Error(`${path} must be one of ${JSON.stringify(schema.enum)}`);
|
|
156
|
+
if (Object.hasOwn(schema, "const") && !jsonEqual(schema.const, value))
|
|
157
|
+
throw new Error(`${path} must be ${JSON.stringify(schema.const)}`);
|
|
158
|
+
// Sibling constraints must see the original input, before any branch removes placeholders.
|
|
159
|
+
let decoded = value;
|
|
160
|
+
if (Array.isArray(value)) {
|
|
161
|
+
decoded = value.map((item, index) => decode(schema.items, item, `${path}[${index}]`, optional));
|
|
162
|
+
}
|
|
163
|
+
else if (isRecord(value) && objectSchema(schema)) {
|
|
164
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
165
|
+
for (const name of Array.isArray(schema.required) ? schema.required : [])
|
|
166
|
+
if (!Object.hasOwn(value, name))
|
|
167
|
+
throw new Error(`${path}.${name} is required`);
|
|
168
|
+
const entries = [];
|
|
169
|
+
for (const [name, child] of Object.entries(value)) {
|
|
170
|
+
const property = properties[name];
|
|
171
|
+
if (!Object.hasOwn(properties, name)) {
|
|
172
|
+
if (schema.additionalProperties === false)
|
|
173
|
+
throw new Error(`${path}.${name} is not allowed`);
|
|
174
|
+
entries.push([name, child]);
|
|
175
|
+
}
|
|
176
|
+
else if (!(child === null && isRecord(property) && optional.has(property)))
|
|
177
|
+
entries.push([name, decode(property, child, `${path}.${name}`, optional)]);
|
|
178
|
+
}
|
|
179
|
+
decoded = Object.fromEntries(entries);
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(schema.anyOf))
|
|
182
|
+
return decoded;
|
|
183
|
+
const alternative = decodeAny(schema.anyOf, value, path, optional);
|
|
184
|
+
if (objectSchema(schema) || (Array.isArray(value) && schema.items !== undefined)) {
|
|
185
|
+
if (!jsonEqual(decoded, alternative))
|
|
186
|
+
throw new Error(`${path} has conflicting anyOf decoding`);
|
|
187
|
+
return decoded;
|
|
188
|
+
}
|
|
189
|
+
return alternative;
|
|
190
|
+
}
|
|
191
|
+
function decodeAny(options, value, path, optional) {
|
|
192
|
+
const matches = [];
|
|
193
|
+
for (const option of options) {
|
|
194
|
+
try {
|
|
195
|
+
const decoded = decode(option, value, path, optional);
|
|
196
|
+
matches.push(decoded);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Try the next alternative.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (matches.length === 0)
|
|
203
|
+
throw new Error(`${path} matches no allowed schema`);
|
|
204
|
+
if (matches.some((match) => !jsonEqual(match, matches[0])))
|
|
205
|
+
throw new Error(`${path} has ambiguous anyOf decoding`);
|
|
206
|
+
return matches[0];
|
|
207
|
+
}
|
|
208
|
+
function acceptsNull(schema, optional) {
|
|
209
|
+
try {
|
|
210
|
+
decode(schema, null, "input", optional);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Adapt an MCP tool input schema to a strict model descriptor and an argument decoder. */
|
|
218
|
+
export function mcpInputSchema(inputSchema) {
|
|
219
|
+
if (!isRecord(inputSchema) || inputSchema.type !== "object")
|
|
220
|
+
throw new Error("MCP tool inputSchema must be a JSON Schema object");
|
|
221
|
+
const optional = new WeakSet();
|
|
222
|
+
const charge = expansionBudget();
|
|
223
|
+
charge(inputSchema, 0);
|
|
224
|
+
const descriptor = strict(inputSchema, { root: inputSchema, optional, charge });
|
|
225
|
+
return {
|
|
226
|
+
"~standard": {
|
|
227
|
+
version: 1,
|
|
228
|
+
vendor: "tubeless-mcp",
|
|
229
|
+
jsonSchema: { input: () => descriptor },
|
|
230
|
+
validate(value) {
|
|
231
|
+
try {
|
|
232
|
+
// SAFETY: the descriptor's root is an object schema, so decoding yields a record.
|
|
233
|
+
return { value: decode(descriptor, value, "input", optional) };
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/** Validate a raw `tools/call` result into the content the agent observes. */
|
|
243
|
+
export const mcpResultSchema = {
|
|
244
|
+
"~standard": {
|
|
245
|
+
version: 1,
|
|
246
|
+
vendor: "tubeless-mcp",
|
|
247
|
+
validate(value) {
|
|
248
|
+
if (!isRecord(value) ||
|
|
249
|
+
!Array.isArray(value.content) ||
|
|
250
|
+
!value.content.every((item) => isRecord(item) && typeof item.type === "string") ||
|
|
251
|
+
(value.structuredContent !== undefined && !isRecord(value.structuredContent)))
|
|
252
|
+
return { issues: [{ message: "MCP tool returned an invalid result" }] };
|
|
253
|
+
return {
|
|
254
|
+
value: {
|
|
255
|
+
content: value.content,
|
|
256
|
+
...(value.structuredContent === undefined
|
|
257
|
+
? {}
|
|
258
|
+
: { structuredContent: value.structuredContent }),
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=mcp-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-schema.js","sourceRoot":"","sources":["../../src/agent/mcp-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StandardSchemaV1 } from \"../core/pipeline-types.js\";\nimport { canonicalJsonValue } from \"../utilities/canonical-json.js\";\nimport { isRecord } from \"./mcp-session.js\";\n\ntype Json = Readonly<Record<string, unknown>>;\n\ninterface SchemaContext {\n readonly root: Json;\n readonly optional: WeakSet<object>;\n charge(value: unknown, depth: number): void;\n}\n\n/** Charge source data and every expanded copy before retaining its descriptor. */\nfunction expansionBudget() {\n let nodes = 0;\n let characters = 0;\n const charge = (value: unknown, depth: number): void => {\n if (++nodes > 10_000 || depth > 64)\n throw new Error(\"MCP input schema exceeds expansion limits\");\n if (typeof value === \"string\") {\n characters += value.length;\n if (characters > 1_048_576) throw new Error(\"MCP input schema exceeds expansion limits\");\n } else if (Array.isArray(value)) {\n for (const child of value) charge(child, depth + 1);\n } else if (isRecord(value)) {\n for (const [key, child] of Object.entries(value)) {\n charge(key, depth + 1);\n charge(child, depth + 1);\n }\n }\n };\n return charge;\n}\n\nconst jsonEqual = (left: unknown, right: unknown) =>\n JSON.stringify(canonicalJsonValue(left)) === JSON.stringify(canonicalJsonValue(right));\n\nconst objectSchema = (schema: Json) =>\n schema.type === \"object\" ||\n (Array.isArray(schema.type) && schema.type.includes(\"object\")) ||\n Object.hasOwn(schema, \"properties\");\n\n// These constructs cannot be rewritten and decoded with the same structural rules.\nconst UNSUPPORTED = [\n \"oneOf\",\n \"allOf\",\n \"prefixItems\",\n \"not\",\n \"if\",\n \"then\",\n \"else\",\n \"contains\",\n \"additionalItems\",\n \"patternProperties\",\n \"dependentSchemas\",\n \"dependencies\",\n \"unevaluatedProperties\",\n \"unevaluatedItems\",\n \"$dynamicRef\",\n \"$recursiveRef\",\n];\n\nconst REFERENCE_ANNOTATIONS = [\n \"title\",\n \"description\",\n \"default\",\n \"examples\",\n \"deprecated\",\n \"readOnly\",\n \"writeOnly\",\n \"$comment\",\n \"$schema\",\n \"$defs\",\n \"definitions\",\n];\n\n/** Inline references and rewrite supported schema positions in one traversal. */\nfunction strict(\n schema: unknown,\n context: SchemaContext,\n seen: readonly string[] = [],\n depth = 0\n): Json {\n const { root, optional, charge } = context;\n charge(null, depth);\n if (!isRecord(schema)) throw new Error(\"MCP input schemas must be JSON Schema objects\");\n for (const keyword of UNSUPPORTED)\n if (Object.hasOwn(schema, keyword))\n throw new Error(`Unsupported MCP schema keyword ${keyword}`);\n if (schema.additionalProperties !== undefined && schema.additionalProperties !== false)\n throw new Error(\"MCP input schemas do not support free-form objects\");\n if (Array.isArray(schema.items)) throw new Error(\"MCP input schemas do not support tuple items\");\n const { $ref, ...rest } = schema;\n if (typeof $ref === \"string\") {\n if (Object.keys(rest).some((key) => !REFERENCE_ANNOTATIONS.includes(key)))\n throw new Error(\"MCP schema references do not support validation siblings\");\n const match = /^#\\/(\\$defs|definitions)\\/(.+)$/.exec($ref);\n let target: unknown = root;\n for (const part of $ref.slice(2).split(\"/\")) {\n const key = part.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n target = isRecord(target) && Object.hasOwn(target, key) ? target[key] : undefined;\n }\n if (!match || !isRecord(target)) throw new Error(`Unsupported MCP schema reference ${$ref}`);\n if (seen.includes($ref)) throw new Error(`Recursive MCP schema reference ${$ref}`);\n return strict({ ...target, ...rest }, context, [...seen, $ref], depth + 1);\n }\n const { $defs: _defs, definitions: _definitions, $schema: _dialect, ...body } = rest;\n const { properties: _properties, items: _items, anyOf: _anyOf, ...literals } = body;\n charge(literals, depth);\n const result: Record<string, unknown> = { ...body };\n if (schema.items !== undefined) result.items = strict(schema.items, context, seen, depth + 1);\n if (Array.isArray(schema.anyOf))\n result.anyOf = schema.anyOf.map((child) => strict(child, context, seen, depth + 1));\n if (!objectSchema(schema)) return result;\n const properties = isRecord(schema.properties) ? schema.properties : {};\n const required = schema.required ?? [];\n if (\n !Array.isArray(required) ||\n required.some((name) => typeof name !== \"string\" || !Object.hasOwn(properties, name))\n )\n throw new Error(\"MCP required names must be declared in properties\");\n result.properties = Object.fromEntries(\n Object.entries(properties).map(([name, child]) => {\n charge(name, depth + 1);\n const field = strict(child, context, seen, depth + 1);\n if (required.includes(name)) return [name, field];\n if (acceptsNull(field, optional))\n throw new Error(\n `Optional MCP property ${name} already accepts null; omission would be ambiguous`\n );\n const wrapper = { anyOf: [field, { type: \"null\" }] };\n optional.add(wrapper);\n return [name, wrapper];\n })\n );\n result.required = Object.keys(properties);\n result.additionalProperties = false;\n return result;\n}\n\nfunction typeMatches(type: unknown, value: unknown): boolean {\n if (Array.isArray(type)) return type.some((option) => typeMatches(option, value));\n switch (type) {\n case \"object\":\n return isRecord(value);\n case \"array\":\n return Array.isArray(value);\n case \"string\":\n return typeof value === \"string\";\n case \"integer\":\n return Number.isSafeInteger(value);\n case \"number\":\n return typeof value === \"number\" && Number.isFinite(value);\n case \"boolean\":\n return typeof value === \"boolean\";\n case \"null\":\n return value === null;\n default:\n return true;\n }\n}\n\n/**\n * Check structure against the strict descriptor and drop the nulls it substitutes for\n * omitted optional properties. The MCP server remains the authority for its full schema.\n */\nfunction decode(schema: unknown, value: unknown, path: string, optional: WeakSet<object>): unknown {\n if (!isRecord(schema)) return value;\n if (schema.type !== undefined && !typeMatches(schema.type, value))\n throw new Error(`${path} must be ${JSON.stringify(schema.type)}`);\n if (Array.isArray(schema.enum) && !schema.enum.some((option) => jsonEqual(option, value)))\n throw new Error(`${path} must be one of ${JSON.stringify(schema.enum)}`);\n if (Object.hasOwn(schema, \"const\") && !jsonEqual(schema.const, value))\n throw new Error(`${path} must be ${JSON.stringify(schema.const)}`);\n // Sibling constraints must see the original input, before any branch removes placeholders.\n let decoded: unknown = value;\n if (Array.isArray(value)) {\n decoded = value.map((item, index) => decode(schema.items, item, `${path}[${index}]`, optional));\n } else if (isRecord(value) && objectSchema(schema)) {\n const properties = isRecord(schema.properties) ? schema.properties : {};\n for (const name of Array.isArray(schema.required) ? schema.required : [])\n if (!Object.hasOwn(value, name)) throw new Error(`${path}.${name} is required`);\n const entries: [string, unknown][] = [];\n for (const [name, child] of Object.entries(value)) {\n const property = properties[name];\n if (!Object.hasOwn(properties, name)) {\n if (schema.additionalProperties === false)\n throw new Error(`${path}.${name} is not allowed`);\n entries.push([name, child]);\n } else if (!(child === null && isRecord(property) && optional.has(property)))\n entries.push([name, decode(property, child, `${path}.${name}`, optional)]);\n }\n decoded = Object.fromEntries(entries);\n }\n if (!Array.isArray(schema.anyOf)) return decoded;\n const alternative = decodeAny(schema.anyOf, value, path, optional);\n if (objectSchema(schema) || (Array.isArray(value) && schema.items !== undefined)) {\n if (!jsonEqual(decoded, alternative)) throw new Error(`${path} has conflicting anyOf decoding`);\n return decoded;\n }\n return alternative;\n}\n\nfunction decodeAny(options: unknown[], value: unknown, path: string, optional: WeakSet<object>) {\n const matches: unknown[] = [];\n for (const option of options) {\n try {\n const decoded = decode(option, value, path, optional);\n matches.push(decoded);\n } catch {\n // Try the next alternative.\n }\n }\n if (matches.length === 0) throw new Error(`${path} matches no allowed schema`);\n if (matches.some((match) => !jsonEqual(match, matches[0])))\n throw new Error(`${path} has ambiguous anyOf decoding`);\n return matches[0];\n}\n\nfunction acceptsNull(schema: Json, optional: WeakSet<object>): boolean {\n try {\n decode(schema, null, \"input\", optional);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Adapt an MCP tool input schema to a strict model descriptor and an argument decoder. */\nexport function mcpInputSchema(inputSchema: unknown): StandardSchemaV1<Record<string, unknown>> {\n if (!isRecord(inputSchema) || inputSchema.type !== \"object\")\n throw new Error(\"MCP tool inputSchema must be a JSON Schema object\");\n const optional = new WeakSet<object>();\n const charge = expansionBudget();\n charge(inputSchema, 0);\n const descriptor = strict(inputSchema, { root: inputSchema, optional, charge });\n return {\n \"~standard\": {\n version: 1,\n vendor: \"tubeless-mcp\",\n jsonSchema: { input: () => descriptor },\n validate(value) {\n try {\n // SAFETY: the descriptor's root is an object schema, so decoding yields a record.\n return { value: decode(descriptor, value, \"input\", optional) as Record<string, unknown> };\n } catch (error) {\n return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };\n }\n },\n },\n };\n}\n\n/** Content and optional structured data returned by an MCP tool call. */\nexport interface McpToolResult {\n readonly content: readonly Readonly<Record<string, unknown> & { type: string }>[];\n readonly structuredContent?: Readonly<Record<string, unknown>>;\n}\n\n/** Validate a raw `tools/call` result into the content the agent observes. */\nexport const mcpResultSchema: StandardSchemaV1<unknown, McpToolResult> = {\n \"~standard\": {\n version: 1,\n vendor: \"tubeless-mcp\",\n validate(value) {\n if (\n !isRecord(value) ||\n !Array.isArray(value.content) ||\n !value.content.every((item) => isRecord(item) && typeof item.type === \"string\") ||\n (value.structuredContent !== undefined && !isRecord(value.structuredContent))\n )\n return { issues: [{ message: \"MCP tool returned an invalid result\" }] };\n return {\n value: {\n content: value.content as McpToolResult[\"content\"],\n ...(value.structuredContent === undefined\n ? {}\n : { structuredContent: value.structuredContent as Json }),\n },\n };\n },\n },\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface McpMessage {
|
|
2
|
+
readonly jsonrpc: "2.0";
|
|
3
|
+
readonly id?: number | string;
|
|
4
|
+
readonly method?: string;
|
|
5
|
+
readonly params?: unknown;
|
|
6
|
+
readonly result?: unknown;
|
|
7
|
+
readonly error?: {
|
|
8
|
+
readonly code: number;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Inbound traffic: every received message, or a failure that ends the connection. */
|
|
13
|
+
export interface McpTransportEvents {
|
|
14
|
+
receive(message: McpMessage): void;
|
|
15
|
+
fail(error: Error): void;
|
|
16
|
+
}
|
|
17
|
+
/** Carries JSON-RPC messages; the session owns ids, deadlines, replies and lifecycle. */
|
|
18
|
+
export interface McpTransport {
|
|
19
|
+
/** Deliver one message; any replies arrive through `receive`. */
|
|
20
|
+
send(message: McpMessage, signal: AbortSignal): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export type OpenMcpTransport = (events: McpTransportEvents) => Promise<McpTransport>;
|
|
24
|
+
/** A JSON-RPC error returned by the server; the connection remains usable. */
|
|
25
|
+
export declare class McpRpcError extends Error {
|
|
26
|
+
readonly code: number;
|
|
27
|
+
constructor(code: number, message: string);
|
|
28
|
+
}
|
|
29
|
+
export declare const MAX_MESSAGE_BYTES = 16777216;
|
|
30
|
+
export declare function isRecord(value: unknown): value is Readonly<Record<string, unknown>>;
|
|
31
|
+
/** Parse one inbound message, rejecting shapes that cannot be JSON-RPC. */
|
|
32
|
+
export declare function parseMessage(text: string): McpMessage;
|
|
33
|
+
export interface McpSession {
|
|
34
|
+
request(method: string, params: object, signal?: AbortSignal): Promise<unknown>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/** Initialize a session: version negotiation, the initialized notification, then requests. */
|
|
38
|
+
export declare function openMcpSession(open: OpenMcpTransport, label: string, timeoutMs: number, signal?: AbortSignal): Promise<McpSession>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createAbortError, throwIfAborted } from "../utilities/abort.js";
|
|
2
|
+
/** A JSON-RPC error returned by the server; the connection remains usable. */
|
|
3
|
+
export class McpRpcError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = "McpRpcError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const MAX_MESSAGE_BYTES = 16_777_216;
|
|
12
|
+
const PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
13
|
+
export function isRecord(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
/** Parse one inbound message, rejecting shapes that cannot be JSON-RPC. */
|
|
17
|
+
export function parseMessage(text) {
|
|
18
|
+
let message;
|
|
19
|
+
try {
|
|
20
|
+
message = JSON.parse(text);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error("MCP server sent invalid JSON");
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(message) || message.jsonrpc !== "2.0")
|
|
26
|
+
throw new Error("MCP server sent an invalid JSON-RPC message");
|
|
27
|
+
// SAFETY: the version guard identifies JSON-RPC; fields are checked where they are consumed.
|
|
28
|
+
return message;
|
|
29
|
+
}
|
|
30
|
+
/** This client declares no capabilities: answer pings and refuse every other server request. */
|
|
31
|
+
function reply(request) {
|
|
32
|
+
return request.method === "ping"
|
|
33
|
+
? { jsonrpc: "2.0", id: request.id, result: {} }
|
|
34
|
+
: {
|
|
35
|
+
jsonrpc: "2.0",
|
|
36
|
+
id: request.id,
|
|
37
|
+
error: { code: -32601, message: `Unsupported method ${request.method}` },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Initialize a session: version negotiation, the initialized notification, then requests. */
|
|
41
|
+
export async function openMcpSession(open, label, timeoutMs, signal) {
|
|
42
|
+
const pending = new Map();
|
|
43
|
+
let failure;
|
|
44
|
+
let nextId = 1;
|
|
45
|
+
const fail = (error) => {
|
|
46
|
+
failure ??= error;
|
|
47
|
+
for (const settle of pending.values())
|
|
48
|
+
settle(failure);
|
|
49
|
+
pending.clear();
|
|
50
|
+
};
|
|
51
|
+
const transport = await open({
|
|
52
|
+
receive(message) {
|
|
53
|
+
if (message.id === undefined)
|
|
54
|
+
return;
|
|
55
|
+
if (message.method !== undefined)
|
|
56
|
+
void transport
|
|
57
|
+
.send(reply({ ...message, id: message.id }), AbortSignal.timeout(timeoutMs))
|
|
58
|
+
.catch(() => { });
|
|
59
|
+
else
|
|
60
|
+
pending.get(message.id)?.(message);
|
|
61
|
+
},
|
|
62
|
+
fail,
|
|
63
|
+
});
|
|
64
|
+
const notify = (method, params) => transport.send({ jsonrpc: "2.0", method, params }, AbortSignal.timeout(timeoutMs));
|
|
65
|
+
const request = async (method, params, caller) => {
|
|
66
|
+
if (failure)
|
|
67
|
+
throw failure;
|
|
68
|
+
throwIfAborted(caller, `MCP ${method}`);
|
|
69
|
+
const id = nextId++;
|
|
70
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
71
|
+
const deadline = caller ? AbortSignal.any([caller, timeout]) : timeout;
|
|
72
|
+
const response = new Promise((resolve, reject) => {
|
|
73
|
+
const settle = (outcome) => {
|
|
74
|
+
pending.delete(id);
|
|
75
|
+
deadline.removeEventListener("abort", abort);
|
|
76
|
+
if (outcome instanceof Error)
|
|
77
|
+
reject(outcome);
|
|
78
|
+
else
|
|
79
|
+
resolve(outcome);
|
|
80
|
+
};
|
|
81
|
+
const abort = () => {
|
|
82
|
+
// Best effort: tell the server to stop work this client will never observe.
|
|
83
|
+
notify("notifications/cancelled", { requestId: id }).catch(() => { });
|
|
84
|
+
settle(caller?.aborted
|
|
85
|
+
? createAbortError(caller, `MCP ${method}`)
|
|
86
|
+
: new Error(`MCP server ${label} timed out after ${timeoutMs} ms during ${method}`));
|
|
87
|
+
};
|
|
88
|
+
if (deadline.aborted)
|
|
89
|
+
return abort();
|
|
90
|
+
deadline.addEventListener("abort", abort, { once: true });
|
|
91
|
+
pending.set(id, settle);
|
|
92
|
+
});
|
|
93
|
+
transport
|
|
94
|
+
.send({ jsonrpc: "2.0", id, method, params }, deadline)
|
|
95
|
+
.catch((error) => pending.get(id)?.(error));
|
|
96
|
+
const { result, error } = await response;
|
|
97
|
+
if (error)
|
|
98
|
+
throw new McpRpcError(typeof error.code === "number" ? error.code : -32603, String(error.message ?? "Unknown error").slice(0, 4096));
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
const close = async () => {
|
|
102
|
+
fail(new Error(`MCP server ${label} is closed`));
|
|
103
|
+
await transport.close();
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const result = await request("initialize", {
|
|
107
|
+
protocolVersion: PROTOCOL_VERSIONS[0],
|
|
108
|
+
capabilities: {},
|
|
109
|
+
clientInfo: { name: "tubeless", version: "unversioned" },
|
|
110
|
+
}, signal);
|
|
111
|
+
if (!isRecord(result) ||
|
|
112
|
+
typeof result.protocolVersion !== "string" ||
|
|
113
|
+
!PROTOCOL_VERSIONS.includes(result.protocolVersion))
|
|
114
|
+
throw new Error(`MCP server ${label} requires an unsupported protocol version`);
|
|
115
|
+
await notify("notifications/initialized");
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
await close().catch(() => { });
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
return { request, close };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=mcp-session.js.map
|