@smthrs/mcp 0.0.0-stage → 1.0.0-rc.4
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/CHANGELOG.md +121 -0
- package/LICENSE +21 -0
- package/README.md +111 -2
- package/dist/cjs/Diagnostics.d.ts +46 -0
- package/dist/cjs/Diagnostics.d.ts.map +1 -0
- package/dist/cjs/Diagnostics.js +29 -0
- package/dist/cjs/Diagnostics.js.map +7 -0
- package/dist/cjs/McpClient.d.ts +304 -0
- package/dist/cjs/McpClient.d.ts.map +1 -0
- package/dist/cjs/McpClient.js +622 -0
- package/dist/cjs/McpClient.js.map +7 -0
- package/dist/cjs/McpError.d.ts +44 -0
- package/dist/cjs/McpError.d.ts.map +1 -0
- package/dist/cjs/McpError.js +41 -0
- package/dist/cjs/McpError.js.map +7 -0
- package/dist/cjs/McpFlows.d.ts +113 -0
- package/dist/cjs/McpFlows.d.ts.map +1 -0
- package/dist/cjs/McpFlows.js +127 -0
- package/dist/cjs/McpFlows.js.map +7 -0
- package/dist/cjs/index.d.ts +39 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +41 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/DiagnosticReporter.d.ts +17 -0
- package/dist/cjs/internal/DiagnosticReporter.d.ts.map +1 -0
- package/dist/cjs/internal/DiagnosticReporter.js +56 -0
- package/dist/cjs/internal/DiagnosticReporter.js.map +7 -0
- package/dist/cjs/internal/HttpTransport.d.ts +67 -0
- package/dist/cjs/internal/HttpTransport.d.ts.map +1 -0
- package/dist/cjs/internal/HttpTransport.js +298 -0
- package/dist/cjs/internal/HttpTransport.js.map +7 -0
- package/dist/cjs/internal/JsonLimits.d.ts +29 -0
- package/dist/cjs/internal/JsonLimits.d.ts.map +1 -0
- package/dist/cjs/internal/JsonLimits.js +51 -0
- package/dist/cjs/internal/JsonLimits.js.map +7 -0
- package/dist/cjs/internal/Limits.d.ts +36 -0
- package/dist/cjs/internal/Limits.d.ts.map +1 -0
- package/dist/cjs/internal/Limits.js +34 -0
- package/dist/cjs/internal/Limits.js.map +7 -0
- package/dist/cjs/internal/Rpc.d.ts +141 -0
- package/dist/cjs/internal/Rpc.d.ts.map +1 -0
- package/dist/cjs/internal/Rpc.js +92 -0
- package/dist/cjs/internal/Rpc.js.map +7 -0
- package/dist/cjs/internal/StdioTransport.d.ts +78 -0
- package/dist/cjs/internal/StdioTransport.d.ts.map +1 -0
- package/dist/cjs/internal/StdioTransport.js +310 -0
- package/dist/cjs/internal/StdioTransport.js.map +7 -0
- package/dist/cjs/internal/Transport.d.ts +87 -0
- package/dist/cjs/internal/Transport.d.ts.map +1 -0
- package/dist/cjs/internal/Transport.js +116 -0
- package/dist/cjs/internal/Transport.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Diagnostics.d.ts +46 -0
- package/dist/esm/Diagnostics.d.ts.map +1 -0
- package/dist/esm/Diagnostics.js +26 -0
- package/dist/esm/Diagnostics.js.map +1 -0
- package/dist/esm/McpClient.d.ts +304 -0
- package/dist/esm/McpClient.d.ts.map +1 -0
- package/dist/esm/McpClient.js +671 -0
- package/dist/esm/McpClient.js.map +1 -0
- package/dist/esm/McpError.d.ts +44 -0
- package/dist/esm/McpError.d.ts.map +1 -0
- package/dist/esm/McpError.js +43 -0
- package/dist/esm/McpError.js.map +1 -0
- package/dist/esm/McpFlows.d.ts +113 -0
- package/dist/esm/McpFlows.d.ts.map +1 -0
- package/dist/esm/McpFlows.js +168 -0
- package/dist/esm/McpFlows.js.map +1 -0
- package/dist/esm/index.d.ts +39 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/DiagnosticReporter.d.ts +17 -0
- package/dist/esm/internal/DiagnosticReporter.d.ts.map +1 -0
- package/dist/esm/internal/DiagnosticReporter.js +44 -0
- package/dist/esm/internal/DiagnosticReporter.js.map +1 -0
- package/dist/esm/internal/HttpTransport.d.ts +67 -0
- package/dist/esm/internal/HttpTransport.d.ts.map +1 -0
- package/dist/esm/internal/HttpTransport.js +266 -0
- package/dist/esm/internal/HttpTransport.js.map +1 -0
- package/dist/esm/internal/JsonLimits.d.ts +29 -0
- package/dist/esm/internal/JsonLimits.d.ts.map +1 -0
- package/dist/esm/internal/JsonLimits.js +55 -0
- package/dist/esm/internal/JsonLimits.js.map +1 -0
- package/dist/esm/internal/Limits.d.ts +36 -0
- package/dist/esm/internal/Limits.d.ts.map +1 -0
- package/dist/esm/internal/Limits.js +41 -0
- package/dist/esm/internal/Limits.js.map +1 -0
- package/dist/esm/internal/Rpc.d.ts +141 -0
- package/dist/esm/internal/Rpc.d.ts.map +1 -0
- package/dist/esm/internal/Rpc.js +129 -0
- package/dist/esm/internal/Rpc.js.map +1 -0
- package/dist/esm/internal/StdioTransport.d.ts +78 -0
- package/dist/esm/internal/StdioTransport.d.ts.map +1 -0
- package/dist/esm/internal/StdioTransport.js +332 -0
- package/dist/esm/internal/StdioTransport.js.map +1 -0
- package/dist/esm/internal/Transport.d.ts +87 -0
- package/dist/esm/internal/Transport.d.ts.map +1 -0
- package/dist/esm/internal/Transport.js +146 -0
- package/dist/esm/internal/Transport.js.map +1 -0
- package/docs/README.md +139 -0
- package/docs/api.md +469 -0
- package/docs/concepts/the-session.md +135 -0
- package/docs/concepts/tools-as-flows.md +116 -0
- package/docs/guides/bound-an-untrusted-server.md +158 -0
- package/docs/guides/configure-servers-for-the-cli.md +167 -0
- package/docs/guides/connect-a-server.md +161 -0
- package/docs/guides/grant-authority-to-mcp-tools.md +130 -0
- package/docs/guides/handle-a-failed-tool-call.md +125 -0
- package/docs/guides/select-the-tools-a-run-sees.md +92 -0
- package/docs/guides/testing.md +132 -0
- package/docs/guides/validate-structured-output.md +103 -0
- package/docs/installation.md +117 -0
- package/docs/quickstart.md +200 -0
- package/docs/troubleshooting.md +316 -0
- package/package.json +157 -3
- package/src/Diagnostics.ts +47 -0
- package/src/McpClient.ts +985 -0
- package/src/McpError.ts +52 -0
- package/src/McpFlows.ts +211 -0
- package/src/index.ts +42 -0
- package/src/internal/DiagnosticReporter.ts +47 -0
- package/src/internal/HttpTransport.ts +400 -0
- package/src/internal/JsonLimits.ts +53 -0
- package/src/internal/Limits.ts +48 -0
- package/src/internal/Rpc.ts +219 -0
- package/src/internal/StdioTransport.ts +491 -0
- package/src/internal/Transport.ts +178 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Context Protocol client and flow bindings.
|
|
3
|
+
*
|
|
4
|
+
* `McpClient` speaks the stdio protocol to a configured MCP server, and
|
|
5
|
+
* `McpFlows` projects that server's tools into the flow catalog as
|
|
6
|
+
* `mcp/<server>/<tool>` bindings. `@smthrs/cli` composes both behind
|
|
7
|
+
* `--mcp-config`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { McpFlows } from "@smthrs/mcp"
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @since 1.0.0-rc.0
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @category services
|
|
17
|
+
* @since 1.0.0-rc.0
|
|
18
|
+
* @slop
|
|
19
|
+
*/
|
|
20
|
+
export * as McpClient from "./McpClient.js";
|
|
21
|
+
/**
|
|
22
|
+
* @category services
|
|
23
|
+
* @since 1.0.0-rc.0
|
|
24
|
+
* @slop
|
|
25
|
+
*/
|
|
26
|
+
export * as Diagnostics from "./Diagnostics.js";
|
|
27
|
+
/**
|
|
28
|
+
* @category errors
|
|
29
|
+
* @since 1.0.0-rc.0
|
|
30
|
+
* @slop
|
|
31
|
+
*/
|
|
32
|
+
export * as McpError from "./McpError.js";
|
|
33
|
+
/**
|
|
34
|
+
* @category services
|
|
35
|
+
* @since 1.0.0-rc.0
|
|
36
|
+
* @slop
|
|
37
|
+
*/
|
|
38
|
+
export * as McpFlows from "./McpFlows.js";
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;;GAIG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA","sourcesContent":["/**\n * Model Context Protocol client and flow bindings.\n *\n * `McpClient` speaks the stdio protocol to a configured MCP server, and\n * `McpFlows` projects that server's tools into the flow catalog as\n * `mcp/<server>/<tool>` bindings. `@smthrs/cli` composes both behind\n * `--mcp-config`.\n *\n * ```ts\n * import { McpFlows } from \"@smthrs/mcp\"\n * ```\n *\n * @since 1.0.0-rc.0\n */\n\n/**\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport * as McpClient from \"./McpClient.ts\"\n\n/**\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport * as Diagnostics from \"./Diagnostics.ts\"\n\n/**\n * @category errors\n * @since 1.0.0-rc.0\n * @slop\n */\nexport * as McpError from \"./McpError.ts\"\n\n/**\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport * as McpFlows from \"./McpFlows.ts\"\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal optional-observer capture. Every detail is redacted with
|
|
3
|
+
* `Redaction.redactDiagnostic` and then wrapped in `Redacted`. Stderr is redacted by the
|
|
4
|
+
* transport, one whole line at a time, before its byte cap.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0-rc.0
|
|
7
|
+
*/
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
import * as Diagnostics from "../Diagnostics.ts";
|
|
10
|
+
/**
|
|
11
|
+
* Captures the optional host observer without introducing a required service.
|
|
12
|
+
*
|
|
13
|
+
* @category constructors
|
|
14
|
+
* @since 1.0.0-rc.0
|
|
15
|
+
*/
|
|
16
|
+
export declare const make: (server: string) => Effect.Effect<(source: Diagnostics.Event["source"], detail: unknown) => void, never, never>;
|
|
17
|
+
//# sourceMappingURL=DiagnosticReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiagnosticReporter.d.ts","sourceRoot":"","sources":["../../../src/internal/DiagnosticReporter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,MAAM,EAAoB,MAAM,QAAQ,CAAA;AACjD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,4BAGR,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,OAAO,KAAG,IAAI,eAyB3E,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal optional-observer capture. Every detail is redacted with
|
|
3
|
+
* `Redaction.redactDiagnostic` and then wrapped in `Redacted`. Stderr is redacted by the
|
|
4
|
+
* transport, one whole line at a time, before its byte cap.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0-rc.0
|
|
7
|
+
*/
|
|
8
|
+
import * as Redaction from "@smthrs/journal/Redaction";
|
|
9
|
+
import { Effect, Option, Redacted } from "effect";
|
|
10
|
+
import * as Diagnostics from "../Diagnostics.js";
|
|
11
|
+
/**
|
|
12
|
+
* Captures the optional host observer without introducing a required service.
|
|
13
|
+
*
|
|
14
|
+
* @category constructors
|
|
15
|
+
* @since 1.0.0-rc.0
|
|
16
|
+
*/
|
|
17
|
+
export const make = (server) => Effect.map(Effect.serviceOption(Diagnostics.Diagnostics), (observer) => (source, detail) => {
|
|
18
|
+
if (Option.isNone(observer))
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
// A remote error can echo a credential and an argument snapshot can
|
|
22
|
+
// carry one under a secret key, so every source is redacted here.
|
|
23
|
+
// StdioTransport redacts stderr line by line as it arrives, holding a
|
|
24
|
+
// value that spans lines until it closes, and caps only the redacted
|
|
25
|
+
// text to `maxStderrBytes`, so the cap cannot cut a credential's
|
|
26
|
+
// recognizable prefix. Redacting that capped tail again would regrow
|
|
27
|
+
// it past the cap.
|
|
28
|
+
const redacted = source === "stderr" ? detail : Redaction.redactDiagnostic(detail);
|
|
29
|
+
const text = typeof redacted === "string" ? redacted : JSON.stringify(redacted);
|
|
30
|
+
const bytes = new TextEncoder().encode(text);
|
|
31
|
+
const truncated = bytes.byteLength > 16_384;
|
|
32
|
+
observer.value.report({
|
|
33
|
+
server,
|
|
34
|
+
source,
|
|
35
|
+
detail: Redacted.make(new TextDecoder().decode(bytes.subarray(0, 16_384), { stream: truncated })),
|
|
36
|
+
truncated
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Observer failures are not MCP failures. Their messages can themselves
|
|
41
|
+
// contain the diagnostic, so neither attach nor log the thrown value.
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=DiagnosticReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiagnosticReporter.js","sourceRoot":"","sources":["../../../src/internal/DiagnosticReporter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,SAAS,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,EAAE,CACrC,MAAM,CAAC,GAAG,CACR,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,WAAW,CAAC,EAC7C,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,MAAmC,EAAE,MAAe,EAAQ,EAAE;IAC3E,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAM;IACnC,IAAI,CAAC;QACH,oEAAoE;QACpE,kEAAkE;QAClE,sEAAsE;QACtE,qEAAqE;QACrE,iEAAiE;QACjE,qEAAqE;QACrE,mBAAmB;QACnB,MAAM,QAAQ,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAA;QAClF,MAAM,IAAI,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAC/E,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;QAC3C,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;YACpB,MAAM;YACN,MAAM;YACN,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACjG,SAAS;SACV,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,sEAAsE;IACxE,CAAC;AACH,CAAC,CACF,CAAA","sourcesContent":["/**\n * Internal optional-observer capture. Every detail is redacted with\n * `Redaction.redactDiagnostic` and then wrapped in `Redacted`. Stderr is redacted by the\n * transport, one whole line at a time, before its byte cap.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Redaction from \"@smthrs/journal/Redaction\"\nimport { Effect, Option, Redacted } from \"effect\"\nimport * as Diagnostics from \"../Diagnostics.ts\"\n\n/**\n * Captures the optional host observer without introducing a required service.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const make = (server: string) =>\n Effect.map(\n Effect.serviceOption(Diagnostics.Diagnostics),\n (observer) => (source: Diagnostics.Event[\"source\"], detail: unknown): void => {\n if (Option.isNone(observer)) return\n try {\n // A remote error can echo a credential and an argument snapshot can\n // carry one under a secret key, so every source is redacted here.\n // StdioTransport redacts stderr line by line as it arrives, holding a\n // value that spans lines until it closes, and caps only the redacted\n // text to `maxStderrBytes`, so the cap cannot cut a credential's\n // recognizable prefix. Redacting that capped tail again would regrow\n // it past the cap.\n const redacted = source === \"stderr\" ? detail : Redaction.redactDiagnostic(detail)\n const text = typeof redacted === \"string\" ? redacted : JSON.stringify(redacted)\n const bytes = new TextEncoder().encode(text)\n const truncated = bytes.byteLength > 16_384\n observer.value.report({\n server,\n source,\n detail: Redacted.make(new TextDecoder().decode(bytes.subarray(0, 16_384), { stream: truncated })),\n truncated\n })\n } catch {\n // Observer failures are not MCP failures. Their messages can themselves\n // contain the diagnostic, so neither attach nor log the thrown value.\n }\n }\n )\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC over the MCP Streamable HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Every client message is one `POST` to the server's endpoint. A notification
|
|
5
|
+
* is answered with `202 Accepted`; a request is answered either with one
|
|
6
|
+
* `application/json` reply or with a `text/event-stream` whose events carry
|
|
7
|
+
* server notifications, server requests, and finally the reply. The server's
|
|
8
|
+
* `Mcp-Session-Id` from `initialize` is sent on every later message, together
|
|
9
|
+
* with the negotiated `MCP-Protocol-Version`.
|
|
10
|
+
*
|
|
11
|
+
* Requests go through the `HttpClient` in context, which is the host's egress
|
|
12
|
+
* client: its proxy, pinning, and capability checks apply unchanged, and a
|
|
13
|
+
* destination the egress policy denies fails with `connection_closed`.
|
|
14
|
+
*
|
|
15
|
+
* Out of scope: the optional standalone `GET` event stream, resuming a broken
|
|
16
|
+
* event stream with `Last-Event-ID`, and OAuth discovery. An expired session
|
|
17
|
+
* fails rather than re-initializing, so no request is ever replayed.
|
|
18
|
+
*
|
|
19
|
+
* @since 1.0.0-rc.1
|
|
20
|
+
*/
|
|
21
|
+
import { Effect, type Redacted, type Scope } from "effect";
|
|
22
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
23
|
+
import type { McpError } from "../McpError.ts";
|
|
24
|
+
import * as Transport from "./Transport.ts";
|
|
25
|
+
/**
|
|
26
|
+
* Supplies the bearer credential for a Streamable HTTP server.
|
|
27
|
+
*
|
|
28
|
+
* `token` runs once per HTTP message, so a provider can rotate or refresh the
|
|
29
|
+
* credential between messages. Its failure fails that message unchanged.
|
|
30
|
+
*
|
|
31
|
+
* @category models
|
|
32
|
+
* @since 1.0.0-rc.1
|
|
33
|
+
*/
|
|
34
|
+
export interface AuthProvider {
|
|
35
|
+
readonly token: Effect.Effect<Redacted.Redacted<string>, McpError>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options accepted by {@link connect}.
|
|
39
|
+
*
|
|
40
|
+
* @category models
|
|
41
|
+
* @since 1.0.0-rc.1
|
|
42
|
+
*/
|
|
43
|
+
export interface ConnectOptions {
|
|
44
|
+
/** The name this server is known by, for error messages only. */
|
|
45
|
+
readonly server: string;
|
|
46
|
+
/** The server's MCP endpoint: an absolute `http:` or `https:` URL without credentials. */
|
|
47
|
+
readonly url: string;
|
|
48
|
+
/** Bearer credential source. Omit for a server that needs none. */
|
|
49
|
+
readonly authProvider?: AuthProvider | undefined;
|
|
50
|
+
/** Default deadline for a request/reply exchange. See {@link Transport.defaultRequestTimeoutMs}. */
|
|
51
|
+
readonly requestTimeoutMs?: number | undefined;
|
|
52
|
+
/** Maximum UTF-8 bytes accepted in one inbound JSON-RPC message. See {@link Transport.defaultMaxFrameBytes}. */
|
|
53
|
+
readonly maxFrameBytes?: number | undefined;
|
|
54
|
+
/** Maximum UTF-8 bytes emitted in one JSON-RPC message. See {@link Transport.defaultMaxOutboundFrameBytes}. */
|
|
55
|
+
readonly maxOutboundFrameBytes?: number | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Opens a Streamable HTTP session and returns a live {@link Transport.Transport}.
|
|
59
|
+
* No message is sent until the first request; the endpoint and limits are
|
|
60
|
+
* checked first. Closing the calling scope ends the session with a
|
|
61
|
+
* best-effort `DELETE` and rejects later traffic.
|
|
62
|
+
*
|
|
63
|
+
* @category constructors
|
|
64
|
+
* @since 1.0.0-rc.1
|
|
65
|
+
*/
|
|
66
|
+
export declare const connect: (options: ConnectOptions) => Effect.Effect<Transport.Transport, McpError, HttpClient.HttpClient | Scope.Scope>;
|
|
67
|
+
//# sourceMappingURL=HttpTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpTransport.d.ts","sourceRoot":"","sources":["../../../src/internal/HttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAY,MAAM,EAAU,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAU,MAAM,QAAQ,CAAA;AACpF,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAA;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAK9C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAA;CACnE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;IAChD,oGAAoG;IACpG,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9C,gHAAgH;IAChH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,+GAA+G;IAC/G,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpD;AAoBD;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAClB,SAAS,cAAc,KACtB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,CA8S/E,CAAA"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC over the MCP Streamable HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Every client message is one `POST` to the server's endpoint. A notification
|
|
5
|
+
* is answered with `202 Accepted`; a request is answered either with one
|
|
6
|
+
* `application/json` reply or with a `text/event-stream` whose events carry
|
|
7
|
+
* server notifications, server requests, and finally the reply. The server's
|
|
8
|
+
* `Mcp-Session-Id` from `initialize` is sent on every later message, together
|
|
9
|
+
* with the negotiated `MCP-Protocol-Version`.
|
|
10
|
+
*
|
|
11
|
+
* Requests go through the `HttpClient` in context, which is the host's egress
|
|
12
|
+
* client: its proxy, pinning, and capability checks apply unchanged, and a
|
|
13
|
+
* destination the egress policy denies fails with `connection_closed`.
|
|
14
|
+
*
|
|
15
|
+
* Out of scope: the optional standalone `GET` event stream, resuming a broken
|
|
16
|
+
* event stream with `Last-Event-ID`, and OAuth discovery. An expired session
|
|
17
|
+
* fails rather than re-initializing, so no request is ever replayed.
|
|
18
|
+
*
|
|
19
|
+
* @since 1.0.0-rc.1
|
|
20
|
+
*/
|
|
21
|
+
import { isRecord } from "@smthrs/canonical/Record";
|
|
22
|
+
import * as KernelHttpClient from "@smthrs/kernel/HttpClient";
|
|
23
|
+
import { Deferred, Effect, Option, Stream } from "effect";
|
|
24
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
25
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
26
|
+
import * as DiagnosticReporter from "./DiagnosticReporter.js";
|
|
27
|
+
import * as JsonLimits from "./JsonLimits.js";
|
|
28
|
+
import * as Limits from "./Limits.js";
|
|
29
|
+
import * as Rpc from "./Rpc.js";
|
|
30
|
+
import * as Transport from "./Transport.js";
|
|
31
|
+
/** Deadline for the best-effort `DELETE` that ends a session on scope close. */
|
|
32
|
+
const sessionCloseMs = 1_000;
|
|
33
|
+
/** A session id is visible ASCII only (0x21-0x7E). */
|
|
34
|
+
const validSessionId = /^[\x21-\x7e]+$/;
|
|
35
|
+
const endpointOf = (url) => {
|
|
36
|
+
if (!URL.canParse(url))
|
|
37
|
+
return undefined;
|
|
38
|
+
const parsed = new URL(url);
|
|
39
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
40
|
+
return undefined;
|
|
41
|
+
if (parsed.username !== "" || parsed.password !== "")
|
|
42
|
+
return undefined;
|
|
43
|
+
return parsed;
|
|
44
|
+
};
|
|
45
|
+
const utf8 = new TextEncoder();
|
|
46
|
+
const mediaType = (header) => (header ?? "").split(";")[0].trim().toLowerCase();
|
|
47
|
+
/**
|
|
48
|
+
* Opens a Streamable HTTP session and returns a live {@link Transport.Transport}.
|
|
49
|
+
* No message is sent until the first request; the endpoint and limits are
|
|
50
|
+
* checked first. Closing the calling scope ends the session with a
|
|
51
|
+
* best-effort `DELETE` and rejects later traffic.
|
|
52
|
+
*
|
|
53
|
+
* @category constructors
|
|
54
|
+
* @since 1.0.0-rc.1
|
|
55
|
+
*/
|
|
56
|
+
export const connect = (options) => Effect.gen(function* () {
|
|
57
|
+
const server = options.server;
|
|
58
|
+
const diagnostic = yield* DiagnosticReporter.make(server);
|
|
59
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? Transport.defaultRequestTimeoutMs;
|
|
60
|
+
const maxFrameBytes = options.maxFrameBytes ?? Transport.defaultMaxFrameBytes;
|
|
61
|
+
const maxOutboundFrameBytes = options.maxOutboundFrameBytes ?? Transport.defaultMaxOutboundFrameBytes;
|
|
62
|
+
yield* Limits.checkPositiveIntegers(server, [
|
|
63
|
+
["requestTimeoutMs", requestTimeoutMs],
|
|
64
|
+
["maxFrameBytes", maxFrameBytes],
|
|
65
|
+
["maxOutboundFrameBytes", maxOutboundFrameBytes]
|
|
66
|
+
]);
|
|
67
|
+
const endpoint = endpointOf(options.url);
|
|
68
|
+
if (endpoint === undefined) {
|
|
69
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" url must be an absolute http or https URL without credentials`));
|
|
70
|
+
}
|
|
71
|
+
// Each message runs in its own scope, which aborts the HTTP request and
|
|
72
|
+
// releases its body however the exchange ends.
|
|
73
|
+
const client = HttpClient.withScope(yield* HttpClient.HttpClient);
|
|
74
|
+
const scope = yield* Effect.scope;
|
|
75
|
+
const closing = yield* Deferred.make();
|
|
76
|
+
let open = true;
|
|
77
|
+
let sessionId;
|
|
78
|
+
let protocolVersion;
|
|
79
|
+
let nextId = 0;
|
|
80
|
+
const unreachable = (error) => {
|
|
81
|
+
diagnostic("transport", error.message);
|
|
82
|
+
return Option.isSome(KernelHttpClient.fromHttpClientError(error))
|
|
83
|
+
? Transport.closed(server, "is not reachable: egress to its URL is not granted")
|
|
84
|
+
: Transport.closed(server, "is not reachable; transport details withheld");
|
|
85
|
+
};
|
|
86
|
+
const frameOf = (method, message) => {
|
|
87
|
+
// Rpc.encode appends the stdio line terminator; an HTTP body has none.
|
|
88
|
+
const frame = Rpc.encode(message).subarray(0, -1);
|
|
89
|
+
return frame.byteLength <= maxOutboundFrameBytes
|
|
90
|
+
? Effect.succeed(frame)
|
|
91
|
+
: Effect.fail(Limits.protocolError(server, `MCP server "${server}" tried to send a ${method} frame larger than ${maxOutboundFrameBytes} bytes`));
|
|
92
|
+
};
|
|
93
|
+
const withSession = (request) => {
|
|
94
|
+
let next = request;
|
|
95
|
+
if (sessionId !== undefined)
|
|
96
|
+
next = HttpClientRequest.setHeader(next, "mcp-session-id", sessionId);
|
|
97
|
+
if (protocolVersion !== undefined) {
|
|
98
|
+
next = HttpClientRequest.setHeader(next, "mcp-protocol-version", protocolVersion);
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
};
|
|
102
|
+
/** Reads a whole body, failing once it passes `maxFrameBytes`. */
|
|
103
|
+
const readBody = (stream) => stream.pipe(Stream.mapError(unreachable), Stream.runFoldEffect(() => ({ chunks: [], bytes: 0 }), (body, chunk) => {
|
|
104
|
+
body.bytes += chunk.byteLength;
|
|
105
|
+
if (body.bytes > maxFrameBytes) {
|
|
106
|
+
return Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`));
|
|
107
|
+
}
|
|
108
|
+
body.chunks.push(chunk);
|
|
109
|
+
return Effect.succeed(body);
|
|
110
|
+
}), Effect.map((body) => {
|
|
111
|
+
const joined = new Uint8Array(body.bytes);
|
|
112
|
+
let offset = 0;
|
|
113
|
+
for (const chunk of body.chunks) {
|
|
114
|
+
joined.set(chunk, offset);
|
|
115
|
+
offset += chunk.byteLength;
|
|
116
|
+
}
|
|
117
|
+
return new TextDecoder().decode(joined);
|
|
118
|
+
}));
|
|
119
|
+
const authorize = (request) => options.authProvider === undefined
|
|
120
|
+
? Effect.succeed(request)
|
|
121
|
+
: Effect.map(options.authProvider.token, (token) => HttpClientRequest.bearerToken(request, token));
|
|
122
|
+
/**
|
|
123
|
+
* Sends one message and returns the raw response, or fails with a
|
|
124
|
+
* transport, status, or session failure.
|
|
125
|
+
*/
|
|
126
|
+
const post = (method, message, dispatched) => Effect.gen(function* () {
|
|
127
|
+
if (!open)
|
|
128
|
+
return yield* Effect.fail(Transport.closed(server, "connection scope closed"));
|
|
129
|
+
const body = yield* frameOf(method, message);
|
|
130
|
+
const request = yield* authorize(withSession(HttpClientRequest.post(endpoint).pipe(HttpClientRequest.setHeader("accept", "application/json, text/event-stream"), HttpClientRequest.bodyUint8Array(body, "application/json"))));
|
|
131
|
+
const sentSession = sessionId !== undefined;
|
|
132
|
+
if (dispatched !== undefined)
|
|
133
|
+
dispatched.value = true;
|
|
134
|
+
const response = yield* Effect.mapError(client.execute(request), unreachable);
|
|
135
|
+
if (response.status === 404 && sentSession) {
|
|
136
|
+
return yield* Effect.fail(Transport.closed(server, "ended the session; reconnect to continue"));
|
|
137
|
+
}
|
|
138
|
+
if (response.status < 200 || response.status > 299) {
|
|
139
|
+
diagnostic("remote-error", { status: response.status });
|
|
140
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" answered ${method} with HTTP ${response.status}`));
|
|
141
|
+
}
|
|
142
|
+
return response;
|
|
143
|
+
});
|
|
144
|
+
/** Sends a notification or a reply to a server request; the body is ignored. */
|
|
145
|
+
const deliver = (method, message) => Effect.scoped(Effect.flatMap(post(method, message), (response) => Effect.asVoid(readBody(response.stream))));
|
|
146
|
+
/** Fails `effect` with `connection_closed`, interrupting it, when the scope closes first. */
|
|
147
|
+
const untilClosed = (effect) => Effect.raceFirst(effect, Effect.andThen(Deferred.await(closing), Effect.fail(Transport.closed(server, "connection scope closed"))));
|
|
148
|
+
/**
|
|
149
|
+
* Handles one inbound JSON-RPC message during request `id`: server
|
|
150
|
+
* requests are answered, notifications dropped, and the reply returned.
|
|
151
|
+
*/
|
|
152
|
+
const receive = (method, id, text) => Effect.gen(function* () {
|
|
153
|
+
const message = Rpc.parse(text);
|
|
154
|
+
if (message === undefined) {
|
|
155
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" sent a message that is not JSON-RPC`));
|
|
156
|
+
}
|
|
157
|
+
const jsonIssue = JsonLimits.checkParsed(message);
|
|
158
|
+
if (jsonIssue !== undefined) {
|
|
159
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" sent invalid JSON: ${jsonIssue}`));
|
|
160
|
+
}
|
|
161
|
+
const reply = Rpc.classify(message);
|
|
162
|
+
switch (reply._tag) {
|
|
163
|
+
case "Notification":
|
|
164
|
+
return Option.none();
|
|
165
|
+
case "Request": {
|
|
166
|
+
yield* deliver("server-response", reply.method === "ping"
|
|
167
|
+
? { jsonrpc: "2.0", id: reply.id, result: {} }
|
|
168
|
+
: { jsonrpc: "2.0", id: reply.id, error: { code: -32_601, message: "Method not found" } });
|
|
169
|
+
return Option.none();
|
|
170
|
+
}
|
|
171
|
+
case "Malformed":
|
|
172
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" sent a malformed JSON-RPC reply: ${reply.reason}`));
|
|
173
|
+
case "UncorrelatedError":
|
|
174
|
+
diagnostic("remote-error", { code: reply.code, message: reply.message, data: reply.data });
|
|
175
|
+
return Option.none();
|
|
176
|
+
}
|
|
177
|
+
if (reply.id !== id) {
|
|
178
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" answered ${method} with a reply to another request`));
|
|
179
|
+
}
|
|
180
|
+
if (reply._tag === "Error") {
|
|
181
|
+
diagnostic("remote-error", { code: reply.code, message: reply.message, data: reply.data });
|
|
182
|
+
return yield* Effect.fail(Transport.replyError(server, method, reply));
|
|
183
|
+
}
|
|
184
|
+
return Option.some(reply.result);
|
|
185
|
+
});
|
|
186
|
+
/** Collects `data:` lines into events; a blank line ends one. */
|
|
187
|
+
const events = (stream) => Transport.lines(server, maxFrameBytes, Stream.mapError(stream, unreachable), { crTerminates: true }).pipe(Stream.mapAccumEffect(() => ({ data: undefined, bytes: 0 }), (event, line) => {
|
|
188
|
+
if (line === "") {
|
|
189
|
+
const data = event.data;
|
|
190
|
+
event.data = undefined;
|
|
191
|
+
event.bytes = 0;
|
|
192
|
+
return Effect.succeed([event, data === undefined ? [] : [data]]);
|
|
193
|
+
}
|
|
194
|
+
if (!line.startsWith("data:"))
|
|
195
|
+
return Effect.succeed([event, []]);
|
|
196
|
+
const value = line.slice(line.startsWith("data: ") ? 6 : 5);
|
|
197
|
+
event.bytes += utf8.encode(value).byteLength + (event.data === undefined ? 0 : 1);
|
|
198
|
+
event.data = event.data === undefined ? value : `${event.data}\n${value}`;
|
|
199
|
+
return event.bytes > maxFrameBytes
|
|
200
|
+
? Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`))
|
|
201
|
+
: Effect.succeed([event, []]);
|
|
202
|
+
}));
|
|
203
|
+
const exchange = (method, params, id, dispatched) => Effect.scoped(Effect.gen(function* () {
|
|
204
|
+
const response = yield* post(method, { jsonrpc: "2.0", id, method, params }, dispatched);
|
|
205
|
+
if (method === "initialize") {
|
|
206
|
+
const header = response.headers["mcp-session-id"];
|
|
207
|
+
if (header !== undefined && !validSessionId.test(header)) {
|
|
208
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" sent an invalid session id`));
|
|
209
|
+
}
|
|
210
|
+
sessionId = header;
|
|
211
|
+
}
|
|
212
|
+
const type = mediaType(response.headers["content-type"]);
|
|
213
|
+
let result;
|
|
214
|
+
if (type === "application/json") {
|
|
215
|
+
result = yield* Effect.flatMap(readBody(response.stream), (text) => receive(method, id, text));
|
|
216
|
+
}
|
|
217
|
+
else if (type === "text/event-stream") {
|
|
218
|
+
result = Option.flatten(yield* events(response.stream).pipe(Stream.mapEffect((data) => receive(method, id, data)), Stream.filter(Option.isSome), Stream.runHead));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
return yield* Effect.fail(Limits.protocolError(server, `MCP server "${server}" answered ${method} with an unsupported content type`));
|
|
222
|
+
}
|
|
223
|
+
if (Option.isNone(result)) {
|
|
224
|
+
return yield* Effect.fail(Transport.closed(server, `closed its response before answering ${method}`));
|
|
225
|
+
}
|
|
226
|
+
if (method === "initialize" && isRecord(result.value) && typeof result.value.protocolVersion === "string") {
|
|
227
|
+
protocolVersion = result.value.protocolVersion;
|
|
228
|
+
}
|
|
229
|
+
return result.value;
|
|
230
|
+
}));
|
|
231
|
+
const notify = (method, params, timeoutMs = requestTimeoutMs) => Limits.isPositiveInteger(timeoutMs)
|
|
232
|
+
? untilClosed(deliver(method, { jsonrpc: "2.0", method, params })).pipe(Effect.timeoutOrElse({
|
|
233
|
+
duration: timeoutMs,
|
|
234
|
+
orElse: () => Effect.fail(Transport.timeout(server, method, timeoutMs))
|
|
235
|
+
}))
|
|
236
|
+
: Effect.fail(Limits.protocolError(server, "MCP notification timeout must be a positive integer"));
|
|
237
|
+
const request = (method, params, timeoutMs = requestTimeoutMs) => Effect.suspend(() => {
|
|
238
|
+
if (!Limits.isPositiveInteger(timeoutMs)) {
|
|
239
|
+
return Effect.fail(Limits.protocolError(server, "MCP request timeout must be a positive integer"));
|
|
240
|
+
}
|
|
241
|
+
const id = ++nextId;
|
|
242
|
+
const dispatched = { value: false };
|
|
243
|
+
return exchange(method, params, id, dispatched).pipe(
|
|
244
|
+
// Closing the HTTP response is not a cancellation in MCP; tell the
|
|
245
|
+
// server explicitly, without delaying the deadline being reported.
|
|
246
|
+
Effect.onInterrupt(() => dispatched.value && open && method !== "initialize"
|
|
247
|
+
? Effect.asVoid(Effect.forkIn(Effect.ignore(notify("notifications/cancelled", {
|
|
248
|
+
requestId: id,
|
|
249
|
+
reason: Transport.cancellationReason
|
|
250
|
+
})), scope))
|
|
251
|
+
: Effect.void), untilClosed, Effect.timeoutOrElse({
|
|
252
|
+
duration: timeoutMs,
|
|
253
|
+
orElse: () => Effect.fail(Transport.timeout(server, method, timeoutMs))
|
|
254
|
+
}));
|
|
255
|
+
});
|
|
256
|
+
yield* Effect.addFinalizer(() => Effect.suspend(() => {
|
|
257
|
+
open = false;
|
|
258
|
+
const ended = Deferred.succeed(closing, undefined);
|
|
259
|
+
if (sessionId === undefined)
|
|
260
|
+
return ended;
|
|
261
|
+
return Effect.andThen(ended, Effect.scoped(Effect.flatMap(authorize(withSession(HttpClientRequest.delete(endpoint))), client.execute))
|
|
262
|
+
.pipe(Effect.timeout(sessionCloseMs), Effect.ignore));
|
|
263
|
+
}));
|
|
264
|
+
return { request, notify };
|
|
265
|
+
});
|
|
266
|
+
//# sourceMappingURL=HttpTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpTransport.js","sourceRoot":"","sources":["../../../src/internal/HttpTransport.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * JSON-RPC over the MCP Streamable HTTP transport.\n *\n * Every client message is one `POST` to the server's endpoint. A notification\n * is answered with `202 Accepted`; a request is answered either with one\n * `application/json` reply or with a `text/event-stream` whose events carry\n * server notifications, server requests, and finally the reply. The server's\n * `Mcp-Session-Id` from `initialize` is sent on every later message, together\n * with the negotiated `MCP-Protocol-Version`.\n *\n * Requests go through the `HttpClient` in context, which is the host's egress\n * client: its proxy, pinning, and capability checks apply unchanged, and a\n * destination the egress policy denies fails with `connection_closed`.\n *\n * Out of scope: the optional standalone `GET` event stream, resuming a broken\n * event stream with `Last-Event-ID`, and OAuth discovery. An expired session\n * fails rather than re-initializing, so no request is ever replayed.\n *\n * @since 1.0.0-rc.1\n */\n\nimport { isRecord } from \"@smthrs/canonical/Record\"\nimport * as KernelHttpClient from \"@smthrs/kernel/HttpClient\"\nimport { Deferred, Effect, Option, type Redacted, type Scope, Stream } from \"effect\"\nimport * as HttpClient from \"effect/unstable/http/HttpClient\"\nimport type * as HttpClientError from \"effect/unstable/http/HttpClientError\"\nimport * as HttpClientRequest from \"effect/unstable/http/HttpClientRequest\"\nimport type { McpError } from \"../McpError.ts\"\nimport * as DiagnosticReporter from \"./DiagnosticReporter.ts\"\nimport * as JsonLimits from \"./JsonLimits.ts\"\nimport * as Limits from \"./Limits.ts\"\nimport * as Rpc from \"./Rpc.ts\"\nimport * as Transport from \"./Transport.ts\"\n\n/**\n * Supplies the bearer credential for a Streamable HTTP server.\n *\n * `token` runs once per HTTP message, so a provider can rotate or refresh the\n * credential between messages. Its failure fails that message unchanged.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface AuthProvider {\n readonly token: Effect.Effect<Redacted.Redacted<string>, McpError>\n}\n\n/**\n * Options accepted by {@link connect}.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface ConnectOptions {\n /** The name this server is known by, for error messages only. */\n readonly server: string\n /** The server's MCP endpoint: an absolute `http:` or `https:` URL without credentials. */\n readonly url: string\n /** Bearer credential source. Omit for a server that needs none. */\n readonly authProvider?: AuthProvider | undefined\n /** Default deadline for a request/reply exchange. See {@link Transport.defaultRequestTimeoutMs}. */\n readonly requestTimeoutMs?: number | undefined\n /** Maximum UTF-8 bytes accepted in one inbound JSON-RPC message. See {@link Transport.defaultMaxFrameBytes}. */\n readonly maxFrameBytes?: number | undefined\n /** Maximum UTF-8 bytes emitted in one JSON-RPC message. See {@link Transport.defaultMaxOutboundFrameBytes}. */\n readonly maxOutboundFrameBytes?: number | undefined\n}\n\n/** Deadline for the best-effort `DELETE` that ends a session on scope close. */\nconst sessionCloseMs = 1_000\n\n/** A session id is visible ASCII only (0x21-0x7E). */\nconst validSessionId = /^[\\x21-\\x7e]+$/\n\nconst endpointOf = (url: string): URL | undefined => {\n if (!URL.canParse(url)) return undefined\n const parsed = new URL(url)\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return undefined\n if (parsed.username !== \"\" || parsed.password !== \"\") return undefined\n return parsed\n}\n\nconst utf8 = new TextEncoder()\n\nconst mediaType = (header: string | undefined): string => (header ?? \"\").split(\";\")[0]!.trim().toLowerCase()\n\n/**\n * Opens a Streamable HTTP session and returns a live {@link Transport.Transport}.\n * No message is sent until the first request; the endpoint and limits are\n * checked first. Closing the calling scope ends the session with a\n * best-effort `DELETE` and rejects later traffic.\n *\n * @category constructors\n * @since 1.0.0-rc.1\n */\nexport const connect = (\n options: ConnectOptions\n): Effect.Effect<Transport.Transport, McpError, HttpClient.HttpClient | Scope.Scope> =>\n Effect.gen(function*() {\n const server = options.server\n const diagnostic = yield* DiagnosticReporter.make(server)\n const requestTimeoutMs = options.requestTimeoutMs ?? Transport.defaultRequestTimeoutMs\n const maxFrameBytes = options.maxFrameBytes ?? Transport.defaultMaxFrameBytes\n const maxOutboundFrameBytes = options.maxOutboundFrameBytes ?? Transport.defaultMaxOutboundFrameBytes\n yield* Limits.checkPositiveIntegers(server, [\n [\"requestTimeoutMs\", requestTimeoutMs],\n [\"maxFrameBytes\", maxFrameBytes],\n [\"maxOutboundFrameBytes\", maxOutboundFrameBytes]\n ])\n const endpoint = endpointOf(options.url)\n if (endpoint === undefined) {\n return yield* Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" url must be an absolute http or https URL without credentials`\n ))\n }\n // Each message runs in its own scope, which aborts the HTTP request and\n // releases its body however the exchange ends.\n const client = HttpClient.withScope(yield* HttpClient.HttpClient)\n const scope = yield* Effect.scope\n const closing = yield* Deferred.make<void>()\n\n let open = true\n let sessionId: string | undefined\n let protocolVersion: string | undefined\n let nextId = 0\n\n const unreachable = (error: HttpClientError.HttpClientError): McpError => {\n diagnostic(\"transport\", error.message)\n return Option.isSome(KernelHttpClient.fromHttpClientError(error))\n ? Transport.closed(server, \"is not reachable: egress to its URL is not granted\")\n : Transport.closed(server, \"is not reachable; transport details withheld\")\n }\n\n const frameOf = (method: string, message: Rpc.OutboundMessage): Effect.Effect<Uint8Array, McpError> => {\n // Rpc.encode appends the stdio line terminator; an HTTP body has none.\n const frame = Rpc.encode(message).subarray(0, -1)\n return frame.byteLength <= maxOutboundFrameBytes\n ? Effect.succeed(frame)\n : Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" tried to send a ${method} frame larger than ${maxOutboundFrameBytes} bytes`\n ))\n }\n\n const withSession = (request: HttpClientRequest.HttpClientRequest): HttpClientRequest.HttpClientRequest => {\n let next = request\n if (sessionId !== undefined) next = HttpClientRequest.setHeader(next, \"mcp-session-id\", sessionId)\n if (protocolVersion !== undefined) {\n next = HttpClientRequest.setHeader(next, \"mcp-protocol-version\", protocolVersion)\n }\n return next\n }\n\n /** Reads a whole body, failing once it passes `maxFrameBytes`. */\n const readBody = (stream: Stream.Stream<Uint8Array, HttpClientError.HttpClientError>) =>\n stream.pipe(\n Stream.mapError(unreachable),\n Stream.runFoldEffect(\n () => ({ chunks: [] as Array<Uint8Array>, bytes: 0 }),\n (body, chunk) => {\n body.bytes += chunk.byteLength\n if (body.bytes > maxFrameBytes) {\n return Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`))\n }\n body.chunks.push(chunk)\n return Effect.succeed(body)\n }\n ),\n Effect.map((body) => {\n const joined = new Uint8Array(body.bytes)\n let offset = 0\n for (const chunk of body.chunks) {\n joined.set(chunk, offset)\n offset += chunk.byteLength\n }\n return new TextDecoder().decode(joined)\n })\n )\n\n const authorize = (request: HttpClientRequest.HttpClientRequest) =>\n options.authProvider === undefined\n ? Effect.succeed(request)\n : Effect.map(options.authProvider.token, (token) => HttpClientRequest.bearerToken(request, token))\n\n /**\n * Sends one message and returns the raw response, or fails with a\n * transport, status, or session failure.\n */\n const post = (method: string, message: Rpc.OutboundMessage, dispatched?: { value: boolean }) =>\n Effect.gen(function*() {\n if (!open) return yield* Effect.fail(Transport.closed(server, \"connection scope closed\"))\n const body = yield* frameOf(method, message)\n const request = yield* authorize(withSession(\n HttpClientRequest.post(endpoint).pipe(\n HttpClientRequest.setHeader(\"accept\", \"application/json, text/event-stream\"),\n HttpClientRequest.bodyUint8Array(body, \"application/json\")\n )\n ))\n const sentSession = sessionId !== undefined\n if (dispatched !== undefined) dispatched.value = true\n const response = yield* Effect.mapError(client.execute(request), unreachable)\n if (response.status === 404 && sentSession) {\n return yield* Effect.fail(Transport.closed(server, \"ended the session; reconnect to continue\"))\n }\n if (response.status < 200 || response.status > 299) {\n diagnostic(\"remote-error\", { status: response.status })\n return yield* Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" answered ${method} with HTTP ${response.status}`\n ))\n }\n return response\n })\n\n /** Sends a notification or a reply to a server request; the body is ignored. */\n const deliver = (method: string, message: Rpc.OutboundMessage) =>\n Effect.scoped(Effect.flatMap(post(method, message), (response) => Effect.asVoid(readBody(response.stream))))\n\n /** Fails `effect` with `connection_closed`, interrupting it, when the scope closes first. */\n const untilClosed = <A>(effect: Effect.Effect<A, McpError>) =>\n Effect.raceFirst(\n effect,\n Effect.andThen(Deferred.await(closing), Effect.fail(Transport.closed(server, \"connection scope closed\")))\n )\n\n /**\n * Handles one inbound JSON-RPC message during request `id`: server\n * requests are answered, notifications dropped, and the reply returned.\n */\n const receive = (method: string, id: number, text: string): Effect.Effect<Option.Option<unknown>, McpError> =>\n Effect.gen(function*() {\n const message = Rpc.parse(text)\n if (message === undefined) {\n return yield* Effect.fail(\n Limits.protocolError(server, `MCP server \"${server}\" sent a message that is not JSON-RPC`)\n )\n }\n const jsonIssue = JsonLimits.checkParsed(message)\n if (jsonIssue !== undefined) {\n return yield* Effect.fail(\n Limits.protocolError(server, `MCP server \"${server}\" sent invalid JSON: ${jsonIssue}`)\n )\n }\n const reply = Rpc.classify(message)\n switch (reply._tag) {\n case \"Notification\":\n return Option.none()\n case \"Request\": {\n yield* deliver(\n \"server-response\",\n reply.method === \"ping\"\n ? { jsonrpc: \"2.0\", id: reply.id, result: {} }\n : { jsonrpc: \"2.0\", id: reply.id, error: { code: -32_601, message: \"Method not found\" } }\n )\n return Option.none()\n }\n case \"Malformed\":\n return yield* Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" sent a malformed JSON-RPC reply: ${reply.reason}`\n ))\n case \"UncorrelatedError\":\n diagnostic(\"remote-error\", { code: reply.code, message: reply.message, data: reply.data })\n return Option.none()\n }\n if (reply.id !== id) {\n return yield* Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" answered ${method} with a reply to another request`\n ))\n }\n if (reply._tag === \"Error\") {\n diagnostic(\"remote-error\", { code: reply.code, message: reply.message, data: reply.data })\n return yield* Effect.fail(Transport.replyError(server, method, reply))\n }\n return Option.some(reply.result)\n })\n\n /** Collects `data:` lines into events; a blank line ends one. */\n const events = (stream: Stream.Stream<Uint8Array, HttpClientError.HttpClientError>) =>\n Transport.lines(server, maxFrameBytes, Stream.mapError(stream, unreachable), { crTerminates: true }).pipe(\n Stream.mapAccumEffect(\n () => ({ data: undefined as string | undefined, bytes: 0 }),\n (event, line) => {\n if (line === \"\") {\n const data = event.data\n event.data = undefined\n event.bytes = 0\n return Effect.succeed([event, data === undefined ? [] : [data]] as const)\n }\n if (!line.startsWith(\"data:\")) return Effect.succeed([event, []] as const)\n const value = line.slice(line.startsWith(\"data: \") ? 6 : 5)\n event.bytes += utf8.encode(value).byteLength + (event.data === undefined ? 0 : 1)\n event.data = event.data === undefined ? value : `${event.data}\\n${value}`\n return event.bytes > maxFrameBytes\n ? Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`))\n : Effect.succeed([event, []] as const)\n }\n )\n )\n\n const exchange = (method: string, params: unknown, id: number, dispatched: { value: boolean }) =>\n Effect.scoped(Effect.gen(function*() {\n const response = yield* post(method, { jsonrpc: \"2.0\", id, method, params }, dispatched)\n if (method === \"initialize\") {\n const header = response.headers[\"mcp-session-id\"]\n if (header !== undefined && !validSessionId.test(header)) {\n return yield* Effect.fail(Limits.protocolError(server, `MCP server \"${server}\" sent an invalid session id`))\n }\n sessionId = header\n }\n const type = mediaType(response.headers[\"content-type\"])\n let result: Option.Option<unknown>\n if (type === \"application/json\") {\n result = yield* Effect.flatMap(readBody(response.stream), (text) => receive(method, id, text))\n } else if (type === \"text/event-stream\") {\n result = Option.flatten(\n yield* events(response.stream).pipe(\n Stream.mapEffect((data) => receive(method, id, data)),\n Stream.filter(Option.isSome),\n Stream.runHead\n )\n )\n } else {\n return yield* Effect.fail(Limits.protocolError(\n server,\n `MCP server \"${server}\" answered ${method} with an unsupported content type`\n ))\n }\n if (Option.isNone(result)) {\n return yield* Effect.fail(Transport.closed(server, `closed its response before answering ${method}`))\n }\n if (method === \"initialize\" && isRecord(result.value) && typeof result.value.protocolVersion === \"string\") {\n protocolVersion = result.value.protocolVersion\n }\n return result.value\n }))\n\n const notify = (method: string, params?: unknown, timeoutMs = requestTimeoutMs): Effect.Effect<void, McpError> =>\n Limits.isPositiveInteger(timeoutMs)\n ? untilClosed(deliver(method, { jsonrpc: \"2.0\", method, params })).pipe(\n Effect.timeoutOrElse({\n duration: timeoutMs,\n orElse: () => Effect.fail(Transport.timeout(server, method, timeoutMs))\n })\n )\n : Effect.fail(Limits.protocolError(server, \"MCP notification timeout must be a positive integer\"))\n\n const request = (\n method: string,\n params?: unknown,\n timeoutMs = requestTimeoutMs\n ): Effect.Effect<unknown, McpError> =>\n Effect.suspend(() => {\n if (!Limits.isPositiveInteger(timeoutMs)) {\n return Effect.fail(Limits.protocolError(server, \"MCP request timeout must be a positive integer\"))\n }\n const id = ++nextId\n const dispatched = { value: false }\n return exchange(method, params, id, dispatched).pipe(\n // Closing the HTTP response is not a cancellation in MCP; tell the\n // server explicitly, without delaying the deadline being reported.\n Effect.onInterrupt(() =>\n dispatched.value && open && method !== \"initialize\"\n ? Effect.asVoid(Effect.forkIn(\n Effect.ignore(notify(\"notifications/cancelled\", {\n requestId: id,\n reason: Transport.cancellationReason\n })),\n scope\n ))\n : Effect.void\n ),\n untilClosed,\n Effect.timeoutOrElse({\n duration: timeoutMs,\n orElse: () => Effect.fail(Transport.timeout(server, method, timeoutMs))\n })\n )\n })\n\n yield* Effect.addFinalizer(() =>\n Effect.suspend(() => {\n open = false\n const ended = Deferred.succeed(closing, undefined)\n if (sessionId === undefined) return ended\n return Effect.andThen(\n ended,\n Effect.scoped(Effect.flatMap(authorize(withSession(HttpClientRequest.delete(endpoint))), client.execute))\n .pipe(\n Effect.timeout(sessionCloseMs),\n Effect.ignore\n )\n )\n })\n )\n\n return { request, notify }\n })\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource bounds checked before recursive JSON consumers see server data.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0-rc.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Maximum nested containers, counting the wire envelope as the first one.
|
|
8
|
+
*
|
|
9
|
+
* @category constants
|
|
10
|
+
* @since 1.0.0-rc.0
|
|
11
|
+
*/
|
|
12
|
+
export declare const maxDepth = 128;
|
|
13
|
+
/**
|
|
14
|
+
* Checks a JSON.parse result iteratively. This is not for arbitrary JS objects
|
|
15
|
+
* with executable accessors; outbound arguments use guarded descriptors.
|
|
16
|
+
*
|
|
17
|
+
* @category validation
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
*/
|
|
20
|
+
export declare const checkParsed: (value: unknown) => string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Freezes validated acyclic catalog data before it is exposed to consumers.
|
|
23
|
+
* The private dispatcher and public catalog must retain the same contract.
|
|
24
|
+
*
|
|
25
|
+
* @category utils
|
|
26
|
+
* @since 1.0.0-rc.0
|
|
27
|
+
*/
|
|
28
|
+
export declare const freezeParsed: (value: unknown) => void;
|
|
29
|
+
//# sourceMappingURL=JsonLimits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonLimits.d.ts","sourceRoot":"","sources":["../../../src/internal/JsonLimits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAcrD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,IAQ7C,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource bounds checked before recursive JSON consumers see server data.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0-rc.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Maximum nested containers, counting the wire envelope as the first one.
|
|
8
|
+
*
|
|
9
|
+
* @category constants
|
|
10
|
+
* @since 1.0.0-rc.0
|
|
11
|
+
*/
|
|
12
|
+
export const maxDepth = 128;
|
|
13
|
+
/**
|
|
14
|
+
* Checks a JSON.parse result iteratively. This is not for arbitrary JS objects
|
|
15
|
+
* with executable accessors; outbound arguments use guarded descriptors.
|
|
16
|
+
*
|
|
17
|
+
* @category validation
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
*/
|
|
20
|
+
export const checkParsed = (value) => {
|
|
21
|
+
const pending = [{ value, depth: 1 }];
|
|
22
|
+
while (pending.length > 0) {
|
|
23
|
+
const current = pending.pop();
|
|
24
|
+
if (typeof current.value === "number" && !Number.isFinite(current.value)) {
|
|
25
|
+
return "a JSON number is outside the finite range";
|
|
26
|
+
}
|
|
27
|
+
if (typeof current.value !== "object" || current.value === null)
|
|
28
|
+
continue;
|
|
29
|
+
if (current.depth > maxDepth)
|
|
30
|
+
return `JSON nesting exceeds ${maxDepth} containers`;
|
|
31
|
+
for (const member of Object.values(current.value)) {
|
|
32
|
+
pending.push({ value: member, depth: current.depth + 1 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Freezes validated acyclic catalog data before it is exposed to consumers.
|
|
39
|
+
* The private dispatcher and public catalog must retain the same contract.
|
|
40
|
+
*
|
|
41
|
+
* @category utils
|
|
42
|
+
* @since 1.0.0-rc.0
|
|
43
|
+
*/
|
|
44
|
+
export const freezeParsed = (value) => {
|
|
45
|
+
const pending = [value];
|
|
46
|
+
while (pending.length > 0) {
|
|
47
|
+
const current = pending.pop();
|
|
48
|
+
if (typeof current !== "object" || current === null)
|
|
49
|
+
continue;
|
|
50
|
+
Object.freeze(current);
|
|
51
|
+
for (const member of Object.values(current))
|
|
52
|
+
pending.push(member);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=JsonLimits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonLimits.js","sourceRoot":"","sources":["../../../src/internal/JsonLimits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAc,EAAsB,EAAE;IAChE,MAAM,OAAO,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IACrC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAG,CAAA;QAC9B,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzE,OAAO,2CAA2C,CAAA;QACpD,CAAC;QACD,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI;YAAE,SAAQ;QACzE,IAAI,OAAO,CAAC,KAAK,GAAG,QAAQ;YAAE,OAAO,wBAAwB,QAAQ,aAAa,CAAA;QAClF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAQ,EAAE;IACnD,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAA;IACvB,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;QAC7B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAAE,SAAQ;QAC7D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACtB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACnE,CAAC;AACH,CAAC,CAAA","sourcesContent":["/**\n * Resource bounds checked before recursive JSON consumers see server data.\n *\n * @since 1.0.0-rc.0\n */\n\n/**\n * Maximum nested containers, counting the wire envelope as the first one.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const maxDepth = 128\n\n/**\n * Checks a JSON.parse result iteratively. This is not for arbitrary JS objects\n * with executable accessors; outbound arguments use guarded descriptors.\n *\n * @category validation\n * @since 1.0.0-rc.0\n */\nexport const checkParsed = (value: unknown): string | undefined => {\n const pending = [{ value, depth: 1 }]\n while (pending.length > 0) {\n const current = pending.pop()!\n if (typeof current.value === \"number\" && !Number.isFinite(current.value)) {\n return \"a JSON number is outside the finite range\"\n }\n if (typeof current.value !== \"object\" || current.value === null) continue\n if (current.depth > maxDepth) return `JSON nesting exceeds ${maxDepth} containers`\n for (const member of Object.values(current.value)) {\n pending.push({ value: member, depth: current.depth + 1 })\n }\n }\n return undefined\n}\n\n/**\n * Freezes validated acyclic catalog data before it is exposed to consumers.\n * The private dispatcher and public catalog must retain the same contract.\n *\n * @category utils\n * @since 1.0.0-rc.0\n */\nexport const freezeParsed = (value: unknown): void => {\n const pending = [value]\n while (pending.length > 0) {\n const current = pending.pop()\n if (typeof current !== \"object\" || current === null) continue\n Object.freeze(current)\n for (const member of Object.values(current)) pending.push(member)\n }\n}\n"]}
|