@effect-uai/mcp 0.12.1
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/LICENSE +21 -0
- package/dist/Client.d.mts +55 -0
- package/dist/Client.d.mts.map +1 -0
- package/dist/Client.mjs +85 -0
- package/dist/Client.mjs.map +1 -0
- package/dist/McpError-DWg9BlW5.d.mts +83 -0
- package/dist/McpError-DWg9BlW5.d.mts.map +1 -0
- package/dist/McpError.d.mts +2 -0
- package/dist/McpError.mjs +61 -0
- package/dist/McpError.mjs.map +1 -0
- package/dist/Toolkit.d.mts +20 -0
- package/dist/Toolkit.d.mts.map +1 -0
- package/dist/Toolkit.mjs +70 -0
- package/dist/Toolkit.mjs.map +1 -0
- package/dist/auth-ZDvsxpCo.d.mts +126 -0
- package/dist/auth-ZDvsxpCo.d.mts.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/internal/auth.d.mts +2 -0
- package/dist/internal/auth.mjs +25 -0
- package/dist/internal/auth.mjs.map +1 -0
- package/dist/internal/httpTransport.d.mts +17 -0
- package/dist/internal/httpTransport.d.mts.map +1 -0
- package/dist/internal/httpTransport.mjs +88 -0
- package/dist/internal/httpTransport.mjs.map +1 -0
- package/dist/internal/protocol.d.mts +2 -0
- package/dist/internal/protocol.mjs +23 -0
- package/dist/internal/protocol.mjs.map +1 -0
- package/dist/internal/protocols/2025-06-18.d.mts +11 -0
- package/dist/internal/protocols/2025-06-18.d.mts.map +1 -0
- package/dist/internal/protocols/2025-06-18.mjs +70 -0
- package/dist/internal/protocols/2025-06-18.mjs.map +1 -0
- package/dist/internal/protocols/2025-11-25.d.mts +2 -0
- package/dist/internal/protocols/2025-11-25.mjs +2 -0
- package/dist/internal/protocols/2026-07-28.d.mts +16 -0
- package/dist/internal/protocols/2026-07-28.d.mts.map +1 -0
- package/dist/internal/protocols/2026-07-28.mjs +83 -0
- package/dist/internal/protocols/2026-07-28.mjs.map +1 -0
- package/dist/internal/rpc.d.mts +2 -0
- package/dist/internal/rpc.mjs +78 -0
- package/dist/internal/rpc.mjs.map +1 -0
- package/dist/internal/schema.d.mts +2 -0
- package/dist/internal/schema.mjs +168 -0
- package/dist/internal/schema.mjs.map +1 -0
- package/dist/internal/stdioTransport.d.mts +15 -0
- package/dist/internal/stdioTransport.d.mts.map +1 -0
- package/dist/internal/stdioTransport.mjs +46 -0
- package/dist/internal/stdioTransport.mjs.map +1 -0
- package/dist/protocol-kjC3iAMb.d.mts +35 -0
- package/dist/protocol-kjC3iAMb.d.mts.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/rpc-Bq95Lio9.d.mts +34 -0
- package/dist/rpc-Bq95Lio9.d.mts.map +1 -0
- package/dist/schema-D2oUNuo5.d.mts +245 -0
- package/dist/schema-D2oUNuo5.d.mts.map +1 -0
- package/package.json +70 -0
- package/src/Client.ts +180 -0
- package/src/McpError.ts +93 -0
- package/src/Toolkit.ts +109 -0
- package/src/index.ts +3 -0
- package/src/internal/auth.ts +48 -0
- package/src/internal/httpTransport.ts +171 -0
- package/src/internal/protocol.ts +67 -0
- package/src/internal/protocols/2025-06-18.ts +110 -0
- package/src/internal/protocols/2025-11-25.ts +8 -0
- package/src/internal/protocols/2026-07-28.test.ts +131 -0
- package/src/internal/protocols/2026-07-28.ts +152 -0
- package/src/internal/rpc.test.ts +156 -0
- package/src/internal/rpc.ts +189 -0
- package/src/internal/schema.ts +223 -0
- package/src/internal/stdioTransport.ts +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Betalyra Sociedade Unipessoal Lda.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { i as McpError } from "./McpError-DWg9BlW5.mjs";
|
|
2
|
+
import { n as TokenSource, t as Auth } from "./auth-ZDvsxpCo.mjs";
|
|
3
|
+
import { C as ProtocolVersion, D as ToolInfo, T as ServerInfo, o as CallToolResult } from "./schema-D2oUNuo5.mjs";
|
|
4
|
+
import { Context, Effect, Layer, Scope } from "effect";
|
|
5
|
+
import { HttpClient } from "effect/unstable/http";
|
|
6
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
7
|
+
declare namespace Client_d_exports {
|
|
8
|
+
export { Auth, Mcp, McpClient, McpClientConfig, McpRequirements, McpServerInfo, ProtocolPin, TokenSource, connect, layer };
|
|
9
|
+
}
|
|
10
|
+
/** `auto` runs the probe; a pinned version skips detection. */
|
|
11
|
+
type ProtocolPin = "auto" | ProtocolVersion;
|
|
12
|
+
type McpClientConfig = {
|
|
13
|
+
readonly transport: "http";
|
|
14
|
+
readonly url: string;
|
|
15
|
+
/** Static, non-auth headers sent on every request. */
|
|
16
|
+
readonly headers?: Record<string, string>;
|
|
17
|
+
/** Omit for a public server. See `Auth` for the three shapes. */
|
|
18
|
+
readonly auth?: Auth;
|
|
19
|
+
readonly protocol?: ProtocolPin;
|
|
20
|
+
} | {
|
|
21
|
+
readonly transport: "stdio";
|
|
22
|
+
readonly command: string;
|
|
23
|
+
readonly args?: ReadonlyArray<string>;
|
|
24
|
+
readonly env?: Record<string, string>;
|
|
25
|
+
readonly cwd?: string;
|
|
26
|
+
readonly protocol?: ProtocolPin;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Services a client needs, by transport. Both are listed because the config is
|
|
30
|
+
* a runtime value: an app that only uses one still provides the other's layer,
|
|
31
|
+
* which is cheap (`FetchHttpClient.layer` / `NodeServices.layer`).
|
|
32
|
+
*/
|
|
33
|
+
type McpRequirements = HttpClient.HttpClient | ChildProcessSpawner.ChildProcessSpawner;
|
|
34
|
+
type McpServerInfo = ServerInfo & {
|
|
35
|
+
/** The negotiated protocol version: the only era signal callers need. */
|
|
36
|
+
readonly protocolVersion: ProtocolVersion;
|
|
37
|
+
};
|
|
38
|
+
type McpClient = {
|
|
39
|
+
readonly listTools: Effect.Effect<ReadonlyArray<ToolInfo>, McpError>;
|
|
40
|
+
readonly callTool: (name: string, args: unknown) => Effect.Effect<CallToolResult, McpError>;
|
|
41
|
+
readonly serverInfo: McpServerInfo;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Open a scoped client. The connection lives exactly as long as the enclosing
|
|
45
|
+
* scope, so `Effect.scoped`, a `Layer`, or `Stream.scoped` all give correct
|
|
46
|
+
* teardown with no explicit close.
|
|
47
|
+
*/
|
|
48
|
+
declare const connect: (config: McpClientConfig) => Effect.Effect<McpClient, McpError, Scope.Scope | McpRequirements>;
|
|
49
|
+
declare const Mcp_base: Context.ServiceClass<Mcp, "@betalyra/effect-uai/providers/mcp/Mcp", McpClient>;
|
|
50
|
+
declare class Mcp extends Mcp_base {}
|
|
51
|
+
/** DI convenience for the single-server app: connects when the layer builds. */
|
|
52
|
+
declare const layer: (config: McpClientConfig) => Layer.Layer<Mcp, McpError, McpRequirements>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { Auth, Mcp, McpClient, McpClientConfig, McpRequirements, McpServerInfo, ProtocolPin, type TokenSource, connect, layer, Client_d_exports as t };
|
|
55
|
+
//# sourceMappingURL=Client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Client.d.mts","names":[],"sources":["../src/Client.ts"],"mappings":";;;;;;;;;;KA8BY,uBAAuB;KAEvB;WAEG;WACA;;WAEA,UAAU;;WAEV,OAAO;WACP,WAAW;;WAGX;WACA;WACA,OAAO;WACP,MAAM;WACN;WACA,WAAW;;;;;;;KAQd,kBAAkB,WAAW,aAAa,oBAAoB;KAE9D,gBAAgB;;WAEjB,iBAAiB;;KAGhB;WACD,WAAW,OAAO,OAAO,cAAc,WAAW;WAClD,WAAW,cAAc,kBAAkB,OAAO,OAAO,gBAAgB;WACzE,YAAY;;;;;;;cA8EV,UAAO,QACV,oBACP,OAAO,OAAO,WAAW,UAAU,MAAM,QAAQ;;cA2BvC,YAAY;;cAKZ,QAAK,QAAY,oBAAkB,MAAM,MAAM,KAAK,UAAU"}
|
package/dist/Client.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { McpConnectFailed, McpProtocolError } from "./McpError.mjs";
|
|
3
|
+
import { Auth } from "./internal/auth.mjs";
|
|
4
|
+
import { decodeCallToolResult, decodeListToolsResult } from "./internal/schema.mjs";
|
|
5
|
+
import { probe } from "./internal/protocols/2025-06-18.mjs";
|
|
6
|
+
import { probe as probe$1 } from "./internal/protocols/2026-07-28.mjs";
|
|
7
|
+
import { open } from "./internal/rpc.mjs";
|
|
8
|
+
import { make } from "./internal/httpTransport.mjs";
|
|
9
|
+
import { make as make$1 } from "./internal/stdioTransport.mjs";
|
|
10
|
+
import { Context, Effect, Layer, Option, Ref } from "effect";
|
|
11
|
+
//#region src/Client.ts
|
|
12
|
+
/**
|
|
13
|
+
* `connect` opens a scoped MCP client: transport -> rpc core -> protocol probe.
|
|
14
|
+
* The protocol era is a property of the server, so it is negotiated once here
|
|
15
|
+
* and fixed for the connection's lifetime. The client surface is era-uniform.
|
|
16
|
+
*/
|
|
17
|
+
var Client_exports = /* @__PURE__ */ __exportAll({
|
|
18
|
+
Auth: () => Auth,
|
|
19
|
+
Mcp: () => Mcp,
|
|
20
|
+
connect: () => connect,
|
|
21
|
+
layer: () => layer
|
|
22
|
+
});
|
|
23
|
+
const undecodable = (method, detail, raw) => new McpProtocolError({
|
|
24
|
+
method,
|
|
25
|
+
reason: `undecodable ${method} result${detail}`,
|
|
26
|
+
raw
|
|
27
|
+
});
|
|
28
|
+
const makeClient = (connection, protocol) => {
|
|
29
|
+
const request = (method, params) => connection.request(method, protocol.envelope(method, params), protocol.meta(method, params));
|
|
30
|
+
return {
|
|
31
|
+
listTools: request("tools/list", {}).pipe(Effect.flatMap((raw) => decodeListToolsResult(raw).pipe(Effect.mapError(() => undecodable("tools/list", "", raw)), Effect.map((result) => result.tools)))),
|
|
32
|
+
callTool: (name, args) => request("tools/call", {
|
|
33
|
+
name,
|
|
34
|
+
arguments: args ?? {}
|
|
35
|
+
}).pipe(Effect.flatMap((raw) => decodeCallToolResult(raw).pipe(Effect.mapError(() => undecodable("tools/call", ` for ${name}`, raw))))),
|
|
36
|
+
serverInfo: {
|
|
37
|
+
...protocol.serverInfo,
|
|
38
|
+
protocolVersion: protocol.version
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Try the stateless protocol first, then the handshake era. The stateless
|
|
44
|
+
* probe yields `None` only when the server proves it is not stateless (an
|
|
45
|
+
* error that is not one of the recognized modern codes), which is exactly the
|
|
46
|
+
* spec's era discriminator. A pinned `protocol` skips detection.
|
|
47
|
+
*
|
|
48
|
+
* The result is cached for the connection's lifetime by the caller: era is a
|
|
49
|
+
* property of the server, not of a request.
|
|
50
|
+
*/
|
|
51
|
+
const negotiate = (connection, pin) => {
|
|
52
|
+
if (pin === "2026-07-28") return required(probe$1(connection));
|
|
53
|
+
if (pin !== "auto") return required(probe(connection));
|
|
54
|
+
return probe$1(connection).pipe(Effect.flatMap(Option.match({
|
|
55
|
+
onNone: () => required(probe(connection)),
|
|
56
|
+
onSome: Effect.succeed
|
|
57
|
+
})));
|
|
58
|
+
};
|
|
59
|
+
const required = (probe) => probe.pipe(Effect.flatMap(Option.match({
|
|
60
|
+
onNone: () => Effect.fail(new McpConnectFailed({ reason: "the server does not speak a supported MCP protocol" })),
|
|
61
|
+
onSome: Effect.succeed
|
|
62
|
+
})));
|
|
63
|
+
/**
|
|
64
|
+
* Open a scoped client. The connection lives exactly as long as the enclosing
|
|
65
|
+
* scope, so `Effect.scoped`, a `Layer`, or `Stream.scoped` all give correct
|
|
66
|
+
* teardown with no explicit close.
|
|
67
|
+
*/
|
|
68
|
+
const connect = (config) => Effect.gen(function* () {
|
|
69
|
+
const transport = yield* config.transport === "http" ? make(config) : make$1(config);
|
|
70
|
+
const protocolRef = yield* Ref.make(Option.none());
|
|
71
|
+
const connection = yield* open(transport, (inbound) => Ref.get(protocolRef).pipe(Effect.flatMap(Option.match({
|
|
72
|
+
onNone: () => Effect.void,
|
|
73
|
+
onSome: (protocol) => protocol.onInbound(inbound)
|
|
74
|
+
}))));
|
|
75
|
+
const protocol = yield* negotiate(connection, config.protocol ?? "auto");
|
|
76
|
+
yield* Ref.set(protocolRef, Option.some(protocol));
|
|
77
|
+
return makeClient(connection, protocol);
|
|
78
|
+
});
|
|
79
|
+
var Mcp = class extends Context.Service()("@betalyra/effect-uai/providers/mcp/Mcp") {};
|
|
80
|
+
/** DI convenience for the single-server app: connects when the layer builds. */
|
|
81
|
+
const layer = (config) => Layer.effect(Mcp, connect(config));
|
|
82
|
+
//#endregion
|
|
83
|
+
export { Auth, Mcp, connect, layer, Client_exports as t };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=Client.mjs.map
|
|
@@ -0,0 +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/unstable/http\"\nimport type { ChildProcessSpawner } from \"effect/unstable/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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
declare namespace McpError_d_exports {
|
|
2
|
+
export { McpAuthError, McpAuthRequired, McpConnectFailed, McpError, McpProtocolError, McpTransportClosed, McpUnsupportedProtocol, describe };
|
|
3
|
+
}
|
|
4
|
+
declare const McpConnectFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
5
|
+
readonly _tag: "McpConnectFailed";
|
|
6
|
+
} & Readonly<A>;
|
|
7
|
+
/**
|
|
8
|
+
* Transport could not establish: DNS / TLS / non-2xx on the first request,
|
|
9
|
+
* a failed spawn, or a failed era probe. The raw transport error on `raw`.
|
|
10
|
+
*/
|
|
11
|
+
declare class McpConnectFailed extends McpConnectFailed_base<{
|
|
12
|
+
reason?: string;
|
|
13
|
+
raw?: unknown;
|
|
14
|
+
}> {}
|
|
15
|
+
declare const McpUnsupportedProtocol_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
16
|
+
readonly _tag: "McpUnsupportedProtocol";
|
|
17
|
+
} & Readonly<A>;
|
|
18
|
+
/**
|
|
19
|
+
* Negotiation ended on a protocol version this client does not support
|
|
20
|
+
* (e.g. a 2025-03-26-only server, or a modern server whose `supported`
|
|
21
|
+
* list has no mutual version). `offered` is what the server proposed.
|
|
22
|
+
*/
|
|
23
|
+
declare class McpUnsupportedProtocol extends McpUnsupportedProtocol_base<{
|
|
24
|
+
offered: ReadonlyArray<string>;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}> {}
|
|
27
|
+
declare const McpProtocolError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
28
|
+
readonly _tag: "McpProtocolError";
|
|
29
|
+
} & Readonly<A>;
|
|
30
|
+
/**
|
|
31
|
+
* A JSON-RPC error reply (carrying its `code`), a reply that failed to
|
|
32
|
+
* decode, or an unsupported protocol feature (v1 fails `input_required`
|
|
33
|
+
* results here). `method` names the request that was in flight.
|
|
34
|
+
*/
|
|
35
|
+
declare class McpProtocolError extends McpProtocolError_base<{
|
|
36
|
+
method?: string;
|
|
37
|
+
code?: number;
|
|
38
|
+
reason?: string;
|
|
39
|
+
raw?: unknown;
|
|
40
|
+
}> {}
|
|
41
|
+
declare const McpTransportClosed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
42
|
+
readonly _tag: "McpTransportClosed";
|
|
43
|
+
} & Readonly<A>;
|
|
44
|
+
/**
|
|
45
|
+
* The connection dropped with a request in flight, or a legacy session
|
|
46
|
+
* expired (HTTP 404 on a known `Mcp-Session-Id`). Not resumable; the
|
|
47
|
+
* remedy is reconnecting and re-issuing the request.
|
|
48
|
+
*/
|
|
49
|
+
declare class McpTransportClosed extends McpTransportClosed_base<{
|
|
50
|
+
method?: string;
|
|
51
|
+
reason?: string;
|
|
52
|
+
raw?: unknown;
|
|
53
|
+
}> {}
|
|
54
|
+
declare const McpAuthRequired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
55
|
+
readonly _tag: "McpAuthRequired";
|
|
56
|
+
} & Readonly<A>;
|
|
57
|
+
/**
|
|
58
|
+
* The server answered `401`. `resourceMetadataUrl` is the RFC 9728
|
|
59
|
+
* Protected Resource Metadata pointer parsed from `WWW-Authenticate`,
|
|
60
|
+
* when present; an OAuth layer starts discovery there.
|
|
61
|
+
*/
|
|
62
|
+
declare class McpAuthRequired extends McpAuthRequired_base<{
|
|
63
|
+
resourceMetadataUrl?: string;
|
|
64
|
+
wwwAuthenticate?: string;
|
|
65
|
+
}> {}
|
|
66
|
+
declare const McpAuthError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
67
|
+
readonly _tag: "McpAuthError";
|
|
68
|
+
} & Readonly<A>;
|
|
69
|
+
/** A `TokenSource` / OAuth grant failed to mint or refresh a token. */
|
|
70
|
+
declare class McpAuthError extends McpAuthError_base<{
|
|
71
|
+
reason?: string;
|
|
72
|
+
raw?: unknown;
|
|
73
|
+
}> {}
|
|
74
|
+
type McpError = McpConnectFailed | McpUnsupportedProtocol | McpProtocolError | McpTransportClosed | McpAuthRequired | McpAuthError;
|
|
75
|
+
/**
|
|
76
|
+
* Short human-readable description of an error, for logs and model-facing
|
|
77
|
+
* failure messages (`Toolkit.describeFailures(McpError.describe)`). Prose,
|
|
78
|
+
* not a contract; branch on `_tag` instead.
|
|
79
|
+
*/
|
|
80
|
+
declare const describe: (e: McpError) => string;
|
|
81
|
+
//#endregion
|
|
82
|
+
export { McpError_d_exports as a, McpUnsupportedProtocol as c, McpError as i, describe as l, McpAuthRequired as n, McpProtocolError as o, McpConnectFailed as r, McpTransportClosed as s, McpAuthError as t };
|
|
83
|
+
//# sourceMappingURL=McpError-DWg9BlW5.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpError-DWg9BlW5.d.mts","names":[],"sources":["../src/McpError.ts"],"mappings":";;;;;;;;;;cAMa,yBAAyB;EACpC;EACA;;;;;;;;;;cAQW,+BAA+B;EAC1C,SAAS;EACT;;;;;;;;;;cAQW,yBAAyB;EACpC;EACA;EACA;EACA;;;;;;;;;;cAQW,2BAA2B;EACtC;EACA;EACA;;;;;;;;;;cAQW,wBAAwB;EACnC;EACA;;;;;;cAIW,qBAAqB;EAChC;EACA;;KAGU,WACR,mBACA,yBACA,mBACA,qBACA,kBACA;;;;;;cAUS,WAAW,GAAG"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { c as McpUnsupportedProtocol, i as McpError, l as describe, n as McpAuthRequired, o as McpProtocolError, r as McpConnectFailed, s as McpTransportClosed, t as McpAuthError } from "./McpError-DWg9BlW5.mjs";
|
|
2
|
+
export { McpAuthError, McpAuthRequired, McpConnectFailed, McpError, McpProtocolError, McpTransportClosed, McpUnsupportedProtocol, describe };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { Data, Match } from "effect";
|
|
3
|
+
//#region src/McpError.ts
|
|
4
|
+
var McpError_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
McpAuthError: () => McpAuthError,
|
|
6
|
+
McpAuthRequired: () => McpAuthRequired,
|
|
7
|
+
McpConnectFailed: () => McpConnectFailed,
|
|
8
|
+
McpProtocolError: () => McpProtocolError,
|
|
9
|
+
McpTransportClosed: () => McpTransportClosed,
|
|
10
|
+
McpUnsupportedProtocol: () => McpUnsupportedProtocol,
|
|
11
|
+
describe: () => describe
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Transport could not establish: DNS / TLS / non-2xx on the first request,
|
|
15
|
+
* a failed spawn, or a failed era probe. The raw transport error on `raw`.
|
|
16
|
+
*/
|
|
17
|
+
var McpConnectFailed = class extends Data.TaggedError("McpConnectFailed") {};
|
|
18
|
+
/**
|
|
19
|
+
* Negotiation ended on a protocol version this client does not support
|
|
20
|
+
* (e.g. a 2025-03-26-only server, or a modern server whose `supported`
|
|
21
|
+
* list has no mutual version). `offered` is what the server proposed.
|
|
22
|
+
*/
|
|
23
|
+
var McpUnsupportedProtocol = class extends Data.TaggedError("McpUnsupportedProtocol") {};
|
|
24
|
+
/**
|
|
25
|
+
* A JSON-RPC error reply (carrying its `code`), a reply that failed to
|
|
26
|
+
* decode, or an unsupported protocol feature (v1 fails `input_required`
|
|
27
|
+
* results here). `method` names the request that was in flight.
|
|
28
|
+
*/
|
|
29
|
+
var McpProtocolError = class extends Data.TaggedError("McpProtocolError") {};
|
|
30
|
+
/**
|
|
31
|
+
* The connection dropped with a request in flight, or a legacy session
|
|
32
|
+
* expired (HTTP 404 on a known `Mcp-Session-Id`). Not resumable; the
|
|
33
|
+
* remedy is reconnecting and re-issuing the request.
|
|
34
|
+
*/
|
|
35
|
+
var McpTransportClosed = class extends Data.TaggedError("McpTransportClosed") {};
|
|
36
|
+
/**
|
|
37
|
+
* The server answered `401`. `resourceMetadataUrl` is the RFC 9728
|
|
38
|
+
* Protected Resource Metadata pointer parsed from `WWW-Authenticate`,
|
|
39
|
+
* when present; an OAuth layer starts discovery there.
|
|
40
|
+
*/
|
|
41
|
+
var McpAuthRequired = class extends Data.TaggedError("McpAuthRequired") {};
|
|
42
|
+
/** A `TokenSource` / OAuth grant failed to mint or refresh a token. */
|
|
43
|
+
var McpAuthError = class extends Data.TaggedError("McpAuthError") {};
|
|
44
|
+
const withReason = (base, reason) => reason === void 0 ? base : `${base}: ${reason}`;
|
|
45
|
+
/**
|
|
46
|
+
* Short human-readable description of an error, for logs and model-facing
|
|
47
|
+
* failure messages (`Toolkit.describeFailures(McpError.describe)`). Prose,
|
|
48
|
+
* not a contract; branch on `_tag` instead.
|
|
49
|
+
*/
|
|
50
|
+
const describe = Match.type().pipe(Match.discriminatorsExhaustive("_tag")({
|
|
51
|
+
McpConnectFailed: (e) => withReason("could not connect to the MCP server", e.reason),
|
|
52
|
+
McpUnsupportedProtocol: (e) => `the MCP server only offers unsupported protocol versions (${e.offered.join(", ")})`,
|
|
53
|
+
McpProtocolError: (e) => withReason(`the MCP server rejected ${e.method ?? "the request"}${e.code === void 0 ? "" : ` (${e.code})`}`, e.reason),
|
|
54
|
+
McpTransportClosed: (e) => withReason("the MCP connection closed", e.reason),
|
|
55
|
+
McpAuthRequired: () => "the MCP server requires authentication",
|
|
56
|
+
McpAuthError: (e) => withReason("MCP authentication failed", e.reason)
|
|
57
|
+
}));
|
|
58
|
+
//#endregion
|
|
59
|
+
export { McpAuthError, McpAuthRequired, McpConnectFailed, McpProtocolError, McpTransportClosed, McpUnsupportedProtocol, describe, McpError_exports as t };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=McpError.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpError.mjs","names":[],"sources":["../src/McpError.ts"],"sourcesContent":["import { Data, Match } from \"effect\"\n\n/**\n * Transport could not establish: DNS / TLS / non-2xx on the first request,\n * a failed spawn, or a failed era probe. The raw transport error on `raw`.\n */\nexport class McpConnectFailed extends Data.TaggedError(\"McpConnectFailed\")<{\n reason?: string\n raw?: unknown\n}> {}\n\n/**\n * Negotiation ended on a protocol version this client does not support\n * (e.g. a 2025-03-26-only server, or a modern server whose `supported`\n * list has no mutual version). `offered` is what the server proposed.\n */\nexport class McpUnsupportedProtocol extends Data.TaggedError(\"McpUnsupportedProtocol\")<{\n offered: ReadonlyArray<string>\n reason?: string\n}> {}\n\n/**\n * A JSON-RPC error reply (carrying its `code`), a reply that failed to\n * decode, or an unsupported protocol feature (v1 fails `input_required`\n * results here). `method` names the request that was in flight.\n */\nexport class McpProtocolError extends Data.TaggedError(\"McpProtocolError\")<{\n method?: string\n code?: number\n reason?: string\n raw?: unknown\n}> {}\n\n/**\n * The connection dropped with a request in flight, or a legacy session\n * expired (HTTP 404 on a known `Mcp-Session-Id`). Not resumable; the\n * remedy is reconnecting and re-issuing the request.\n */\nexport class McpTransportClosed extends Data.TaggedError(\"McpTransportClosed\")<{\n method?: string\n reason?: string\n raw?: unknown\n}> {}\n\n/**\n * The server answered `401`. `resourceMetadataUrl` is the RFC 9728\n * Protected Resource Metadata pointer parsed from `WWW-Authenticate`,\n * when present; an OAuth layer starts discovery there.\n */\nexport class McpAuthRequired extends Data.TaggedError(\"McpAuthRequired\")<{\n resourceMetadataUrl?: string\n wwwAuthenticate?: string\n}> {}\n\n/** A `TokenSource` / OAuth grant failed to mint or refresh a token. */\nexport class McpAuthError extends Data.TaggedError(\"McpAuthError\")<{\n reason?: string\n raw?: unknown\n}> {}\n\nexport type McpError =\n | McpConnectFailed\n | McpUnsupportedProtocol\n | McpProtocolError\n | McpTransportClosed\n | McpAuthRequired\n | McpAuthError\n\nconst withReason = (base: string, reason: string | undefined): string =>\n reason === undefined ? base : `${base}: ${reason}`\n\n/**\n * Short human-readable description of an error, for logs and model-facing\n * failure messages (`Toolkit.describeFailures(McpError.describe)`). Prose,\n * not a contract; branch on `_tag` instead.\n */\nexport const describe: (e: McpError) => string = Match.type<McpError>().pipe(\n Match.discriminatorsExhaustive(\"_tag\")({\n McpConnectFailed: (e) => withReason(\"could not connect to the MCP server\", e.reason),\n McpUnsupportedProtocol: (e) =>\n `the MCP server only offers unsupported protocol versions (${e.offered.join(\", \")})`,\n McpProtocolError: (e) =>\n withReason(\n `the MCP server rejected ${e.method ?? \"the request\"}${\n e.code === undefined ? \"\" : ` (${e.code})`\n }`,\n e.reason,\n ),\n McpTransportClosed: (e) => withReason(\"the MCP connection closed\", e.reason),\n McpAuthRequired: () => \"the MCP server requires authentication\",\n McpAuthError: (e) => withReason(\"MCP authentication failed\", e.reason),\n }),\n)\n"],"mappings":";;;;;;;;;;;;;;;;AAMA,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAGvE,CAAC;;;;;;AAOJ,IAAa,yBAAb,cAA4C,KAAK,YAAY,wBAAwB,CAAC,CAGnF,CAAC;;;;;;AAOJ,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAKvE,CAAC;;;;;;AAOJ,IAAa,qBAAb,cAAwC,KAAK,YAAY,oBAAoB,CAAC,CAI3E,CAAC;;;;;;AAOJ,IAAa,kBAAb,cAAqC,KAAK,YAAY,iBAAiB,CAAC,CAGrE,CAAC;;AAGJ,IAAa,eAAb,cAAkC,KAAK,YAAY,cAAc,CAAC,CAG/D,CAAC;AAUJ,MAAM,cAAc,MAAc,WAChC,WAAW,KAAA,IAAY,OAAO,GAAG,KAAK,IAAI;;;;;;AAO5C,MAAa,WAAoC,MAAM,KAAe,CAAC,CAAC,KACtE,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,mBAAmB,MAAM,WAAW,uCAAuC,EAAE,MAAM;CACnF,yBAAyB,MACvB,6DAA6D,EAAE,QAAQ,KAAK,IAAI,EAAE;CACpF,mBAAmB,MACjB,WACE,2BAA2B,EAAE,UAAU,gBACrC,EAAE,SAAS,KAAA,IAAY,KAAK,KAAK,EAAE,KAAK,MAE1C,EAAE,MACJ;CACF,qBAAqB,MAAM,WAAW,6BAA6B,EAAE,MAAM;CAC3E,uBAAuB;CACvB,eAAe,MAAM,WAAW,6BAA6B,EAAE,MAAM;AACvE,CAAC,CACH"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { i as McpError } from "./McpError-DWg9BlW5.mjs";
|
|
2
|
+
import { McpClient } from "./Client.mjs";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import * as CoreToolkit from "@effect-uai/core/Toolkit";
|
|
5
|
+
declare namespace Toolkit_d_exports {
|
|
6
|
+
export { McpToolkitOptions, mcpToolkit };
|
|
7
|
+
}
|
|
8
|
+
type McpToolkitOptions = {
|
|
9
|
+
/** Namespace every tool as `<prefix>__<name>`; recommended when composing servers. */
|
|
10
|
+
readonly prefix?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Build a `Toolkit` from the server's advertised tools. The list is snapshotted
|
|
14
|
+
* here; a tool that later vanishes surfaces through `Toolkit.run`'s graceful
|
|
15
|
+
* `unknown_tool` path.
|
|
16
|
+
*/
|
|
17
|
+
declare const mcpToolkit: (client: McpClient, options?: McpToolkitOptions) => Effect.Effect<CoreToolkit.Toolkit, McpError>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { McpToolkitOptions, mcpToolkit, Toolkit_d_exports as t };
|
|
20
|
+
//# sourceMappingURL=Toolkit.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toolkit.d.mts","names":[],"sources":["../src/Toolkit.ts"],"mappings":";;;;;;;KAiBY;;WAED;;;;;;;cA2EE,aAAU,QACb,WAAS,UACP,sBACT,OAAO,OAAO,YAAY,SAAS"}
|
package/dist/Toolkit.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { McpProtocolError } from "./McpError.mjs";
|
|
3
|
+
import { isTextContent } from "./internal/schema.mjs";
|
|
4
|
+
import { Effect, Option } from "effect";
|
|
5
|
+
import * as Tool from "@effect-uai/core/Tool";
|
|
6
|
+
import * as CoreToolkit from "@effect-uai/core/Toolkit";
|
|
7
|
+
//#region src/Toolkit.ts
|
|
8
|
+
/**
|
|
9
|
+
* Turn a connected MCP server into a core `Toolkit`, one `LocalTool` per MCP
|
|
10
|
+
* tool. Effectful because the tool list is fetched; composes with
|
|
11
|
+
* `Toolkit.compose` like any other kit.
|
|
12
|
+
*/
|
|
13
|
+
var Toolkit_exports = /* @__PURE__ */ __exportAll({ mcpToolkit: () => mcpToolkit });
|
|
14
|
+
/**
|
|
15
|
+
* The server owns validation, so the input schema is a passthrough: it carries
|
|
16
|
+
* the server's JSON Schema verbatim (the model sees the real thing) and
|
|
17
|
+
* accepts whatever the model produced. `strict: false` because server schemas
|
|
18
|
+
* are not written to OpenAI strict rules.
|
|
19
|
+
*/
|
|
20
|
+
const passthroughSchema = (tool) => ({ "~standard": {
|
|
21
|
+
version: 1,
|
|
22
|
+
vendor: "effect-uai-mcp",
|
|
23
|
+
validate: (value) => ({ value }),
|
|
24
|
+
jsonSchema: {
|
|
25
|
+
input: () => tool.inputSchema,
|
|
26
|
+
output: () => ({ type: "object" })
|
|
27
|
+
}
|
|
28
|
+
} });
|
|
29
|
+
const textOf = (block) => isTextContent(block) ? block.text : `[${block.type} content omitted]`;
|
|
30
|
+
/**
|
|
31
|
+
* Serialize a successful result for the model: `structuredContent` when the
|
|
32
|
+
* server provides it, otherwise the joined text blocks. Non-text blocks become
|
|
33
|
+
* a short placeholder until the loop grows a multimodal tool-output path.
|
|
34
|
+
*/
|
|
35
|
+
const outputOf = (result) => result.structuredContent ?? (result.content ?? []).map(textOf).join("\n");
|
|
36
|
+
/**
|
|
37
|
+
* Two failure channels, deliberately: `isError` is the server telling the
|
|
38
|
+
* model it went wrong, so it becomes a model-visible `ToolFailed`; an
|
|
39
|
+
* `input_required` result is a protocol capability gap the model cannot fix,
|
|
40
|
+
* so it stays a typed `McpError`.
|
|
41
|
+
*/
|
|
42
|
+
const interpret = (name, result) => {
|
|
43
|
+
if (result.isError === true) return Tool.fail(String(outputOf(result)), { kind: "tool_failed" });
|
|
44
|
+
if (result.resultType === "input_required") return Effect.fail(new McpProtocolError({
|
|
45
|
+
method: "tools/call",
|
|
46
|
+
reason: `${name} requires interactive input (MRTR), unsupported by this client`
|
|
47
|
+
}));
|
|
48
|
+
return Effect.succeed(outputOf(result));
|
|
49
|
+
};
|
|
50
|
+
const runTool = (client, name) => (input) => client.callTool(name, input).pipe(Effect.flatMap((result) => interpret(name, result)));
|
|
51
|
+
const toLocalTool = (client, info) => Tool.make({
|
|
52
|
+
name: info.name,
|
|
53
|
+
description: info.description ?? info.name,
|
|
54
|
+
inputSchema: passthroughSchema(info),
|
|
55
|
+
run: runTool(client, info.name),
|
|
56
|
+
strict: false
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Build a `Toolkit` from the server's advertised tools. The list is snapshotted
|
|
60
|
+
* here; a tool that later vanishes surfaces through `Toolkit.run`'s graceful
|
|
61
|
+
* `unknown_tool` path.
|
|
62
|
+
*/
|
|
63
|
+
const mcpToolkit = (client, options) => client.listTools.pipe(Effect.map((tools) => CoreToolkit.fromArray(tools.map((info) => toLocalTool(client, info)))), Effect.map((kit) => Option.fromNullishOr(options?.prefix).pipe(Option.match({
|
|
64
|
+
onNone: () => kit,
|
|
65
|
+
onSome: (prefix) => CoreToolkit.namespace(prefix, kit)
|
|
66
|
+
}))));
|
|
67
|
+
//#endregion
|
|
68
|
+
export { mcpToolkit, Toolkit_exports as t };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=Toolkit.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toolkit.mjs","names":[],"sources":["../src/Toolkit.ts"],"sourcesContent":["/**\n * Turn a connected MCP server into a core `Toolkit`, one `LocalTool` per MCP\n * tool. Effectful because the tool list is fetched; composes with\n * `Toolkit.compose` like any other kit.\n */\nimport { Effect, Option } from \"effect\"\nimport * as Tool from \"@effect-uai/core/Tool\"\nimport * as CoreToolkit from \"@effect-uai/core/Toolkit\"\nimport type { McpClient } from \"./Client.js\"\nimport { type McpError, McpProtocolError } from \"./McpError.js\"\nimport {\n type CallToolResult,\n type ContentBlock,\n isTextContent,\n type ToolInfo,\n} from \"./internal/schema.js\"\n\nexport type McpToolkitOptions = {\n /** Namespace every tool as `<prefix>__<name>`; recommended when composing servers. */\n readonly prefix?: string\n}\n\n/**\n * The server owns validation, so the input schema is a passthrough: it carries\n * the server's JSON Schema verbatim (the model sees the real thing) and\n * accepts whatever the model produced. `strict: false` because server schemas\n * are not written to OpenAI strict rules.\n */\nconst passthroughSchema = (tool: ToolInfo): Tool.ToolInputSchema<unknown> => ({\n \"~standard\": {\n version: 1,\n vendor: \"effect-uai-mcp\",\n validate: (value: unknown) => ({ value }),\n jsonSchema: {\n input: () => tool.inputSchema,\n output: () => ({ type: \"object\" }),\n },\n },\n})\n\nconst textOf = (block: ContentBlock): string =>\n isTextContent(block) ? block.text : `[${block.type} content omitted]`\n\n/**\n * Serialize a successful result for the model: `structuredContent` when the\n * server provides it, otherwise the joined text blocks. Non-text blocks become\n * a short placeholder until the loop grows a multimodal tool-output path.\n */\nconst outputOf = (result: CallToolResult): unknown =>\n result.structuredContent ?? (result.content ?? []).map(textOf).join(\"\\n\")\n\n/**\n * Two failure channels, deliberately: `isError` is the server telling the\n * model it went wrong, so it becomes a model-visible `ToolFailed`; an\n * `input_required` result is a protocol capability gap the model cannot fix,\n * so it stays a typed `McpError`.\n */\nconst interpret = (\n name: string,\n result: CallToolResult,\n): Effect.Effect<unknown, McpError | Tool.ToolFailed> => {\n if (result.isError === true) {\n return Tool.fail(String(outputOf(result)), { kind: \"tool_failed\" })\n }\n if (result.resultType === \"input_required\") {\n return Effect.fail(\n new McpProtocolError({\n method: \"tools/call\",\n reason: `${name} requires interactive input (MRTR), unsupported by this client`,\n }),\n )\n }\n return Effect.succeed(outputOf(result))\n}\n\nconst runTool =\n (client: McpClient, name: string) =>\n (input: unknown): Effect.Effect<unknown, McpError | Tool.ToolFailed> =>\n client.callTool(name, input).pipe(Effect.flatMap((result) => interpret(name, result)))\n\nconst toLocalTool = (client: McpClient, info: ToolInfo) =>\n Tool.make({\n name: info.name,\n description: info.description ?? info.name,\n inputSchema: passthroughSchema(info),\n run: runTool(client, info.name),\n strict: false,\n })\n\n/**\n * Build a `Toolkit` from the server's advertised tools. The list is snapshotted\n * here; a tool that later vanishes surfaces through `Toolkit.run`'s graceful\n * `unknown_tool` path.\n */\nexport const mcpToolkit = (\n client: McpClient,\n options?: McpToolkitOptions,\n): Effect.Effect<CoreToolkit.Toolkit, McpError> =>\n client.listTools.pipe(\n Effect.map((tools) => CoreToolkit.fromArray(tools.map((info) => toLocalTool(client, info)))),\n Effect.map((kit) =>\n Option.fromNullishOr(options?.prefix).pipe(\n Option.match({\n onNone: () => kit,\n onSome: (prefix) => CoreToolkit.namespace(prefix, kit),\n }),\n ),\n ),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,MAAM,qBAAqB,UAAmD,EAC5E,aAAa;CACX,SAAS;CACT,QAAQ;CACR,WAAW,WAAoB,EAAE,MAAM;CACvC,YAAY;EACV,aAAa,KAAK;EAClB,eAAe,EAAE,MAAM,SAAS;CAClC;AACF,EACF;AAEA,MAAM,UAAU,UACd,cAAc,KAAK,IAAI,MAAM,OAAO,IAAI,MAAM,KAAK;;;;;;AAOrD,MAAM,YAAY,WAChB,OAAO,sBAAsB,OAAO,WAAW,CAAC,EAAA,CAAG,IAAI,MAAM,CAAC,CAAC,KAAK,IAAI;;;;;;;AAQ1E,MAAM,aACJ,MACA,WACuD;CACvD,IAAI,OAAO,YAAY,MACrB,OAAO,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,MAAM,cAAc,CAAC;CAEpE,IAAI,OAAO,eAAe,kBACxB,OAAO,OAAO,KACZ,IAAI,iBAAiB;EACnB,QAAQ;EACR,QAAQ,GAAG,KAAK;CAClB,CAAC,CACH;CAEF,OAAO,OAAO,QAAQ,SAAS,MAAM,CAAC;AACxC;AAEA,MAAM,WACH,QAAmB,UACnB,UACC,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,SAAS,WAAW,UAAU,MAAM,MAAM,CAAC,CAAC;AAEzF,MAAM,eAAe,QAAmB,SACtC,KAAK,KAAK;CACR,MAAM,KAAK;CACX,aAAa,KAAK,eAAe,KAAK;CACtC,aAAa,kBAAkB,IAAI;CACnC,KAAK,QAAQ,QAAQ,KAAK,IAAI;CAC9B,QAAQ;AACV,CAAC;;;;;;AAOH,MAAa,cACX,QACA,YAEA,OAAO,UAAU,KACf,OAAO,KAAK,UAAU,YAAY,UAAU,MAAM,KAAK,SAAS,YAAY,QAAQ,IAAI,CAAC,CAAC,CAAC,GAC3F,OAAO,KAAK,QACV,OAAO,cAAc,SAAS,MAAM,CAAC,CAAC,KACpC,OAAO,MAAM;CACX,cAAc;CACd,SAAS,WAAW,YAAY,UAAU,QAAQ,GAAG;AACvD,CAAC,CACH,CACF,CACF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { t as McpAuthError } from "./McpError-DWg9BlW5.mjs";
|
|
2
|
+
import { Data, Effect, Option, Redacted } from "effect";
|
|
3
|
+
//#region src/internal/auth.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* An Effect yielding a bearer token. Any external OAuth, vault, or secret
|
|
6
|
+
* manager plugs in here. Read per request, so a producer minting expensive
|
|
7
|
+
* tokens caches internally (as `OAuth.clientCredentials` does).
|
|
8
|
+
*/
|
|
9
|
+
type TokenSource = Effect.Effect<Redacted.Redacted<string>, McpAuthError>;
|
|
10
|
+
type Auth = Data.TaggedEnum<{
|
|
11
|
+
/** A fixed token: the turnkey case for a token-gated server. */
|
|
12
|
+
Static: {
|
|
13
|
+
readonly token: Redacted.Redacted<string>;
|
|
14
|
+
readonly scheme?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Bring your own, re-read before every request. */
|
|
17
|
+
TokenSource: {
|
|
18
|
+
readonly token: TokenSource;
|
|
19
|
+
readonly scheme?: string;
|
|
20
|
+
};
|
|
21
|
+
/** Built-in OAuth 2.1, which produces a `TokenSource` and rides the same seam. */
|
|
22
|
+
OAuth: {
|
|
23
|
+
readonly source: TokenSource;
|
|
24
|
+
readonly scheme?: string;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
27
|
+
declare const Auth: {
|
|
28
|
+
readonly $is: <Tag extends "OAuth" | "Static" | "TokenSource">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
29
|
+
readonly _tag: "OAuth";
|
|
30
|
+
readonly source: TokenSource;
|
|
31
|
+
readonly scheme?: string;
|
|
32
|
+
}, {
|
|
33
|
+
readonly _tag: Tag;
|
|
34
|
+
}> | Extract<{
|
|
35
|
+
readonly _tag: "Static";
|
|
36
|
+
readonly token: Redacted.Redacted<string>;
|
|
37
|
+
readonly scheme?: string;
|
|
38
|
+
}, {
|
|
39
|
+
readonly _tag: Tag;
|
|
40
|
+
}> | Extract<{
|
|
41
|
+
readonly _tag: "TokenSource";
|
|
42
|
+
readonly token: TokenSource;
|
|
43
|
+
readonly scheme?: string;
|
|
44
|
+
}, {
|
|
45
|
+
readonly _tag: Tag;
|
|
46
|
+
}>;
|
|
47
|
+
readonly $match: {
|
|
48
|
+
<Cases extends { readonly [Tag in "OAuth" | "Static" | "TokenSource"]: (args: Extract<{
|
|
49
|
+
readonly _tag: "OAuth";
|
|
50
|
+
readonly source: TokenSource;
|
|
51
|
+
readonly scheme?: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly _tag: "Static";
|
|
54
|
+
readonly token: Redacted.Redacted<string>;
|
|
55
|
+
readonly scheme?: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly _tag: "TokenSource";
|
|
58
|
+
readonly token: TokenSource;
|
|
59
|
+
readonly scheme?: string;
|
|
60
|
+
}, {
|
|
61
|
+
readonly _tag: Tag;
|
|
62
|
+
}>) => any; }>(cases: Cases): (value: {
|
|
63
|
+
readonly _tag: "OAuth";
|
|
64
|
+
readonly source: TokenSource;
|
|
65
|
+
readonly scheme?: string;
|
|
66
|
+
} | {
|
|
67
|
+
readonly _tag: "Static";
|
|
68
|
+
readonly token: Redacted.Redacted<string>;
|
|
69
|
+
readonly scheme?: string;
|
|
70
|
+
} | {
|
|
71
|
+
readonly _tag: "TokenSource";
|
|
72
|
+
readonly token: TokenSource;
|
|
73
|
+
readonly scheme?: string;
|
|
74
|
+
}) => import("effect/Unify").Unify<ReturnType<Cases["OAuth" | "Static" | "TokenSource"]>>;
|
|
75
|
+
<Cases extends { readonly [Tag in "OAuth" | "Static" | "TokenSource"]: (args: Extract<{
|
|
76
|
+
readonly _tag: "OAuth";
|
|
77
|
+
readonly source: TokenSource;
|
|
78
|
+
readonly scheme?: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly _tag: "Static";
|
|
81
|
+
readonly token: Redacted.Redacted<string>;
|
|
82
|
+
readonly scheme?: string;
|
|
83
|
+
} | {
|
|
84
|
+
readonly _tag: "TokenSource";
|
|
85
|
+
readonly token: TokenSource;
|
|
86
|
+
readonly scheme?: string;
|
|
87
|
+
}, {
|
|
88
|
+
readonly _tag: Tag;
|
|
89
|
+
}>) => any; }>(value: {
|
|
90
|
+
readonly _tag: "OAuth";
|
|
91
|
+
readonly source: TokenSource;
|
|
92
|
+
readonly scheme?: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly _tag: "Static";
|
|
95
|
+
readonly token: Redacted.Redacted<string>;
|
|
96
|
+
readonly scheme?: string;
|
|
97
|
+
} | {
|
|
98
|
+
readonly _tag: "TokenSource";
|
|
99
|
+
readonly token: TokenSource;
|
|
100
|
+
readonly scheme?: string;
|
|
101
|
+
}, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["OAuth" | "Static" | "TokenSource"]>>;
|
|
102
|
+
};
|
|
103
|
+
readonly OAuth: Data.TaggedEnum.ConstructorFrom<{
|
|
104
|
+
readonly _tag: "OAuth";
|
|
105
|
+
readonly source: TokenSource;
|
|
106
|
+
readonly scheme?: string;
|
|
107
|
+
}, "_tag">;
|
|
108
|
+
readonly Static: Data.TaggedEnum.ConstructorFrom<{
|
|
109
|
+
readonly _tag: "Static";
|
|
110
|
+
readonly token: Redacted.Redacted<string>;
|
|
111
|
+
readonly scheme?: string;
|
|
112
|
+
}, "_tag">;
|
|
113
|
+
readonly TokenSource: Data.TaggedEnum.ConstructorFrom<{
|
|
114
|
+
readonly _tag: "TokenSource";
|
|
115
|
+
readonly token: TokenSource;
|
|
116
|
+
readonly scheme?: string;
|
|
117
|
+
}, "_tag">;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The `Authorization` header for one request, or none for a public server.
|
|
121
|
+
* `Redacted.value` is unwrapped only here.
|
|
122
|
+
*/
|
|
123
|
+
declare const authHeaders: (auth: Option.Option<Auth>) => Effect.Effect<Record<string, string>, McpAuthError>;
|
|
124
|
+
//#endregion
|
|
125
|
+
export { TokenSource as n, authHeaders as r, Auth as t };
|
|
126
|
+
//# sourceMappingURL=auth-ZDvsxpCo.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-ZDvsxpCo.d.mts","names":[],"sources":["../src/internal/auth.ts"],"mappings":";;;;;;;;KAaY,cAAc,OAAO,OAAO,SAAS,kBAAkB;KAEvD,OAAO,KAAK;;EAEtB;aAAmB,OAAO,SAAS;aAA2B;;;EAE9D;aAAwB,OAAO;aAAsB;;;EAErD;aAAkB,QAAQ;aAAsB;;;cAGrC;;;aAHe,QAAA;aAA+B;;;;;aAJ/B,OAAA,SAAS;aAAoC;;;;;aAExC,OAAA;aAA+B;;;;;;;eAEpC,QAAA;eAA+B;;;eAJ/B,OAAA,SAAS;eAAoC;;;eAExC,OAAA;eAA+B;;;;;;;;;;;;;;;;;;eAEpC,QAAA;eAA+B;;;eAJ/B,OAAA,SAAS;eAAoC;;;eAExC,OAAA;eAA+B;;;;;;;;;;;;;;;;;;;aAEpC,QAAA;aAA+B;;;;aAJ/B,OAAA,SAAS;aAAoC;;;;aAExC,OAAA;aAA+B;;;;;;;cAiBnD,cAAW,MAChB,OAAO,OAAO,UACnB,OAAO,OAAO,wBAAwB"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as McpError_d_exports } from "./McpError-DWg9BlW5.mjs";
|
|
2
|
+
import { t as Client_d_exports } from "./Client.mjs";
|
|
3
|
+
import { t as Toolkit_d_exports } from "./Toolkit.mjs";
|
|
4
|
+
export { Client_d_exports as Client, McpError_d_exports as McpError, Toolkit_d_exports as Toolkit };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Data, Effect, Option, Redacted } from "effect";
|
|
2
|
+
//#region src/internal/auth.ts
|
|
3
|
+
/**
|
|
4
|
+
* Auth resolution for the HTTP transport. Era-blind: the stateless and
|
|
5
|
+
* handshake protocols both ride an OAuth 2.1 resource server, and neither
|
|
6
|
+
* knows this file exists. Tokens stay `Redacted` until the wire boundary.
|
|
7
|
+
*/
|
|
8
|
+
const Auth = Data.taggedEnum();
|
|
9
|
+
const tokenOf = Auth.$match({
|
|
10
|
+
Static: ({ token }) => Effect.succeed(token),
|
|
11
|
+
TokenSource: ({ token }) => token,
|
|
12
|
+
OAuth: ({ source }) => source
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* The `Authorization` header for one request, or none for a public server.
|
|
16
|
+
* `Redacted.value` is unwrapped only here.
|
|
17
|
+
*/
|
|
18
|
+
const authHeaders = (auth) => Option.match(auth, {
|
|
19
|
+
onNone: () => Effect.succeed({}),
|
|
20
|
+
onSome: (a) => tokenOf(a).pipe(Effect.map((token) => ({ authorization: `${a.scheme ?? "Bearer"} ${Redacted.value(token)}` })))
|
|
21
|
+
});
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Auth, authHeaders };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../../src/internal/auth.ts"],"sourcesContent":["/**\n * Auth resolution for the HTTP transport. Era-blind: the stateless and\n * handshake protocols both ride an OAuth 2.1 resource server, and neither\n * knows this file exists. Tokens stay `Redacted` until the wire boundary.\n */\nimport { Data, Effect, Option, Redacted } from \"effect\"\nimport type { McpAuthError } from \"../McpError.js\"\n\n/**\n * An Effect yielding a bearer token. Any external OAuth, vault, or secret\n * manager plugs in here. Read per request, so a producer minting expensive\n * tokens caches internally (as `OAuth.clientCredentials` does).\n */\nexport type TokenSource = Effect.Effect<Redacted.Redacted<string>, McpAuthError>\n\nexport type Auth = Data.TaggedEnum<{\n /** A fixed token: the turnkey case for a token-gated server. */\n Static: { readonly token: Redacted.Redacted<string>; readonly scheme?: string }\n /** Bring your own, re-read before every request. */\n TokenSource: { readonly token: TokenSource; readonly scheme?: string }\n /** Built-in OAuth 2.1, which produces a `TokenSource` and rides the same seam. */\n OAuth: { readonly source: TokenSource; readonly scheme?: string }\n}>\n\nexport const Auth = Data.taggedEnum<Auth>()\n\nconst tokenOf: (auth: Auth) => TokenSource = Auth.$match({\n Static: ({ token }) => Effect.succeed(token),\n TokenSource: ({ token }) => token,\n OAuth: ({ source }) => source,\n})\n\n/**\n * The `Authorization` header for one request, or none for a public server.\n * `Redacted.value` is unwrapped only here.\n */\nexport const authHeaders = (\n auth: Option.Option<Auth>,\n): Effect.Effect<Record<string, string>, McpAuthError> =>\n Option.match(auth, {\n onNone: () => Effect.succeed({}),\n onSome: (a) =>\n tokenOf(a).pipe(\n Effect.map((token) => ({\n authorization: `${a.scheme ?? \"Bearer\"} ${Redacted.value(token)}`,\n })),\n ),\n })\n"],"mappings":";;;;;;;AAwBA,MAAa,OAAO,KAAK,WAAiB;AAE1C,MAAM,UAAuC,KAAK,OAAO;CACvD,SAAS,EAAE,YAAY,OAAO,QAAQ,KAAK;CAC3C,cAAc,EAAE,YAAY;CAC5B,QAAQ,EAAE,aAAa;AACzB,CAAC;;;;;AAMD,MAAa,eACX,SAEA,OAAO,MAAM,MAAM;CACjB,cAAc,OAAO,QAAQ,CAAC,CAAC;CAC/B,SAAS,MACP,QAAQ,CAAC,CAAC,CAAC,KACT,OAAO,KAAK,WAAW,EACrB,eAAe,GAAG,EAAE,UAAU,SAAS,GAAG,SAAS,MAAM,KAAK,IAChE,EAAE,CACJ;AACJ,CAAC"}
|