@smthrs/mcp 0.0.0-stage → 1.0.0-rc.3
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 +112 -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 +112 -0
- package/dist/esm/McpFlows.d.ts.map +1 -0
- package/dist/esm/McpFlows.js +167 -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 +210 -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,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var DiagnosticReporter_exports = {};
|
|
30
|
+
__export(DiagnosticReporter_exports, {
|
|
31
|
+
make: () => make
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(DiagnosticReporter_exports);
|
|
34
|
+
var Redaction = __toESM(require("@smthrs/journal/Redaction"), 1);
|
|
35
|
+
var import_effect = require("effect");
|
|
36
|
+
var Diagnostics = __toESM(require("../Diagnostics.js"), 1);
|
|
37
|
+
const make = (server) => import_effect.Effect.map(
|
|
38
|
+
import_effect.Effect.serviceOption(Diagnostics.Diagnostics),
|
|
39
|
+
(observer) => (source, detail) => {
|
|
40
|
+
if (import_effect.Option.isNone(observer)) return;
|
|
41
|
+
try {
|
|
42
|
+
const redacted = source === "stderr" ? detail : Redaction.redactDiagnostic(detail);
|
|
43
|
+
const text = typeof redacted === "string" ? redacted : JSON.stringify(redacted);
|
|
44
|
+
const bytes = new TextEncoder().encode(text);
|
|
45
|
+
const truncated = bytes.byteLength > 16384;
|
|
46
|
+
observer.value.report({
|
|
47
|
+
server,
|
|
48
|
+
source,
|
|
49
|
+
detail: import_effect.Redacted.make(new TextDecoder().decode(bytes.subarray(0, 16384), { stream: truncated })),
|
|
50
|
+
truncated
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
//# sourceMappingURL=DiagnosticReporter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/DiagnosticReporter.ts"],
|
|
4
|
+
"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"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,gBAA2B;AAC3B,oBAAyC;AACzC,kBAA6B;AAQtB,MAAM,OAAO,CAAC,WACnB,qBAAO;AAAA,EACL,qBAAO,cAAc,YAAY,WAAW;AAAA,EAC5C,CAAC,aAAa,CAAC,QAAqC,WAA0B;AAC5E,QAAI,qBAAO,OAAO,QAAQ,EAAG;AAC7B,QAAI;AAQF,YAAM,WAAW,WAAW,WAAW,SAAS,UAAU,iBAAiB,MAAM;AACjF,YAAM,OAAO,OAAO,aAAa,WAAW,WAAW,KAAK,UAAU,QAAQ;AAC9E,YAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,IAAI;AAC3C,YAAM,YAAY,MAAM,aAAa;AACrC,eAAS,MAAM,OAAO;AAAA,QACpB;AAAA,QACA;AAAA,QACA,QAAQ,uBAAS,KAAK,IAAI,YAAY,EAAE,OAAO,MAAM,SAAS,GAAG,KAAM,GAAG,EAAE,QAAQ,UAAU,CAAC,CAAC;AAAA,QAChG;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAGR;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var HttpTransport_exports = {};
|
|
30
|
+
__export(HttpTransport_exports, {
|
|
31
|
+
connect: () => connect
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(HttpTransport_exports);
|
|
34
|
+
var import_Record = require("@smthrs/canonical/Record");
|
|
35
|
+
var KernelHttpClient = __toESM(require("@smthrs/kernel/HttpClient"), 1);
|
|
36
|
+
var import_effect = require("effect");
|
|
37
|
+
var HttpClient = __toESM(require("effect/unstable/http/HttpClient"), 1);
|
|
38
|
+
var HttpClientRequest = __toESM(require("effect/unstable/http/HttpClientRequest"), 1);
|
|
39
|
+
var DiagnosticReporter = __toESM(require("./DiagnosticReporter.js"), 1);
|
|
40
|
+
var JsonLimits = __toESM(require("./JsonLimits.js"), 1);
|
|
41
|
+
var Limits = __toESM(require("./Limits.js"), 1);
|
|
42
|
+
var Rpc = __toESM(require("./Rpc.js"), 1);
|
|
43
|
+
var Transport = __toESM(require("./Transport.js"), 1);
|
|
44
|
+
const sessionCloseMs = 1e3;
|
|
45
|
+
const validSessionId = /^[\x21-\x7e]+$/;
|
|
46
|
+
const endpointOf = (url) => {
|
|
47
|
+
if (!URL.canParse(url)) return void 0;
|
|
48
|
+
const parsed = new URL(url);
|
|
49
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
50
|
+
if (parsed.username !== "" || parsed.password !== "") return void 0;
|
|
51
|
+
return parsed;
|
|
52
|
+
};
|
|
53
|
+
const utf8 = new TextEncoder();
|
|
54
|
+
const mediaType = (header) => (header ?? "").split(";")[0].trim().toLowerCase();
|
|
55
|
+
const connect = (options) => import_effect.Effect.gen(function* () {
|
|
56
|
+
const server = options.server;
|
|
57
|
+
const diagnostic = yield* DiagnosticReporter.make(server);
|
|
58
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? Transport.defaultRequestTimeoutMs;
|
|
59
|
+
const maxFrameBytes = options.maxFrameBytes ?? Transport.defaultMaxFrameBytes;
|
|
60
|
+
const maxOutboundFrameBytes = options.maxOutboundFrameBytes ?? Transport.defaultMaxOutboundFrameBytes;
|
|
61
|
+
yield* Limits.checkPositiveIntegers(server, [
|
|
62
|
+
["requestTimeoutMs", requestTimeoutMs],
|
|
63
|
+
["maxFrameBytes", maxFrameBytes],
|
|
64
|
+
["maxOutboundFrameBytes", maxOutboundFrameBytes]
|
|
65
|
+
]);
|
|
66
|
+
const endpoint = endpointOf(options.url);
|
|
67
|
+
if (endpoint === void 0) {
|
|
68
|
+
return yield* import_effect.Effect.fail(Limits.protocolError(
|
|
69
|
+
server,
|
|
70
|
+
`MCP server "${server}" url must be an absolute http or https URL without credentials`
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
const client = HttpClient.withScope(yield* HttpClient.HttpClient);
|
|
74
|
+
const scope = yield* import_effect.Effect.scope;
|
|
75
|
+
const closing = yield* import_effect.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 import_effect.Option.isSome(KernelHttpClient.fromHttpClientError(error)) ? Transport.closed(server, "is not reachable: egress to its URL is not granted") : Transport.closed(server, "is not reachable; transport details withheld");
|
|
83
|
+
};
|
|
84
|
+
const frameOf = (method, message) => {
|
|
85
|
+
const frame = Rpc.encode(message).subarray(0, -1);
|
|
86
|
+
return frame.byteLength <= maxOutboundFrameBytes ? import_effect.Effect.succeed(frame) : import_effect.Effect.fail(Limits.protocolError(
|
|
87
|
+
server,
|
|
88
|
+
`MCP server "${server}" tried to send a ${method} frame larger than ${maxOutboundFrameBytes} bytes`
|
|
89
|
+
));
|
|
90
|
+
};
|
|
91
|
+
const withSession = (request2) => {
|
|
92
|
+
let next = request2;
|
|
93
|
+
if (sessionId !== void 0) next = HttpClientRequest.setHeader(next, "mcp-session-id", sessionId);
|
|
94
|
+
if (protocolVersion !== void 0) {
|
|
95
|
+
next = HttpClientRequest.setHeader(next, "mcp-protocol-version", protocolVersion);
|
|
96
|
+
}
|
|
97
|
+
return next;
|
|
98
|
+
};
|
|
99
|
+
const readBody = (stream) => stream.pipe(
|
|
100
|
+
import_effect.Stream.mapError(unreachable),
|
|
101
|
+
import_effect.Stream.runFoldEffect(
|
|
102
|
+
() => ({ chunks: [], bytes: 0 }),
|
|
103
|
+
(body, chunk) => {
|
|
104
|
+
body.bytes += chunk.byteLength;
|
|
105
|
+
if (body.bytes > maxFrameBytes) {
|
|
106
|
+
return import_effect.Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`));
|
|
107
|
+
}
|
|
108
|
+
body.chunks.push(chunk);
|
|
109
|
+
return import_effect.Effect.succeed(body);
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
import_effect.Effect.map((body) => {
|
|
113
|
+
const joined = new Uint8Array(body.bytes);
|
|
114
|
+
let offset = 0;
|
|
115
|
+
for (const chunk of body.chunks) {
|
|
116
|
+
joined.set(chunk, offset);
|
|
117
|
+
offset += chunk.byteLength;
|
|
118
|
+
}
|
|
119
|
+
return new TextDecoder().decode(joined);
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
const authorize = (request2) => options.authProvider === void 0 ? import_effect.Effect.succeed(request2) : import_effect.Effect.map(options.authProvider.token, (token) => HttpClientRequest.bearerToken(request2, token));
|
|
123
|
+
const post = (method, message, dispatched) => import_effect.Effect.gen(function* () {
|
|
124
|
+
if (!open) return yield* import_effect.Effect.fail(Transport.closed(server, "connection scope closed"));
|
|
125
|
+
const body = yield* frameOf(method, message);
|
|
126
|
+
const request2 = yield* authorize(withSession(
|
|
127
|
+
HttpClientRequest.post(endpoint).pipe(
|
|
128
|
+
HttpClientRequest.setHeader("accept", "application/json, text/event-stream"),
|
|
129
|
+
HttpClientRequest.bodyUint8Array(body, "application/json")
|
|
130
|
+
)
|
|
131
|
+
));
|
|
132
|
+
const sentSession = sessionId !== void 0;
|
|
133
|
+
if (dispatched !== void 0) dispatched.value = true;
|
|
134
|
+
const response = yield* import_effect.Effect.mapError(client.execute(request2), unreachable);
|
|
135
|
+
if (response.status === 404 && sentSession) {
|
|
136
|
+
return yield* import_effect.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* import_effect.Effect.fail(Limits.protocolError(
|
|
141
|
+
server,
|
|
142
|
+
`MCP server "${server}" answered ${method} with HTTP ${response.status}`
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
return response;
|
|
146
|
+
});
|
|
147
|
+
const deliver = (method, message) => import_effect.Effect.scoped(import_effect.Effect.flatMap(post(method, message), (response) => import_effect.Effect.asVoid(readBody(response.stream))));
|
|
148
|
+
const untilClosed = (effect) => import_effect.Effect.raceFirst(
|
|
149
|
+
effect,
|
|
150
|
+
import_effect.Effect.andThen(import_effect.Deferred.await(closing), import_effect.Effect.fail(Transport.closed(server, "connection scope closed")))
|
|
151
|
+
);
|
|
152
|
+
const receive = (method, id, text) => import_effect.Effect.gen(function* () {
|
|
153
|
+
const message = Rpc.parse(text);
|
|
154
|
+
if (message === void 0) {
|
|
155
|
+
return yield* import_effect.Effect.fail(
|
|
156
|
+
Limits.protocolError(server, `MCP server "${server}" sent a message that is not JSON-RPC`)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const jsonIssue = JsonLimits.checkParsed(message);
|
|
160
|
+
if (jsonIssue !== void 0) {
|
|
161
|
+
return yield* import_effect.Effect.fail(
|
|
162
|
+
Limits.protocolError(server, `MCP server "${server}" sent invalid JSON: ${jsonIssue}`)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const reply = Rpc.classify(message);
|
|
166
|
+
switch (reply._tag) {
|
|
167
|
+
case "Notification":
|
|
168
|
+
return import_effect.Option.none();
|
|
169
|
+
case "Request": {
|
|
170
|
+
yield* deliver(
|
|
171
|
+
"server-response",
|
|
172
|
+
reply.method === "ping" ? { jsonrpc: "2.0", id: reply.id, result: {} } : { jsonrpc: "2.0", id: reply.id, error: { code: -32601, message: "Method not found" } }
|
|
173
|
+
);
|
|
174
|
+
return import_effect.Option.none();
|
|
175
|
+
}
|
|
176
|
+
case "Malformed":
|
|
177
|
+
return yield* import_effect.Effect.fail(Limits.protocolError(
|
|
178
|
+
server,
|
|
179
|
+
`MCP server "${server}" sent a malformed JSON-RPC reply: ${reply.reason}`
|
|
180
|
+
));
|
|
181
|
+
case "UncorrelatedError":
|
|
182
|
+
diagnostic("remote-error", { code: reply.code, message: reply.message, data: reply.data });
|
|
183
|
+
return import_effect.Option.none();
|
|
184
|
+
}
|
|
185
|
+
if (reply.id !== id) {
|
|
186
|
+
return yield* import_effect.Effect.fail(Limits.protocolError(
|
|
187
|
+
server,
|
|
188
|
+
`MCP server "${server}" answered ${method} with a reply to another request`
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
if (reply._tag === "Error") {
|
|
192
|
+
diagnostic("remote-error", { code: reply.code, message: reply.message, data: reply.data });
|
|
193
|
+
return yield* import_effect.Effect.fail(Transport.replyError(server, method, reply));
|
|
194
|
+
}
|
|
195
|
+
return import_effect.Option.some(reply.result);
|
|
196
|
+
});
|
|
197
|
+
const events = (stream) => Transport.lines(server, maxFrameBytes, import_effect.Stream.mapError(stream, unreachable), { crTerminates: true }).pipe(
|
|
198
|
+
import_effect.Stream.mapAccumEffect(
|
|
199
|
+
() => ({ data: void 0, bytes: 0 }),
|
|
200
|
+
(event, line) => {
|
|
201
|
+
if (line === "") {
|
|
202
|
+
const data = event.data;
|
|
203
|
+
event.data = void 0;
|
|
204
|
+
event.bytes = 0;
|
|
205
|
+
return import_effect.Effect.succeed([event, data === void 0 ? [] : [data]]);
|
|
206
|
+
}
|
|
207
|
+
if (!line.startsWith("data:")) return import_effect.Effect.succeed([event, []]);
|
|
208
|
+
const value = line.slice(line.startsWith("data: ") ? 6 : 5);
|
|
209
|
+
event.bytes += utf8.encode(value).byteLength + (event.data === void 0 ? 0 : 1);
|
|
210
|
+
event.data = event.data === void 0 ? value : `${event.data}
|
|
211
|
+
${value}`;
|
|
212
|
+
return event.bytes > maxFrameBytes ? import_effect.Effect.fail(Limits.protocolError(server, `MCP frame exceeded ${maxFrameBytes} bytes`)) : import_effect.Effect.succeed([event, []]);
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
);
|
|
216
|
+
const exchange = (method, params, id, dispatched) => import_effect.Effect.scoped(import_effect.Effect.gen(function* () {
|
|
217
|
+
const response = yield* post(method, { jsonrpc: "2.0", id, method, params }, dispatched);
|
|
218
|
+
if (method === "initialize") {
|
|
219
|
+
const header = response.headers["mcp-session-id"];
|
|
220
|
+
if (header !== void 0 && !validSessionId.test(header)) {
|
|
221
|
+
return yield* import_effect.Effect.fail(Limits.protocolError(server, `MCP server "${server}" sent an invalid session id`));
|
|
222
|
+
}
|
|
223
|
+
sessionId = header;
|
|
224
|
+
}
|
|
225
|
+
const type = mediaType(response.headers["content-type"]);
|
|
226
|
+
let result;
|
|
227
|
+
if (type === "application/json") {
|
|
228
|
+
result = yield* import_effect.Effect.flatMap(readBody(response.stream), (text) => receive(method, id, text));
|
|
229
|
+
} else if (type === "text/event-stream") {
|
|
230
|
+
result = import_effect.Option.flatten(
|
|
231
|
+
yield* events(response.stream).pipe(
|
|
232
|
+
import_effect.Stream.mapEffect((data) => receive(method, id, data)),
|
|
233
|
+
import_effect.Stream.filter(import_effect.Option.isSome),
|
|
234
|
+
import_effect.Stream.runHead
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
return yield* import_effect.Effect.fail(Limits.protocolError(
|
|
239
|
+
server,
|
|
240
|
+
`MCP server "${server}" answered ${method} with an unsupported content type`
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
if (import_effect.Option.isNone(result)) {
|
|
244
|
+
return yield* import_effect.Effect.fail(Transport.closed(server, `closed its response before answering ${method}`));
|
|
245
|
+
}
|
|
246
|
+
if (method === "initialize" && (0, import_Record.isRecord)(result.value) && typeof result.value.protocolVersion === "string") {
|
|
247
|
+
protocolVersion = result.value.protocolVersion;
|
|
248
|
+
}
|
|
249
|
+
return result.value;
|
|
250
|
+
}));
|
|
251
|
+
const notify = (method, params, timeoutMs = requestTimeoutMs) => Limits.isPositiveInteger(timeoutMs) ? untilClosed(deliver(method, { jsonrpc: "2.0", method, params })).pipe(
|
|
252
|
+
import_effect.Effect.timeoutOrElse({
|
|
253
|
+
duration: timeoutMs,
|
|
254
|
+
orElse: () => import_effect.Effect.fail(Transport.timeout(server, method, timeoutMs))
|
|
255
|
+
})
|
|
256
|
+
) : import_effect.Effect.fail(Limits.protocolError(server, "MCP notification timeout must be a positive integer"));
|
|
257
|
+
const request = (method, params, timeoutMs = requestTimeoutMs) => import_effect.Effect.suspend(() => {
|
|
258
|
+
if (!Limits.isPositiveInteger(timeoutMs)) {
|
|
259
|
+
return import_effect.Effect.fail(Limits.protocolError(server, "MCP request timeout must be a positive integer"));
|
|
260
|
+
}
|
|
261
|
+
const id = ++nextId;
|
|
262
|
+
const dispatched = { value: false };
|
|
263
|
+
return exchange(method, params, id, dispatched).pipe(
|
|
264
|
+
// Closing the HTTP response is not a cancellation in MCP; tell the
|
|
265
|
+
// server explicitly, without delaying the deadline being reported.
|
|
266
|
+
import_effect.Effect.onInterrupt(
|
|
267
|
+
() => dispatched.value && open && method !== "initialize" ? import_effect.Effect.asVoid(import_effect.Effect.forkIn(
|
|
268
|
+
import_effect.Effect.ignore(notify("notifications/cancelled", {
|
|
269
|
+
requestId: id,
|
|
270
|
+
reason: Transport.cancellationReason
|
|
271
|
+
})),
|
|
272
|
+
scope
|
|
273
|
+
)) : import_effect.Effect.void
|
|
274
|
+
),
|
|
275
|
+
untilClosed,
|
|
276
|
+
import_effect.Effect.timeoutOrElse({
|
|
277
|
+
duration: timeoutMs,
|
|
278
|
+
orElse: () => import_effect.Effect.fail(Transport.timeout(server, method, timeoutMs))
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
yield* import_effect.Effect.addFinalizer(
|
|
283
|
+
() => import_effect.Effect.suspend(() => {
|
|
284
|
+
open = false;
|
|
285
|
+
const ended = import_effect.Deferred.succeed(closing, void 0);
|
|
286
|
+
if (sessionId === void 0) return ended;
|
|
287
|
+
return import_effect.Effect.andThen(
|
|
288
|
+
ended,
|
|
289
|
+
import_effect.Effect.scoped(import_effect.Effect.flatMap(authorize(withSession(HttpClientRequest.delete(endpoint))), client.execute)).pipe(
|
|
290
|
+
import_effect.Effect.timeout(sessionCloseMs),
|
|
291
|
+
import_effect.Effect.ignore
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
return { request, notify };
|
|
297
|
+
});
|
|
298
|
+
//# sourceMappingURL=HttpTransport.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/HttpTransport.ts"],
|
|
4
|
+
"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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["request"]
|
|
7
|
+
}
|
|
@@ -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,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var JsonLimits_exports = {};
|
|
20
|
+
__export(JsonLimits_exports, {
|
|
21
|
+
checkParsed: () => checkParsed,
|
|
22
|
+
freezeParsed: () => freezeParsed,
|
|
23
|
+
maxDepth: () => maxDepth
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(JsonLimits_exports);
|
|
26
|
+
const maxDepth = 128;
|
|
27
|
+
const checkParsed = (value) => {
|
|
28
|
+
const pending = [{ value, depth: 1 }];
|
|
29
|
+
while (pending.length > 0) {
|
|
30
|
+
const current = pending.pop();
|
|
31
|
+
if (typeof current.value === "number" && !Number.isFinite(current.value)) {
|
|
32
|
+
return "a JSON number is outside the finite range";
|
|
33
|
+
}
|
|
34
|
+
if (typeof current.value !== "object" || current.value === null) continue;
|
|
35
|
+
if (current.depth > maxDepth) return `JSON nesting exceeds ${maxDepth} containers`;
|
|
36
|
+
for (const member of Object.values(current.value)) {
|
|
37
|
+
pending.push({ value: member, depth: current.depth + 1 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return void 0;
|
|
41
|
+
};
|
|
42
|
+
const freezeParsed = (value) => {
|
|
43
|
+
const pending = [value];
|
|
44
|
+
while (pending.length > 0) {
|
|
45
|
+
const current = pending.pop();
|
|
46
|
+
if (typeof current !== "object" || current === null) continue;
|
|
47
|
+
Object.freeze(current);
|
|
48
|
+
for (const member of Object.values(current)) pending.push(member);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=JsonLimits.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/internal/JsonLimits.ts"],
|
|
4
|
+
"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"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,MAAM,WAAW;AASjB,MAAM,cAAc,CAAC,UAAuC;AACjE,QAAM,UAAU,CAAC,EAAE,OAAO,OAAO,EAAE,CAAC;AACpC,SAAO,QAAQ,SAAS,GAAG;AACzB,UAAM,UAAU,QAAQ,IAAI;AAC5B,QAAI,OAAO,QAAQ,UAAU,YAAY,CAAC,OAAO,SAAS,QAAQ,KAAK,GAAG;AACxE,aAAO;AAAA,IACT;AACA,QAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,KAAM;AACjE,QAAI,QAAQ,QAAQ,SAAU,QAAO,wBAAwB,QAAQ;AACrE,eAAW,UAAU,OAAO,OAAO,QAAQ,KAAK,GAAG;AACjD,cAAQ,KAAK,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAC1D;AAAA,EACF;AACA,SAAO;AACT;AASO,MAAM,eAAe,CAAC,UAAyB;AACpD,QAAM,UAAU,CAAC,KAAK;AACtB,SAAO,QAAQ,SAAS,GAAG;AACzB,UAAM,UAAU,QAAQ,IAAI;AAC5B,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM;AACrD,WAAO,OAAO,OAAO;AACrB,eAAW,UAAU,OAAO,OAAO,OAAO,EAAG,SAAQ,KAAK,MAAM;AAAA,EAClE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Positive-integer option bounds shared by the transport and the client.
|
|
3
|
+
*
|
|
4
|
+
* Both `connect` functions resolve their defaults and then reject the first
|
|
5
|
+
* option that is not a positive safe integer, before any process is spawned.
|
|
6
|
+
* The check and its error prose live here so the two option lists cannot
|
|
7
|
+
* drift in wording or in semantics.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0-rc.0
|
|
10
|
+
*/
|
|
11
|
+
import { Effect } from "effect";
|
|
12
|
+
import { McpError } from "../McpError.ts";
|
|
13
|
+
/**
|
|
14
|
+
* Builds the `protocol_error` failure both modules report for malformed
|
|
15
|
+
* options and frames.
|
|
16
|
+
*
|
|
17
|
+
* @category errors
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
*/
|
|
20
|
+
export declare const protocolError: (server: string, message: string) => McpError;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a value is a positive safe integer.
|
|
23
|
+
*
|
|
24
|
+
* @category validation
|
|
25
|
+
* @since 1.0.0-rc.0
|
|
26
|
+
*/
|
|
27
|
+
export declare const isPositiveInteger: (value: number) => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Fails with `protocol_error` naming the first option whose resolved value is
|
|
30
|
+
* not a positive safe integer. Entries are checked in the order given.
|
|
31
|
+
*
|
|
32
|
+
* @category validation
|
|
33
|
+
* @since 1.0.0-rc.0
|
|
34
|
+
*/
|
|
35
|
+
export declare const checkPositiveIntegers: (server: string, entries: ReadonlyArray<readonly [name: string, value: number]>) => Effect.Effect<void, McpError>;
|
|
36
|
+
//# sourceMappingURL=Limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Limits.d.ts","sourceRoot":"","sources":["../../../src/internal/Limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,QACL,CAAA;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,OAAmD,CAAA;AAErG;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EACd,SAAS,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,KAC7D,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAK9B,CAAA"}
|