@effect-uai/mcp 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Client.d.mts +2 -2
- package/dist/Client.mjs.map +1 -1
- package/dist/internal/httpTransport.d.mts +1 -1
- package/dist/internal/httpTransport.mjs +1 -1
- package/dist/internal/httpTransport.mjs.map +1 -1
- package/dist/internal/protocols/2026-07-28.d.mts.map +1 -1
- package/dist/internal/protocols/2026-07-28.mjs +3 -2
- package/dist/internal/protocols/2026-07-28.mjs.map +1 -1
- package/dist/internal/stdioTransport.d.mts +1 -1
- package/dist/internal/stdioTransport.mjs +1 -1
- package/dist/internal/stdioTransport.mjs.map +1 -1
- package/package.json +6 -6
- package/src/Client.ts +2 -2
- package/src/internal/httpTransport.ts +1 -1
- package/src/internal/protocols/2026-07-28.ts +3 -2
- package/src/internal/stdioTransport.ts +1 -1
package/dist/Client.d.mts
CHANGED
|
@@ -2,8 +2,8 @@ import { i as McpError } from "./McpError-DWg9BlW5.mjs";
|
|
|
2
2
|
import { n as TokenSource, t as Auth } from "./auth-ZDvsxpCo.mjs";
|
|
3
3
|
import { C as ProtocolVersion, D as ToolInfo, T as ServerInfo, o as CallToolResult } from "./schema-D2oUNuo5.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Scope } from "effect";
|
|
5
|
-
import { HttpClient } from "effect/
|
|
6
|
-
import { ChildProcessSpawner } from "effect/
|
|
5
|
+
import { HttpClient } from "effect/http";
|
|
6
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
7
7
|
declare namespace Client_d_exports {
|
|
8
8
|
export { Auth, Mcp, McpClient, McpClientConfig, McpRequirements, McpServerInfo, ProtocolPin, TokenSource, connect, layer };
|
|
9
9
|
}
|
package/dist/Client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Client.mjs","names":["Stateless.probe","Handshake.probe","HttpTransport.make","StdioTransport.make"],"sources":["../src/Client.ts"],"sourcesContent":["/**\n * `connect` opens a scoped MCP client: transport -> rpc core -> protocol probe.\n * The protocol era is a property of the server, so it is negotiated once here\n * and fixed for the connection's lifetime. The client surface is era-uniform.\n */\nimport { Context, Effect, Layer, Option, Ref, type Scope } from \"effect\"\nimport type { HttpClient } from \"effect/
|
|
1
|
+
{"version":3,"file":"Client.mjs","names":["Stateless.probe","Handshake.probe","HttpTransport.make","StdioTransport.make"],"sources":["../src/Client.ts"],"sourcesContent":["/**\n * `connect` opens a scoped MCP client: transport -> rpc core -> protocol probe.\n * The protocol era is a property of the server, so it is negotiated once here\n * and fixed for the connection's lifetime. The client surface is era-uniform.\n */\nimport { Context, Effect, Layer, Option, Ref, type Scope } from \"effect\"\nimport type { HttpClient } from \"effect/http\"\nimport type { ChildProcessSpawner } from \"effect/process\"\nimport { McpConnectFailed, type McpError, McpProtocolError } from \"./McpError.js\"\nimport { Auth, type TokenSource } from \"./internal/auth.js\"\nimport * as Handshake from \"./internal/protocols/2025-06-18.js\"\nimport * as Stateless from \"./internal/protocols/2026-07-28.js\"\nimport type { Protocol } from \"./internal/protocol.js\"\nimport { type McpConnection, open } from \"./internal/rpc.js\"\nimport {\n type CallToolResult,\n decodeCallToolResult,\n decodeListToolsResult,\n LATEST_VERSION,\n type McpMethod,\n type ProtocolVersion,\n type ServerInfo,\n type ToolInfo,\n} from \"./internal/schema.js\"\nimport * as HttpTransport from \"./internal/httpTransport.js\"\nimport * as StdioTransport from \"./internal/stdioTransport.js\"\n\nexport { Auth, type TokenSource }\n\n/** `auto` runs the probe; a pinned version skips detection. */\nexport type ProtocolPin = \"auto\" | ProtocolVersion\n\nexport type McpClientConfig =\n | {\n readonly transport: \"http\"\n readonly url: string\n /** Static, non-auth headers sent on every request. */\n readonly headers?: Record<string, string>\n /** Omit for a public server. See `Auth` for the three shapes. */\n readonly auth?: Auth\n readonly protocol?: ProtocolPin\n }\n | {\n readonly transport: \"stdio\"\n readonly command: string\n readonly args?: ReadonlyArray<string>\n readonly env?: Record<string, string>\n readonly cwd?: string\n readonly protocol?: ProtocolPin\n }\n\n/**\n * Services a client needs, by transport. Both are listed because the config is\n * a runtime value: an app that only uses one still provides the other's layer,\n * which is cheap (`FetchHttpClient.layer` / `NodeServices.layer`).\n */\nexport type McpRequirements = HttpClient.HttpClient | ChildProcessSpawner.ChildProcessSpawner\n\nexport type McpServerInfo = ServerInfo & {\n /** The negotiated protocol version: the only era signal callers need. */\n readonly protocolVersion: ProtocolVersion\n}\n\nexport type McpClient = {\n readonly listTools: Effect.Effect<ReadonlyArray<ToolInfo>, McpError>\n readonly callTool: (name: string, args: unknown) => Effect.Effect<CallToolResult, McpError>\n readonly serverInfo: McpServerInfo\n}\n\nconst undecodable = (method: McpMethod, detail: string, raw: unknown): McpError =>\n new McpProtocolError({ method, reason: `undecodable ${method} result${detail}`, raw })\n\nconst makeClient = (connection: McpConnection, protocol: Protocol): McpClient => {\n const request = (method: McpMethod, params: unknown) =>\n connection.request(method, protocol.envelope(method, params), protocol.meta(method, params))\n\n return {\n listTools: request(\"tools/list\", {}).pipe(\n Effect.flatMap((raw) =>\n decodeListToolsResult(raw).pipe(\n Effect.mapError(() => undecodable(\"tools/list\", \"\", raw)),\n Effect.map((result) => result.tools),\n ),\n ),\n ),\n\n callTool: (name, args) =>\n request(\"tools/call\", { name, arguments: args ?? {} }).pipe(\n Effect.flatMap((raw) =>\n decodeCallToolResult(raw).pipe(\n Effect.mapError(() => undecodable(\"tools/call\", ` for ${name}`, raw)),\n ),\n ),\n ),\n\n serverInfo: { ...protocol.serverInfo, protocolVersion: protocol.version },\n }\n}\n\n/**\n * Try the stateless protocol first, then the handshake era. The stateless\n * probe yields `None` only when the server proves it is not stateless (an\n * error that is not one of the recognized modern codes), which is exactly the\n * spec's era discriminator. A pinned `protocol` skips detection.\n *\n * The result is cached for the connection's lifetime by the caller: era is a\n * property of the server, not of a request.\n */\nconst negotiate = (\n connection: McpConnection,\n pin: ProtocolPin,\n): Effect.Effect<Protocol, McpError, Scope.Scope> => {\n if (pin === LATEST_VERSION) return required(Stateless.probe(connection))\n if (pin !== \"auto\") return required(Handshake.probe(connection))\n return Stateless.probe(connection).pipe(\n Effect.flatMap(\n Option.match({\n onNone: () => required(Handshake.probe(connection)),\n onSome: Effect.succeed<Protocol>,\n }),\n ),\n )\n}\n\nconst required = (\n probe: Effect.Effect<Option.Option<Protocol>, McpError, Scope.Scope>,\n): Effect.Effect<Protocol, McpError, Scope.Scope> =>\n probe.pipe(\n Effect.flatMap(\n Option.match({\n onNone: () =>\n Effect.fail(\n new McpConnectFailed({ reason: \"the server does not speak a supported MCP protocol\" }),\n ),\n onSome: Effect.succeed<Protocol>,\n }),\n ),\n )\n\n/**\n * Open a scoped client. The connection lives exactly as long as the enclosing\n * scope, so `Effect.scoped`, a `Layer`, or `Stream.scoped` all give correct\n * teardown with no explicit close.\n */\nexport const connect = (\n config: McpClientConfig,\n): Effect.Effect<McpClient, McpError, Scope.Scope | McpRequirements> =>\n Effect.gen(function* () {\n const transport = yield* config.transport === \"http\"\n ? HttpTransport.make(config)\n : StdioTransport.make(config)\n\n // Inbound handling belongs to the protocol, which is not chosen until the\n // probe completes, so the reader reads it from a Ref that starts empty.\n // Frames arriving before then can only be legacy noise; ignore them.\n const protocolRef = yield* Ref.make(Option.none<Protocol>())\n const connection = yield* open(transport, (inbound) =>\n Ref.get(protocolRef).pipe(\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.void,\n onSome: (protocol) => protocol.onInbound(inbound),\n }),\n ),\n ),\n )\n\n const protocol = yield* negotiate(connection, config.protocol ?? \"auto\")\n yield* Ref.set(protocolRef, Option.some(protocol))\n\n return makeClient(connection, protocol)\n })\n\nexport class Mcp extends Context.Service<Mcp, McpClient>()(\n \"@betalyra/effect-uai/providers/mcp/Mcp\",\n) {}\n\n/** DI convenience for the single-server app: connects when the layer builds. */\nexport const layer = (config: McpClientConfig): Layer.Layer<Mcp, McpError, McpRequirements> =>\n Layer.effect(Mcp, connect(config))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqEA,MAAM,eAAe,QAAmB,QAAgB,QACtD,IAAI,iBAAiB;CAAE;CAAQ,QAAQ,eAAe,OAAO,SAAS;CAAU;AAAI,CAAC;AAEvF,MAAM,cAAc,YAA2B,aAAkC;CAC/E,MAAM,WAAW,QAAmB,WAClC,WAAW,QAAQ,QAAQ,SAAS,SAAS,QAAQ,MAAM,GAAG,SAAS,KAAK,QAAQ,MAAM,CAAC;CAE7F,OAAO;EACL,WAAW,QAAQ,cAAc,CAAC,CAAC,CAAC,CAAC,KACnC,OAAO,SAAS,QACd,sBAAsB,GAAG,CAAC,CAAC,KACzB,OAAO,eAAe,YAAY,cAAc,IAAI,GAAG,CAAC,GACxD,OAAO,KAAK,WAAW,OAAO,KAAK,CACrC,CACF,CACF;EAEA,WAAW,MAAM,SACf,QAAQ,cAAc;GAAE;GAAM,WAAW,QAAQ,CAAC;EAAE,CAAC,CAAC,CAAC,KACrD,OAAO,SAAS,QACd,qBAAqB,GAAG,CAAC,CAAC,KACxB,OAAO,eAAe,YAAY,cAAc,QAAQ,QAAQ,GAAG,CAAC,CACtE,CACF,CACF;EAEF,YAAY;GAAE,GAAG,SAAS;GAAY,iBAAiB,SAAS;EAAQ;CAC1E;AACF;;;;;;;;;;AAWA,MAAM,aACJ,YACA,QACmD;CACnD,IAAI,QAAA,cAAwB,OAAO,SAASA,QAAgB,UAAU,CAAC;CACvE,IAAI,QAAQ,QAAQ,OAAO,SAASC,MAAgB,UAAU,CAAC;CAC/D,OAAOD,QAAgB,UAAU,CAAC,CAAC,KACjC,OAAO,QACL,OAAO,MAAM;EACX,cAAc,SAASC,MAAgB,UAAU,CAAC;EAClD,QAAQ,OAAO;CACjB,CAAC,CACH,CACF;AACF;AAEA,MAAM,YACJ,UAEA,MAAM,KACJ,OAAO,QACL,OAAO,MAAM;CACX,cACE,OAAO,KACL,IAAI,iBAAiB,EAAE,QAAQ,qDAAqD,CAAC,CACvF;CACF,QAAQ,OAAO;AACjB,CAAC,CACH,CACF;;;;;;AAOF,MAAa,WACX,WAEA,OAAO,IAAI,aAAa;CACtB,MAAM,YAAY,OAAO,OAAO,cAAc,SAC1CC,KAAmB,MAAM,IACzBC,OAAoB,MAAM;CAK9B,MAAM,cAAc,OAAO,IAAI,KAAK,OAAO,KAAe,CAAC;CAC3D,MAAM,aAAa,OAAO,KAAK,YAAY,YACzC,IAAI,IAAI,WAAW,CAAC,CAAC,KACnB,OAAO,QACL,OAAO,MAAM;EACX,cAAc,OAAO;EACrB,SAAS,aAAa,SAAS,UAAU,OAAO;CAClD,CAAC,CACH,CACF,CACF;CAEA,MAAM,WAAW,OAAO,UAAU,YAAY,OAAO,YAAY,MAAM;CACvE,OAAO,IAAI,IAAI,aAAa,OAAO,KAAK,QAAQ,CAAC;CAEjD,OAAO,WAAW,YAAY,QAAQ;AACxC,CAAC;AAEH,IAAa,MAAb,cAAyB,QAAQ,QAAwB,CAAC,CACxD,wCACF,CAAC,CAAC,CAAC;;AAGH,MAAa,SAAS,WACpB,MAAM,OAAO,KAAK,QAAQ,MAAM,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { i as McpError } from "../McpError-DWg9BlW5.mjs";
|
|
|
2
2
|
import { t as Auth } from "../auth-ZDvsxpCo.mjs";
|
|
3
3
|
import { i as Transport } from "../rpc-Bq95Lio9.mjs";
|
|
4
4
|
import { Effect, Scope } from "effect";
|
|
5
|
-
import { HttpClient } from "effect/
|
|
5
|
+
import { HttpClient } from "effect/http";
|
|
6
6
|
//#region src/internal/httpTransport.d.ts
|
|
7
7
|
type HttpConfig = {
|
|
8
8
|
readonly url: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpAuthRequired, McpConnectFailed, McpProtocolError, McpTransportClosed } from "../McpError.mjs";
|
|
2
2
|
import { authHeaders } from "./auth.mjs";
|
|
3
3
|
import { Effect, Match, Option, Queue, Ref, Stream } from "effect";
|
|
4
|
-
import { HttpClient, HttpClientRequest } from "effect/
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
5
5
|
import * as SSE from "@effect-uai/core/SSE";
|
|
6
6
|
//#region src/internal/httpTransport.ts
|
|
7
7
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpTransport.mjs","names":[],"sources":["../../src/internal/httpTransport.ts"],"sourcesContent":["/**\n * Streamable HTTP `Transport`. Each client message is its own POST; the server\n * answers with either one JSON object or a request-scoped SSE stream ending in\n * the JSON-RPC response, and a client MUST accept both. Frames from every\n * in-flight POST demux into one `messages` stream, which the rpc core\n * correlates by id.\n *\n * Interrupting a request aborts its POST, which is exactly the spec's\n * cancellation signal (closing the response stream).\n */\nimport { Cause, Effect, Match, Option, Queue, Ref, type Scope, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest, type HttpClientResponse } from \"effect/
|
|
1
|
+
{"version":3,"file":"httpTransport.mjs","names":[],"sources":["../../src/internal/httpTransport.ts"],"sourcesContent":["/**\n * Streamable HTTP `Transport`. Each client message is its own POST; the server\n * answers with either one JSON object or a request-scoped SSE stream ending in\n * the JSON-RPC response, and a client MUST accept both. Frames from every\n * in-flight POST demux into one `messages` stream, which the rpc core\n * correlates by id.\n *\n * Interrupting a request aborts its POST, which is exactly the spec's\n * cancellation signal (closing the response stream).\n */\nimport { Cause, Effect, Match, Option, Queue, Ref, type Scope, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest, type HttpClientResponse } from \"effect/http\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport {\n McpAuthRequired,\n McpConnectFailed,\n type McpError,\n McpProtocolError,\n McpTransportClosed,\n} from \"../McpError.js\"\nimport { type Auth, authHeaders } from \"./auth.js\"\nimport type { SendMeta, Transport } from \"./rpc.js\"\n\nexport type HttpConfig = {\n readonly url: string\n /** Static, non-auth headers applied to every request. */\n readonly headers?: Record<string, string>\n /** Omit for a public server. Resolved per request, so a rotating source works. */\n readonly auth?: Auth\n}\n\n/** RFC 9728: the `resource_metadata` pointer on a 401's `WWW-Authenticate`. */\nconst resourceMetadataUrl = (wwwAuthenticate: string): string | undefined =>\n /resource_metadata=\"([^\"]+)\"/.exec(wwwAuthenticate)?.[1]\n\nconst unauthorized = (response: HttpClientResponse.HttpClientResponse): McpError => {\n const header = response.headers[\"www-authenticate\"]\n const pointer = header === undefined ? undefined : resourceMetadataUrl(header)\n return new McpAuthRequired({\n ...(pointer !== undefined ? { resourceMetadataUrl: pointer } : {}),\n ...(header !== undefined ? { wwwAuthenticate: header } : {}),\n })\n}\n\n/**\n * A non-2xx that is not a 401. The body still matters: a modern server answers\n * `400` for `-32022` / `-32021` / `-32020` and `404` for `-32601`, and era\n * detection reads those codes off the JSON-RPC body. So the body is forwarded\n * into `messages` whenever it parses as JSON, and only a bodyless failure\n * becomes a transport error.\n */\nconst isJsonRpcBody = (body: string): boolean => body.trimStart().startsWith(\"{\")\n\nexport const make = (\n config: HttpConfig,\n): Effect.Effect<Transport, McpError, Scope.Scope | HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const inbox = yield* Queue.make<string, Cause.Done>()\n yield* Effect.addFinalizer(() => Queue.end(inbox))\n\n // The legacy era's `Mcp-Session-Id` is a transport concern, not a protocol\n // one: capture whatever the server mints and echo it from then on, so the\n // protocol implementations stay transport-agnostic. Stateless servers\n // never send one, and this stays empty.\n const session = yield* Ref.make(Option.none<string>())\n\n const post = (frame: string, meta?: SendMeta): Effect.Effect<void, McpError> =>\n Effect.gen(function* () {\n const sessionId = yield* Ref.get(session)\n const auth = yield* authHeaders(Option.fromNullishOr(config.auth))\n const request = HttpClientRequest.post(config.url).pipe(\n HttpClientRequest.setHeaders({\n \"content-type\": \"application/json\",\n // Both response shapes are legal for any request.\n accept: \"application/json, text/event-stream\",\n ...config.headers,\n ...auth,\n ...meta?.headers,\n ...Option.match(sessionId, {\n onNone: () => ({}),\n onSome: (id) => ({ \"Mcp-Session-Id\": id }),\n }),\n }),\n // The content type must be set on the body: it overrides any header.\n HttpClientRequest.bodyText(frame, \"application/json\"),\n )\n const response = yield* client\n .execute(request)\n .pipe(\n Effect.mapError(\n (cause) => new McpConnectFailed({ reason: \"MCP request failed\", raw: cause }),\n ),\n )\n yield* captureSession(response)\n yield* ingest(response)\n })\n\n const captureSession = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void> => {\n const minted = response.headers[\"mcp-session-id\"]\n return minted === undefined ? Effect.void : Ref.set(session, Option.some(minted))\n }\n\n const ingest = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void, McpError> =>\n Match.value(response.status).pipe(\n Match.when(401, () => Effect.fail(unauthorized(response))),\n Match.when(404, () => expiredOrBody(response)),\n // 202 answers a notification and carries no body.\n Match.when(202, () => Effect.void),\n Match.orElse(() => readFrames(response)),\n )\n\n // A 404 against a session we hold means the server dropped it; the caller\n // reconnects rather than retrying into a dead session.\n const expiredOrBody = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void, McpError> =>\n Ref.get(session).pipe(\n Effect.flatMap((held) =>\n Option.isSome(held)\n ? Effect.fail(new McpTransportClosed({ reason: \"the server expired this session\" }))\n : readBody(response),\n ),\n )\n\n const readFrames = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void, McpError> =>\n (response.headers[\"content-type\"] ?? \"\").includes(\"text/event-stream\")\n ? readSse(response)\n : readBody(response)\n\n const readSse = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void, McpError> =>\n response.stream.pipe(\n SSE.fromBytes,\n Stream.runForEach((event) => Queue.offer(inbox, event.data)),\n Effect.mapError(\n (cause) => new McpTransportClosed({ reason: \"SSE stream failed\", raw: cause }),\n ),\n )\n\n const readBody = (\n response: HttpClientResponse.HttpClientResponse,\n ): Effect.Effect<void, McpError> =>\n response.text.pipe(\n Effect.mapError(\n (cause) => new McpTransportClosed({ reason: \"could not read response\", raw: cause }),\n ),\n Effect.flatMap((body) =>\n isJsonRpcBody(body)\n ? Effect.asVoid(Queue.offer(inbox, body))\n : Effect.fail(\n new McpProtocolError({\n code: response.status,\n reason: `HTTP ${response.status} with a non-JSON-RPC body`,\n raw: body.slice(0, 500),\n }),\n ),\n ),\n )\n\n // The POST runs on the caller's fiber so interruption aborts it, which is\n // the spec's cancellation signal.\n return { send: post, messages: Stream.fromQueue(inbox) } satisfies Transport\n })\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"2026-07-28.d.mts","names":[],"sources":["../../../src/internal/protocols/2026-07-28.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"2026-07-28.d.mts","names":[],"sources":["../../../src/internal/protocols/2026-07-28.ts"],"mappings":";;;;;;cA0Da,cAAW;;;;;;cAqCX,OAAO"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { McpUnsupportedProtocol } from "../../McpError.mjs";
|
|
2
2
|
import { CLIENT_INFO, LATEST_VERSION, META_CLIENT_CAPABILITIES, META_CLIENT_INFO, META_PROTOCOL_VERSION, asProtocolVersion, decodeDiscoverResult } from "../schema.mjs";
|
|
3
3
|
import { modernRejection } from "../protocol.mjs";
|
|
4
|
-
import { Effect,
|
|
4
|
+
import { Effect, Option, Predicate, Record } from "effect";
|
|
5
|
+
import { Base64 } from "effect/encoding";
|
|
5
6
|
//#region src/internal/protocols/2026-07-28.ts
|
|
6
7
|
/**
|
|
7
8
|
* Protocol revision 2026-07-28: the stateless era. No handshake and no
|
|
@@ -31,7 +32,7 @@ const HEADER_SAFE = /^[\x21-\x7e]([\x20-\x7e]*[\x21-\x7e])?$/;
|
|
|
31
32
|
* Header values outside the safe set (and any literal that would be mistaken
|
|
32
33
|
* for the marker) ride the spec's Base64 sentinel form.
|
|
33
34
|
*/
|
|
34
|
-
const headerValue = (raw) => HEADER_SAFE.test(raw) && !SENTINEL.test(raw) ? raw : `=?base64?${
|
|
35
|
+
const headerValue = (raw) => HEADER_SAFE.test(raw) && !SENTINEL.test(raw) ? raw : `=?base64?${Base64.encode(raw)}?=`;
|
|
35
36
|
const makeProtocol = (version, serverInfo) => ({
|
|
36
37
|
version,
|
|
37
38
|
serverInfo,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"2026-07-28.mjs","names":[],"sources":["../../../src/internal/protocols/2026-07-28.ts"],"sourcesContent":["/**\n * Protocol revision 2026-07-28: the stateless era. No handshake and no\n * session; every request self-describes via `_meta`, mirrored into the HTTP\n * request headers. Servers never send requests back, so `onInbound` has\n * nothing to answer.\n *\n * Files here are named for the revision they implement. A later revision that\n * keeps this wire shape gets its own file re-exporting this one.\n */\nimport { Effect,
|
|
1
|
+
{"version":3,"file":"2026-07-28.mjs","names":[],"sources":["../../../src/internal/protocols/2026-07-28.ts"],"sourcesContent":["/**\n * Protocol revision 2026-07-28: the stateless era. No handshake and no\n * session; every request self-describes via `_meta`, mirrored into the HTTP\n * request headers. Servers never send requests back, so `onInbound` has\n * nothing to answer.\n *\n * Files here are named for the revision they implement. A later revision that\n * keeps this wire shape gets its own file re-exporting this one.\n */\nimport { Effect, Option, Predicate, Record, type Scope } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { type McpError, McpUnsupportedProtocol } from \"../../McpError.js\"\nimport { modernRejection, type Protocol, type ProtocolProbe } from \"../protocol.js\"\nimport type { McpConnection, SendMeta } from \"../rpc.js\"\nimport {\n asProtocolVersion,\n CLIENT_INFO,\n decodeDiscoverResult,\n META_CLIENT_CAPABILITIES,\n META_CLIENT_INFO,\n META_SERVER_INFO,\n META_PROTOCOL_VERSION,\n type McpMethod,\n LATEST_VERSION,\n type ProtocolVersion,\n type ServerInfo,\n} from \"../schema.js\"\n\nconst UNKNOWN_SERVER: ServerInfo = { name: \"unknown\" }\n\n/**\n * Which param field carries the subject name mirrored into `Mcp-Name`, per\n * method. Keyed by string rather than `McpMethod` so the methods a later\n * revision adds need no cast here.\n */\nconst NAME_FIELD: Record<string, string> = {\n \"tools/call\": \"name\",\n \"prompts/get\": \"name\",\n \"resources/read\": \"uri\",\n}\n\nconst subjectName = (method: McpMethod, params: unknown): Option.Option<string> =>\n Record.get(NAME_FIELD, method).pipe(\n Option.flatMap((field) => Record.get(asRecord(params), field)),\n Option.filter(Predicate.isString),\n )\n\nconst asRecord = (value: unknown): Record<string, unknown> =>\n Predicate.isObject(value) ? value : {}\n\nconst SENTINEL = /^=\\?base64\\?.*\\?=$/\n// Visible ASCII only, no leading or trailing space (RFC 9110 field values).\nconst HEADER_SAFE = /^[\\x21-\\x7e]([\\x20-\\x7e]*[\\x21-\\x7e])?$/\n\n/**\n * Header values outside the safe set (and any literal that would be mistaken\n * for the marker) ride the spec's Base64 sentinel form.\n */\nexport const headerValue = (raw: string): string =>\n HEADER_SAFE.test(raw) && !SENTINEL.test(raw) ? raw : `=?base64?${Base64.encode(raw)}?=`\n\nconst makeProtocol = (version: ProtocolVersion, serverInfo: ServerInfo): Protocol => ({\n version,\n serverInfo,\n\n envelope: (_method, params) => ({\n ...asRecord(params),\n _meta: {\n [META_PROTOCOL_VERSION]: version,\n [META_CLIENT_INFO]: CLIENT_INFO,\n [META_CLIENT_CAPABILITIES]: {},\n },\n }),\n\n // `MCP-Protocol-Version` MUST equal the `_meta` version or the server\n // answers -32020, so both are minted from the same `version`.\n meta: (method, params): SendMeta => ({\n headers: {\n \"MCP-Protocol-Version\": version,\n \"Mcp-Method\": method,\n ...subjectName(method, params).pipe(\n Option.map((name) => ({ \"Mcp-Name\": headerValue(name) })),\n Option.getOrElse(() => ({})),\n ),\n },\n }),\n\n onInbound: () => Effect.void,\n})\n\n/**\n * Probe with `server/discover`. A `DiscoverResult` means modern. A modern\n * error code means modern-but-rejected: on `-32022` retry with a mutual\n * version. Anything else yields `None`, i.e. \"try the legacy handshake\".\n */\nexport const probe: ProtocolProbe = (connection) =>\n discover(connection, LATEST_VERSION).pipe(\n Effect.map(Option.some),\n Effect.catch((error) =>\n Option.match(modernRejection(error), {\n // Not a modern error shape, so this is not a modern server.\n onNone: () => Effect.succeedNone,\n onSome: ({ supported }) => retryOnMutualVersion(connection, supported),\n }),\n ),\n )\n\nconst retryOnMutualVersion = (\n connection: McpConnection,\n offered: ReadonlyArray<string>,\n): Effect.Effect<Option.Option<Protocol>, McpError, Scope.Scope> =>\n Option.match(Option.firstSomeOf(offered.map(asProtocolVersion)), {\n onNone: () =>\n Effect.fail(\n new McpUnsupportedProtocol({\n offered,\n reason: \"the server is modern but shares no protocol version with this client\",\n }),\n ),\n onSome: (version) => discover(connection, version).pipe(Effect.map(Option.some)),\n })\n\nconst discover = (\n connection: McpConnection,\n version: ProtocolVersion,\n): Effect.Effect<Protocol, McpError, Scope.Scope> =>\n Effect.gen(function* () {\n const probing = makeProtocol(version, UNKNOWN_SERVER)\n const raw = yield* connection.request(\n \"server/discover\",\n probing.envelope(\"server/discover\", {}),\n probing.meta(\"server/discover\", {}),\n )\n const result = yield* decodeDiscoverResult(raw).pipe(\n Effect.mapError(\n () =>\n new McpUnsupportedProtocol({\n offered: [],\n reason: \"server/discover returned an undecodable result\",\n }),\n ),\n )\n // The server answered on `version`, so it supports it; prefer it, and only\n // fall to its list if it somehow disagrees.\n const agreed = result.supportedVersions.includes(version)\n ? version\n : Option.getOrElse(\n Option.firstSomeOf(result.supportedVersions.map(asProtocolVersion)),\n () => version,\n )\n const identity = result._meta?.[META_SERVER_INFO] ?? result.serverInfo ?? UNKNOWN_SERVER\n return makeProtocol(agreed, identity)\n })\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,MAAM,iBAA6B,EAAE,MAAM,UAAU;;;;;;AAOrD,MAAM,aAAqC;CACzC,cAAc;CACd,eAAe;CACf,kBAAkB;AACpB;AAEA,MAAM,eAAe,QAAmB,WACtC,OAAO,IAAI,YAAY,MAAM,CAAC,CAAC,KAC7B,OAAO,SAAS,UAAU,OAAO,IAAI,SAAS,MAAM,GAAG,KAAK,CAAC,GAC7D,OAAO,OAAO,UAAU,QAAQ,CAClC;AAEF,MAAM,YAAY,UAChB,UAAU,SAAS,KAAK,IAAI,QAAQ,CAAC;AAEvC,MAAM,WAAW;AAEjB,MAAM,cAAc;;;;;AAMpB,MAAa,eAAe,QAC1B,YAAY,KAAK,GAAG,KAAK,CAAC,SAAS,KAAK,GAAG,IAAI,MAAM,YAAY,OAAO,OAAO,GAAG,EAAE;AAEtF,MAAM,gBAAgB,SAA0B,gBAAsC;CACpF;CACA;CAEA,WAAW,SAAS,YAAY;EAC9B,GAAG,SAAS,MAAM;EAClB,OAAO;IACJ,wBAAwB;IACxB,mBAAmB;IACnB,2BAA2B,CAAC;EAC/B;CACF;CAIA,OAAO,QAAQ,YAAsB,EACnC,SAAS;EACP,wBAAwB;EACxB,cAAc;EACd,GAAG,YAAY,QAAQ,MAAM,CAAC,CAAC,KAC7B,OAAO,KAAK,UAAU,EAAE,YAAY,YAAY,IAAI,EAAE,EAAE,GACxD,OAAO,iBAAiB,CAAC,EAAE,CAC7B;CACF,EACF;CAEA,iBAAiB,OAAO;AAC1B;;;;;;AAOA,MAAa,SAAwB,eACnC,SAAS,YAAY,cAAc,CAAC,CAAC,KACnC,OAAO,IAAI,OAAO,IAAI,GACtB,OAAO,OAAO,UACZ,OAAO,MAAM,gBAAgB,KAAK,GAAG;CAEnC,cAAc,OAAO;CACrB,SAAS,EAAE,gBAAgB,qBAAqB,YAAY,SAAS;AACvE,CAAC,CACH,CACF;AAEF,MAAM,wBACJ,YACA,YAEA,OAAO,MAAM,OAAO,YAAY,QAAQ,IAAI,iBAAiB,CAAC,GAAG;CAC/D,cACE,OAAO,KACL,IAAI,uBAAuB;EACzB;EACA,QAAQ;CACV,CAAC,CACH;CACF,SAAS,YAAY,SAAS,YAAY,OAAO,CAAC,CAAC,KAAK,OAAO,IAAI,OAAO,IAAI,CAAC;AACjF,CAAC;AAEH,MAAM,YACJ,YACA,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,aAAa,SAAS,cAAc;CACpD,MAAM,MAAM,OAAO,WAAW,QAC5B,mBACA,QAAQ,SAAS,mBAAmB,CAAC,CAAC,GACtC,QAAQ,KAAK,mBAAmB,CAAC,CAAC,CACpC;CACA,MAAM,SAAS,OAAO,qBAAqB,GAAG,CAAC,CAAC,KAC9C,OAAO,eAEH,IAAI,uBAAuB;EACzB,SAAS,CAAC;EACV,QAAQ;CACV,CAAC,CACL,CACF;CAGA,MAAM,SAAS,OAAO,kBAAkB,SAAS,OAAO,IACpD,UACA,OAAO,UACL,OAAO,YAAY,OAAO,kBAAkB,IAAI,iBAAiB,CAAC,SAC5D,OACR;CACJ,MAAM,WAAW,OAAO,QAAA,yCAA6B,OAAO,cAAc;CAC1E,OAAO,aAAa,QAAQ,QAAQ;AACtC,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as McpError } from "../McpError-DWg9BlW5.mjs";
|
|
2
2
|
import { i as Transport } from "../rpc-Bq95Lio9.mjs";
|
|
3
3
|
import { Effect, Scope } from "effect";
|
|
4
|
-
import { ChildProcessSpawner } from "effect/
|
|
4
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
5
5
|
//#region src/internal/stdioTransport.d.ts
|
|
6
6
|
type StdioConfig = {
|
|
7
7
|
readonly command: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpConnectFailed, McpTransportClosed } from "../McpError.mjs";
|
|
2
2
|
import { Effect, Queue, Stream } from "effect";
|
|
3
3
|
import * as JSONL from "@effect-uai/core/JSONL";
|
|
4
|
-
import { ChildProcess } from "effect/
|
|
4
|
+
import { ChildProcess } from "effect/process";
|
|
5
5
|
//#region src/internal/stdioTransport.ts
|
|
6
6
|
/**
|
|
7
7
|
* stdio `Transport`: spawn the server, frame stdout as JSONL (one JSON message
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdioTransport.mjs","names":[],"sources":["../../src/internal/stdioTransport.ts"],"sourcesContent":["/**\n * stdio `Transport`: spawn the server, frame stdout as JSONL (one JSON message\n * per line). Scoped, so the child is killed on scope close. Era-blind; the\n * `meta.headers` a caller passes are HTTP-only and ignored here.\n */\nimport { Cause, Effect, Queue, type Scope, Stream } from \"effect\"\nimport { ChildProcess, ChildProcessSpawner } from \"effect/
|
|
1
|
+
{"version":3,"file":"stdioTransport.mjs","names":[],"sources":["../../src/internal/stdioTransport.ts"],"sourcesContent":["/**\n * stdio `Transport`: spawn the server, frame stdout as JSONL (one JSON message\n * per line). Scoped, so the child is killed on scope close. Era-blind; the\n * `meta.headers` a caller passes are HTTP-only and ignored here.\n */\nimport { Cause, Effect, Queue, type Scope, Stream } from \"effect\"\nimport { ChildProcess, ChildProcessSpawner } from \"effect/process\"\nimport * as JSONL from \"@effect-uai/core/JSONL\"\nimport { McpConnectFailed, type McpError, McpTransportClosed } from \"../McpError.js\"\nimport type { Transport } from \"./rpc.js\"\n\nexport type StdioConfig = {\n readonly command: string\n readonly args?: ReadonlyArray<string>\n readonly env?: Record<string, string>\n readonly cwd?: string\n}\n\nconst encoder = new TextEncoder()\n\nexport const make = (\n config: StdioConfig,\n): Effect.Effect<Transport, McpError, Scope.Scope | ChildProcessSpawner.ChildProcessSpawner> =>\n Effect.gen(function* () {\n // Outbound frames go through a queue rendered as the child's stdin stream;\n // `Queue.end` on scope close lets the child see a clean EOF.\n const outbound = yield* Queue.make<Uint8Array, Cause.Done>()\n const stdin = Stream.fromQueue(outbound)\n\n const handle = yield* ChildProcess.make(config.command, [...(config.args ?? [])], {\n ...(config.cwd !== undefined ? { cwd: config.cwd } : {}),\n ...(config.env !== undefined ? { env: config.env } : {}),\n stdin: { stream: stdin, endOnDone: true },\n stdout: \"pipe\",\n stderr: \"pipe\",\n killSignal: \"SIGTERM\",\n forceKillAfter: \"2 seconds\",\n }).pipe(\n Effect.mapError(\n (cause) =>\n new McpConnectFailed({ reason: `could not spawn ${config.command}`, raw: cause }),\n ),\n )\n yield* Effect.addFinalizer(() => Queue.end(outbound))\n\n const send = (frame: string): Effect.Effect<void, McpError> =>\n Queue.offer(outbound, encoder.encode(`${frame}\\n`)).pipe(\n Effect.mapError(\n (cause) =>\n new McpTransportClosed({ reason: \"server stdin closed\", raw: cause }) as McpError,\n ),\n Effect.asVoid,\n )\n\n const messages = handle.stdout.pipe(\n JSONL.fromBytes,\n Stream.mapError(\n (cause) => new McpTransportClosed({ reason: \"server stdout closed\", raw: cause }),\n ),\n )\n\n return { send, messages } satisfies Transport\n })\n"],"mappings":";;;;;;;;;;AAkBA,MAAM,UAAU,IAAI,YAAY;AAEhC,MAAa,QACX,WAEA,OAAO,IAAI,aAAa;CAGtB,MAAM,WAAW,OAAO,MAAM,KAA6B;CAC3D,MAAM,QAAQ,OAAO,UAAU,QAAQ;CAEvC,MAAM,SAAS,OAAO,aAAa,KAAK,OAAO,SAAS,CAAC,GAAI,OAAO,QAAQ,CAAC,CAAE,GAAG;EAChF,GAAI,OAAO,QAAQ,KAAA,IAAY,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;EACtD,GAAI,OAAO,QAAQ,KAAA,IAAY,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;EACtD,OAAO;GAAE,QAAQ;GAAO,WAAW;EAAK;EACxC,QAAQ;EACR,QAAQ;EACR,YAAY;EACZ,gBAAgB;CAClB,CAAC,CAAC,CAAC,KACD,OAAO,UACJ,UACC,IAAI,iBAAiB;EAAE,QAAQ,mBAAmB,OAAO;EAAW,KAAK;CAAM,CAAC,CACpF,CACF;CACA,OAAO,OAAO,mBAAmB,MAAM,IAAI,QAAQ,CAAC;CAEpD,MAAM,QAAQ,UACZ,MAAM,MAAM,UAAU,QAAQ,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,KAClD,OAAO,UACJ,UACC,IAAI,mBAAmB;EAAE,QAAQ;EAAuB,KAAK;CAAM,CAAC,CACxE,GACA,OAAO,MACT;CASF,OAAO;EAAE;EAAM,UAPE,OAAO,OAAO,KAC7B,MAAM,WACN,OAAO,UACJ,UAAU,IAAI,mBAAmB;GAAE,QAAQ;GAAwB,KAAK;EAAM,CAAC,CAClF,CAGoB;CAAE;AAC1B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Model Context Protocol client provider for @effect-uai/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@effect/platform-node": "4.0.
|
|
57
|
-
"@effect/vitest": "4.0.
|
|
58
|
-
"effect": "4.0.
|
|
56
|
+
"@effect/platform-node": "4.0.1",
|
|
57
|
+
"@effect/vitest": "4.0.1",
|
|
58
|
+
"effect": "4.0.1",
|
|
59
59
|
"typescript": "^7.0.2",
|
|
60
|
-
"@effect-uai/core": "0.
|
|
60
|
+
"@effect-uai/core": "0.18.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@effect-uai/core": ">=0.5.0 <1",
|
|
64
|
-
"effect": ">=4.0.
|
|
64
|
+
"effect": ">=4.0.1 <5.0.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsdown",
|
package/src/Client.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* and fixed for the connection's lifetime. The client surface is era-uniform.
|
|
5
5
|
*/
|
|
6
6
|
import { Context, Effect, Layer, Option, Ref, type Scope } from "effect"
|
|
7
|
-
import type { HttpClient } from "effect/
|
|
8
|
-
import type { ChildProcessSpawner } from "effect/
|
|
7
|
+
import type { HttpClient } from "effect/http"
|
|
8
|
+
import type { ChildProcessSpawner } from "effect/process"
|
|
9
9
|
import { McpConnectFailed, type McpError, McpProtocolError } from "./McpError.js"
|
|
10
10
|
import { Auth, type TokenSource } from "./internal/auth.js"
|
|
11
11
|
import * as Handshake from "./internal/protocols/2025-06-18.js"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* cancellation signal (closing the response stream).
|
|
10
10
|
*/
|
|
11
11
|
import { Cause, Effect, Match, Option, Queue, Ref, type Scope, Stream } from "effect"
|
|
12
|
-
import { HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/
|
|
12
|
+
import { HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http"
|
|
13
13
|
import * as SSE from "@effect-uai/core/SSE"
|
|
14
14
|
import {
|
|
15
15
|
McpAuthRequired,
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Files here are named for the revision they implement. A later revision that
|
|
8
8
|
* keeps this wire shape gets its own file re-exporting this one.
|
|
9
9
|
*/
|
|
10
|
-
import { Effect,
|
|
10
|
+
import { Effect, Option, Predicate, Record, type Scope } from "effect"
|
|
11
|
+
import { Base64 } from "effect/encoding"
|
|
11
12
|
import { type McpError, McpUnsupportedProtocol } from "../../McpError.js"
|
|
12
13
|
import { modernRejection, type Protocol, type ProtocolProbe } from "../protocol.js"
|
|
13
14
|
import type { McpConnection, SendMeta } from "../rpc.js"
|
|
@@ -56,7 +57,7 @@ const HEADER_SAFE = /^[\x21-\x7e]([\x20-\x7e]*[\x21-\x7e])?$/
|
|
|
56
57
|
* for the marker) ride the spec's Base64 sentinel form.
|
|
57
58
|
*/
|
|
58
59
|
export const headerValue = (raw: string): string =>
|
|
59
|
-
HEADER_SAFE.test(raw) && !SENTINEL.test(raw) ? raw : `=?base64?${
|
|
60
|
+
HEADER_SAFE.test(raw) && !SENTINEL.test(raw) ? raw : `=?base64?${Base64.encode(raw)}?=`
|
|
60
61
|
|
|
61
62
|
const makeProtocol = (version: ProtocolVersion, serverInfo: ServerInfo): Protocol => ({
|
|
62
63
|
version,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `meta.headers` a caller passes are HTTP-only and ignored here.
|
|
5
5
|
*/
|
|
6
6
|
import { Cause, Effect, Queue, type Scope, Stream } from "effect"
|
|
7
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/
|
|
7
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process"
|
|
8
8
|
import * as JSONL from "@effect-uai/core/JSONL"
|
|
9
9
|
import { McpConnectFailed, type McpError, McpTransportClosed } from "../McpError.js"
|
|
10
10
|
import type { Transport } from "./rpc.js"
|