@smthrs/mcp 0.0.0-stage → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -2
  4. package/dist/cjs/Diagnostics.d.ts +46 -0
  5. package/dist/cjs/Diagnostics.d.ts.map +1 -0
  6. package/dist/cjs/Diagnostics.js +29 -0
  7. package/dist/cjs/Diagnostics.js.map +7 -0
  8. package/dist/cjs/McpClient.d.ts +304 -0
  9. package/dist/cjs/McpClient.d.ts.map +1 -0
  10. package/dist/cjs/McpClient.js +622 -0
  11. package/dist/cjs/McpClient.js.map +7 -0
  12. package/dist/cjs/McpError.d.ts +44 -0
  13. package/dist/cjs/McpError.d.ts.map +1 -0
  14. package/dist/cjs/McpError.js +41 -0
  15. package/dist/cjs/McpError.js.map +7 -0
  16. package/dist/cjs/McpFlows.d.ts +112 -0
  17. package/dist/cjs/McpFlows.d.ts.map +1 -0
  18. package/dist/cjs/McpFlows.js +127 -0
  19. package/dist/cjs/McpFlows.js.map +7 -0
  20. package/dist/cjs/index.d.ts +39 -0
  21. package/dist/cjs/index.d.ts.map +1 -0
  22. package/dist/cjs/index.js +41 -0
  23. package/dist/cjs/index.js.map +7 -0
  24. package/dist/cjs/internal/DiagnosticReporter.d.ts +17 -0
  25. package/dist/cjs/internal/DiagnosticReporter.d.ts.map +1 -0
  26. package/dist/cjs/internal/DiagnosticReporter.js +56 -0
  27. package/dist/cjs/internal/DiagnosticReporter.js.map +7 -0
  28. package/dist/cjs/internal/HttpTransport.d.ts +67 -0
  29. package/dist/cjs/internal/HttpTransport.d.ts.map +1 -0
  30. package/dist/cjs/internal/HttpTransport.js +298 -0
  31. package/dist/cjs/internal/HttpTransport.js.map +7 -0
  32. package/dist/cjs/internal/JsonLimits.d.ts +29 -0
  33. package/dist/cjs/internal/JsonLimits.d.ts.map +1 -0
  34. package/dist/cjs/internal/JsonLimits.js +51 -0
  35. package/dist/cjs/internal/JsonLimits.js.map +7 -0
  36. package/dist/cjs/internal/Limits.d.ts +36 -0
  37. package/dist/cjs/internal/Limits.d.ts.map +1 -0
  38. package/dist/cjs/internal/Limits.js +34 -0
  39. package/dist/cjs/internal/Limits.js.map +7 -0
  40. package/dist/cjs/internal/Rpc.d.ts +141 -0
  41. package/dist/cjs/internal/Rpc.d.ts.map +1 -0
  42. package/dist/cjs/internal/Rpc.js +92 -0
  43. package/dist/cjs/internal/Rpc.js.map +7 -0
  44. package/dist/cjs/internal/StdioTransport.d.ts +78 -0
  45. package/dist/cjs/internal/StdioTransport.d.ts.map +1 -0
  46. package/dist/cjs/internal/StdioTransport.js +310 -0
  47. package/dist/cjs/internal/StdioTransport.js.map +7 -0
  48. package/dist/cjs/internal/Transport.d.ts +87 -0
  49. package/dist/cjs/internal/Transport.d.ts.map +1 -0
  50. package/dist/cjs/internal/Transport.js +116 -0
  51. package/dist/cjs/internal/Transport.js.map +7 -0
  52. package/dist/cjs/package.json +1 -0
  53. package/dist/esm/Diagnostics.d.ts +46 -0
  54. package/dist/esm/Diagnostics.d.ts.map +1 -0
  55. package/dist/esm/Diagnostics.js +26 -0
  56. package/dist/esm/Diagnostics.js.map +1 -0
  57. package/dist/esm/McpClient.d.ts +304 -0
  58. package/dist/esm/McpClient.d.ts.map +1 -0
  59. package/dist/esm/McpClient.js +671 -0
  60. package/dist/esm/McpClient.js.map +1 -0
  61. package/dist/esm/McpError.d.ts +44 -0
  62. package/dist/esm/McpError.d.ts.map +1 -0
  63. package/dist/esm/McpError.js +43 -0
  64. package/dist/esm/McpError.js.map +1 -0
  65. package/dist/esm/McpFlows.d.ts +112 -0
  66. package/dist/esm/McpFlows.d.ts.map +1 -0
  67. package/dist/esm/McpFlows.js +167 -0
  68. package/dist/esm/McpFlows.js.map +1 -0
  69. package/dist/esm/index.d.ts +39 -0
  70. package/dist/esm/index.d.ts.map +1 -0
  71. package/dist/esm/index.js +39 -0
  72. package/dist/esm/index.js.map +1 -0
  73. package/dist/esm/internal/DiagnosticReporter.d.ts +17 -0
  74. package/dist/esm/internal/DiagnosticReporter.d.ts.map +1 -0
  75. package/dist/esm/internal/DiagnosticReporter.js +44 -0
  76. package/dist/esm/internal/DiagnosticReporter.js.map +1 -0
  77. package/dist/esm/internal/HttpTransport.d.ts +67 -0
  78. package/dist/esm/internal/HttpTransport.d.ts.map +1 -0
  79. package/dist/esm/internal/HttpTransport.js +266 -0
  80. package/dist/esm/internal/HttpTransport.js.map +1 -0
  81. package/dist/esm/internal/JsonLimits.d.ts +29 -0
  82. package/dist/esm/internal/JsonLimits.d.ts.map +1 -0
  83. package/dist/esm/internal/JsonLimits.js +55 -0
  84. package/dist/esm/internal/JsonLimits.js.map +1 -0
  85. package/dist/esm/internal/Limits.d.ts +36 -0
  86. package/dist/esm/internal/Limits.d.ts.map +1 -0
  87. package/dist/esm/internal/Limits.js +41 -0
  88. package/dist/esm/internal/Limits.js.map +1 -0
  89. package/dist/esm/internal/Rpc.d.ts +141 -0
  90. package/dist/esm/internal/Rpc.d.ts.map +1 -0
  91. package/dist/esm/internal/Rpc.js +129 -0
  92. package/dist/esm/internal/Rpc.js.map +1 -0
  93. package/dist/esm/internal/StdioTransport.d.ts +78 -0
  94. package/dist/esm/internal/StdioTransport.d.ts.map +1 -0
  95. package/dist/esm/internal/StdioTransport.js +332 -0
  96. package/dist/esm/internal/StdioTransport.js.map +1 -0
  97. package/dist/esm/internal/Transport.d.ts +87 -0
  98. package/dist/esm/internal/Transport.d.ts.map +1 -0
  99. package/dist/esm/internal/Transport.js +146 -0
  100. package/dist/esm/internal/Transport.js.map +1 -0
  101. package/docs/README.md +139 -0
  102. package/docs/api.md +469 -0
  103. package/docs/concepts/the-session.md +135 -0
  104. package/docs/concepts/tools-as-flows.md +116 -0
  105. package/docs/guides/bound-an-untrusted-server.md +158 -0
  106. package/docs/guides/configure-servers-for-the-cli.md +167 -0
  107. package/docs/guides/connect-a-server.md +161 -0
  108. package/docs/guides/grant-authority-to-mcp-tools.md +130 -0
  109. package/docs/guides/handle-a-failed-tool-call.md +125 -0
  110. package/docs/guides/select-the-tools-a-run-sees.md +92 -0
  111. package/docs/guides/testing.md +132 -0
  112. package/docs/guides/validate-structured-output.md +103 -0
  113. package/docs/installation.md +117 -0
  114. package/docs/quickstart.md +200 -0
  115. package/docs/troubleshooting.md +316 -0
  116. package/package.json +157 -3
  117. package/src/Diagnostics.ts +47 -0
  118. package/src/McpClient.ts +985 -0
  119. package/src/McpError.ts +52 -0
  120. package/src/McpFlows.ts +210 -0
  121. package/src/index.ts +42 -0
  122. package/src/internal/DiagnosticReporter.ts +47 -0
  123. package/src/internal/HttpTransport.ts +400 -0
  124. package/src/internal/JsonLimits.ts +53 -0
  125. package/src/internal/Limits.ts +48 -0
  126. package/src/internal/Rpc.ts +219 -0
  127. package/src/internal/StdioTransport.ts +491 -0
  128. package/src/internal/Transport.ts +178 -0
@@ -0,0 +1,622 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var McpClient_exports = {};
30
+ __export(McpClient_exports, {
31
+ ConnectOptionsSchema: () => ConnectOptionsSchema,
32
+ HttpConnectOptionsSchema: () => HttpConnectOptionsSchema,
33
+ clientInfo: () => clientInfo,
34
+ connect: () => connect,
35
+ defaultHandshakeTimeoutMs: () => defaultHandshakeTimeoutMs,
36
+ defaultMaxCatalogPages: () => defaultMaxCatalogPages,
37
+ defaultMaxFrameBytes: () => defaultMaxFrameBytes,
38
+ defaultMaxOutboundFrameBytes: () => defaultMaxOutboundFrameBytes,
39
+ defaultMaxStderrBytes: () => defaultMaxStderrBytes,
40
+ defaultMaxToolDocumentBytes: () => defaultMaxToolDocumentBytes,
41
+ defaultMaxToolNameBytes: () => defaultMaxToolNameBytes,
42
+ defaultMaxTools: () => defaultMaxTools,
43
+ defaultQueueCapacity: () => defaultQueueCapacity,
44
+ defaultRequestTimeoutMs: () => defaultRequestTimeoutMs,
45
+ maxJsonDepth: () => maxJsonDepth,
46
+ supportedProtocolVersions: () => supportedProtocolVersions
47
+ });
48
+ module.exports = __toCommonJS(McpClient_exports);
49
+ var import_Record = require("@smthrs/canonical/Record");
50
+ var import_effect = require("effect");
51
+ var DiagnosticReporter = __toESM(require("./internal/DiagnosticReporter.js"), 1);
52
+ var HttpTransport = __toESM(require("./internal/HttpTransport.js"), 1);
53
+ var JsonLimits = __toESM(require("./internal/JsonLimits.js"), 1);
54
+ var Limits = __toESM(require("./internal/Limits.js"), 1);
55
+ var StdioTransport = __toESM(require("./internal/StdioTransport.js"), 1);
56
+ var Transport = __toESM(require("./internal/Transport.js"), 1);
57
+ var import_McpError = require("./McpError.js");
58
+ const isHttp = (options) => options.url !== void 0;
59
+ const PositiveInteger = import_effect.Schema.Int.check(import_effect.Schema.isGreaterThan(0));
60
+ const ConnectOptionsSchema = import_effect.Schema.Struct({
61
+ server: import_effect.Schema.NonEmptyString,
62
+ command: import_effect.Schema.NonEmptyString,
63
+ args: import_effect.Schema.Array(import_effect.Schema.String),
64
+ cwd: import_effect.Schema.optional(import_effect.Schema.NonEmptyString),
65
+ env: import_effect.Schema.optional(import_effect.Schema.Record(import_effect.Schema.String, import_effect.Schema.String)),
66
+ handshakeTimeoutMs: import_effect.Schema.optional(PositiveInteger),
67
+ requestTimeoutMs: import_effect.Schema.optional(PositiveInteger),
68
+ queueCapacity: import_effect.Schema.optional(PositiveInteger),
69
+ maxFrameBytes: import_effect.Schema.optional(PositiveInteger),
70
+ maxOutboundFrameBytes: import_effect.Schema.optional(PositiveInteger),
71
+ maxStderrBytes: import_effect.Schema.optional(PositiveInteger),
72
+ maxTools: import_effect.Schema.optional(PositiveInteger),
73
+ maxToolNameBytes: import_effect.Schema.optional(PositiveInteger),
74
+ maxToolDocumentBytes: import_effect.Schema.optional(PositiveInteger),
75
+ maxCatalogPages: import_effect.Schema.optional(PositiveInteger)
76
+ });
77
+ const isEndpoint = (url) => {
78
+ if (!URL.canParse(url)) return false;
79
+ const parsed = new URL(url);
80
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "";
81
+ };
82
+ const HttpConnectOptionsSchema = import_effect.Schema.Struct({
83
+ server: import_effect.Schema.NonEmptyString,
84
+ url: import_effect.Schema.String.check(import_effect.Schema.makeFilter(isEndpoint, { expected: "an http: or https: URL without credentials" })),
85
+ bearerTokenEnv: import_effect.Schema.optionalKey(import_effect.Schema.String.check(import_effect.Schema.isPattern(/^[A-Za-z_][A-Za-z0-9_]*$/))),
86
+ handshakeTimeoutMs: import_effect.Schema.optional(PositiveInteger),
87
+ requestTimeoutMs: import_effect.Schema.optional(PositiveInteger),
88
+ maxFrameBytes: import_effect.Schema.optional(PositiveInteger),
89
+ maxOutboundFrameBytes: import_effect.Schema.optional(PositiveInteger),
90
+ maxTools: import_effect.Schema.optional(PositiveInteger),
91
+ maxToolNameBytes: import_effect.Schema.optional(PositiveInteger),
92
+ maxToolDocumentBytes: import_effect.Schema.optional(PositiveInteger),
93
+ maxCatalogPages: import_effect.Schema.optional(PositiveInteger)
94
+ });
95
+ const clientInfo = Object.freeze({
96
+ name: "smithers",
97
+ version: "1.0.0-rc.3"
98
+ });
99
+ const supportedProtocolVersions = Object.freeze([
100
+ "2025-06-18",
101
+ "2025-03-26",
102
+ "2024-11-05"
103
+ ]);
104
+ const defaultHandshakeTimeoutMs = 1e4;
105
+ const defaultRequestTimeoutMs = Transport.defaultRequestTimeoutMs;
106
+ const defaultQueueCapacity = StdioTransport.defaultQueueCapacity;
107
+ const defaultMaxFrameBytes = Transport.defaultMaxFrameBytes;
108
+ const defaultMaxOutboundFrameBytes = Transport.defaultMaxOutboundFrameBytes;
109
+ const defaultMaxStderrBytes = StdioTransport.defaultMaxStderrBytes;
110
+ const defaultMaxTools = 256;
111
+ const defaultMaxToolNameBytes = 128;
112
+ const defaultMaxToolDocumentBytes = 65536;
113
+ const defaultMaxCatalogPages = 32;
114
+ const maxJsonDepth = JsonLimits.maxDepth;
115
+ const invalidResponse = (server, message) => new import_McpError.McpError({ code: "invalid_response", message, server });
116
+ const asInitialize = (server, result) => {
117
+ if (!(0, import_Record.isRecord)(result)) {
118
+ return import_effect.Result.fail(Limits.protocolError(
119
+ server,
120
+ `MCP server "${server}" returned a malformed initialize result: result is not an object`
121
+ ));
122
+ }
123
+ if (typeof result.protocolVersion !== "string") {
124
+ return import_effect.Result.fail(Limits.protocolError(
125
+ server,
126
+ `MCP server "${server}" returned a malformed initialize result: protocolVersion is not a string`
127
+ ));
128
+ }
129
+ if (!supportedProtocolVersions.includes(result.protocolVersion)) {
130
+ return import_effect.Result.fail(Limits.protocolError(
131
+ server,
132
+ `MCP server "${server}" speaks an unsupported protocol version; this client speaks ${supportedProtocolVersions.join(", ")}`
133
+ ));
134
+ }
135
+ if (!(0, import_Record.isRecord)(result.capabilities)) {
136
+ return import_effect.Result.fail(Limits.protocolError(
137
+ server,
138
+ `MCP server "${server}" returned a malformed initialize result: capabilities is not an object`
139
+ ));
140
+ }
141
+ if (!Object.hasOwn(result.capabilities, "tools") || !(0, import_Record.isRecord)(result.capabilities.tools)) {
142
+ return import_effect.Result.fail(Limits.protocolError(
143
+ server,
144
+ `MCP server "${server}" does not serve tools: its initialize result declares no tools capability`
145
+ ));
146
+ }
147
+ return import_effect.Result.succeed(void 0);
148
+ };
149
+ const nameEncoder = new TextEncoder();
150
+ const forbiddenToolNameCharacter = /[/\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
151
+ const isForbiddenToolName = (name) => name === "." || name === ".." || forbiddenToolNameCharacter.test(name);
152
+ const asToolPage = (server, result, limits, seen, described) => {
153
+ const tools = (0, import_Record.isRecord)(result) ? result.tools : void 0;
154
+ if (!Array.isArray(tools)) {
155
+ return import_effect.Result.fail(invalidResponse(
156
+ server,
157
+ `MCP server "${server}" returned a tools/list result with no tools array`
158
+ ));
159
+ }
160
+ for (const [index, tool] of tools.entries()) {
161
+ if (described.length >= limits.maxTools) {
162
+ return import_effect.Result.fail(invalidResponse(
163
+ server,
164
+ `MCP server "${server}" returned more than ${limits.maxTools} tools`
165
+ ));
166
+ }
167
+ if (!(0, import_Record.isRecord)(tool)) {
168
+ return import_effect.Result.fail(invalidResponse(
169
+ server,
170
+ `MCP server "${server}" returned tools[${index}], which is not an object`
171
+ ));
172
+ }
173
+ const record = tool;
174
+ if (typeof record.name !== "string" || record.name === "") {
175
+ return import_effect.Result.fail(invalidResponse(server, `MCP server "${server}" returned tools[${index}] with no name`));
176
+ }
177
+ if (nameEncoder.encode(record.name).byteLength > limits.maxToolNameBytes) {
178
+ return import_effect.Result.fail(invalidResponse(
179
+ server,
180
+ `MCP server "${server}" returned a tool name longer than ${limits.maxToolNameBytes} bytes`
181
+ ));
182
+ }
183
+ if (isForbiddenToolName(record.name)) {
184
+ return import_effect.Result.fail(invalidResponse(
185
+ server,
186
+ `MCP server "${server}" returned a tool name that is "." or "..", or contains "/" or an invisible or control character`
187
+ ));
188
+ }
189
+ if (seen.has(record.name)) {
190
+ return import_effect.Result.fail(invalidResponse(
191
+ server,
192
+ `MCP server "${server}" returned a duplicate tool name at catalog index ${index}`
193
+ ));
194
+ }
195
+ if (!(0, import_Record.isRecord)(record.inputSchema) || record.inputSchema.type !== "object") {
196
+ return import_effect.Result.fail(invalidResponse(
197
+ server,
198
+ `MCP server "${server}" returned a tool whose inputSchema is not a JSON Schema object of type "object"`
199
+ ));
200
+ }
201
+ const description = typeof record.description === "string" ? record.description : void 0;
202
+ const documentBytes = nameEncoder.encode(description ?? "").byteLength + nameEncoder.encode(JSON.stringify(record.inputSchema)).byteLength;
203
+ if (documentBytes > limits.maxToolDocumentBytes) {
204
+ return import_effect.Result.fail(invalidResponse(
205
+ server,
206
+ `MCP server "${server}" returned a tool description and inputSchema longer than ${limits.maxToolDocumentBytes} bytes`
207
+ ));
208
+ }
209
+ let outputSchema;
210
+ if (Object.hasOwn(record, "outputSchema")) {
211
+ if (!(0, import_Record.isRecord)(record.outputSchema)) {
212
+ return import_effect.Result.fail(invalidResponse(
213
+ server,
214
+ `MCP server "${server}" returned a tool whose outputSchema is not a JSON object`
215
+ ));
216
+ }
217
+ outputSchema = record.outputSchema;
218
+ }
219
+ seen.add(record.name);
220
+ described.push({
221
+ name: record.name,
222
+ description,
223
+ inputSchema: record.inputSchema,
224
+ outputSchema
225
+ });
226
+ }
227
+ const nextCursor = (0, import_Record.isRecord)(result) && Object.hasOwn(result, "nextCursor") ? result.nextCursor : void 0;
228
+ if (nextCursor === void 0) return import_effect.Result.succeed({ nextCursor: void 0 });
229
+ if (typeof nextCursor !== "string" || nextCursor === "") {
230
+ return import_effect.Result.fail(invalidResponse(
231
+ server,
232
+ `MCP server "${server}" returned a tools/list cursor that is not a non-empty string`
233
+ ));
234
+ }
235
+ return import_effect.Result.succeed({ nextCursor });
236
+ };
237
+ const outputTypes = {
238
+ null: import_effect.Schema.Null,
239
+ boolean: import_effect.Schema.Boolean,
240
+ object: import_effect.Schema.declare(import_Record.isRecord),
241
+ array: import_effect.Schema.declare(Array.isArray),
242
+ number: import_effect.Schema.Number,
243
+ string: import_effect.Schema.String,
244
+ integer: import_effect.Schema.Number.check(import_effect.Schema.makeFilter(Number.isInteger))
245
+ };
246
+ const runJsonWork = (work) => import_effect.Effect.gen(function* () {
247
+ while (true) {
248
+ for (let steps = 0; steps < 1024; steps += 1) {
249
+ const next = work.next();
250
+ if (next.done) return next.value;
251
+ }
252
+ yield* import_effect.Effect.yieldNow;
253
+ }
254
+ });
255
+ const enumKey = function* (value) {
256
+ yield;
257
+ if (Array.isArray(value)) {
258
+ const items = [];
259
+ for (const item of value) items.push(yield* enumKey(item));
260
+ return `[${items.join(",")}]`;
261
+ }
262
+ if ((0, import_Record.isRecord)(value)) {
263
+ const members = [];
264
+ for (const key of Object.keys(value).sort()) {
265
+ members.push(`${JSON.stringify(key)}:${yield* enumKey(value[key])}`);
266
+ }
267
+ return `{${members.join(",")}}`;
268
+ }
269
+ return JSON.stringify(value);
270
+ };
271
+ const compileOutputSchema = function* (schema) {
272
+ yield;
273
+ const types = /* @__PURE__ */ new Set();
274
+ for (const candidate of Array.isArray(schema.type) ? schema.type : [schema.type]) {
275
+ yield;
276
+ if (typeof candidate === "string" && Object.hasOwn(outputTypes, candidate)) {
277
+ types.add(candidate);
278
+ }
279
+ }
280
+ const accepts = import_effect.Schema.is(
281
+ types.size === 0 ? import_effect.Schema.Unknown : import_effect.Schema.Union([...types].map((type) => outputTypes[type]))
282
+ );
283
+ const typeReason = `expected ${[...types].join(" or ")}`;
284
+ let enumIndex;
285
+ if (Array.isArray(schema.enum)) {
286
+ enumIndex = /* @__PURE__ */ new Set();
287
+ for (const member of schema.enum) enumIndex.add(yield* enumKey(member));
288
+ }
289
+ const required = [];
290
+ if (Array.isArray(schema.required)) {
291
+ for (const key of schema.required) {
292
+ yield;
293
+ if (typeof key === "string") required.push(key);
294
+ }
295
+ }
296
+ const properties = [];
297
+ if ((0, import_Record.isRecord)(schema.properties)) {
298
+ for (const [key, property] of Object.entries(schema.properties)) {
299
+ yield;
300
+ if ((0, import_Record.isRecord)(property)) properties.push([key, yield* compileOutputSchema(property)]);
301
+ }
302
+ }
303
+ const items = (0, import_Record.isRecord)(schema.items) ? yield* compileOutputSchema(schema.items) : void 0;
304
+ return function* (value, path) {
305
+ yield;
306
+ if (enumIndex !== void 0 && !enumIndex.has(yield* enumKey(value))) {
307
+ return { path, reason: "expected a declared enum value" };
308
+ }
309
+ if (!accepts(value)) return { path, reason: typeReason };
310
+ if ((0, import_Record.isRecord)(value)) {
311
+ for (const key of required) {
312
+ yield;
313
+ if (!Object.hasOwn(value, key)) return { path: `${path}.${key}`, reason: "required property is missing" };
314
+ }
315
+ for (const [key, validate] of properties) {
316
+ yield;
317
+ if (!Object.hasOwn(value, key)) continue;
318
+ const issue = yield* validate(value[key], `${path}.${key}`);
319
+ if (issue !== void 0) return issue;
320
+ }
321
+ }
322
+ if (Array.isArray(value) && items !== void 0) {
323
+ for (const [index, item] of value.entries()) {
324
+ const issue = yield* items(item, `${path}[${index}]`);
325
+ if (issue !== void 0) return issue;
326
+ }
327
+ }
328
+ return void 0;
329
+ };
330
+ };
331
+ const asToolResult = function* (server, result, outputSchema, diagnostic, validate) {
332
+ if (!(0, import_Record.isRecord)(result)) {
333
+ return import_effect.Result.fail(invalidResponse(
334
+ server,
335
+ `MCP server "${server}" returned a tools/call result that is not an object`
336
+ ));
337
+ }
338
+ const hasContent = Object.hasOwn(result, "content");
339
+ const hasStructuredContent = Object.hasOwn(result, "structuredContent");
340
+ if (!hasContent && !hasStructuredContent) {
341
+ return import_effect.Result.fail(invalidResponse(
342
+ server,
343
+ `MCP server "${server}" returned a tools/call result with no content array`
344
+ ));
345
+ }
346
+ if (hasContent && !Array.isArray(result.content)) {
347
+ return import_effect.Result.fail(invalidResponse(
348
+ server,
349
+ `MCP server "${server}" returned a tools/call result with no content array`
350
+ ));
351
+ }
352
+ const content = [];
353
+ const blocks = hasContent ? result.content : [];
354
+ for (const [index, block] of blocks.entries()) {
355
+ yield;
356
+ if (!(0, import_Record.isRecord)(block)) {
357
+ return import_effect.Result.fail(invalidResponse(
358
+ server,
359
+ `MCP server "${server}" returned a tools/call result whose content[${index}] is not an object`
360
+ ));
361
+ }
362
+ content.push(block);
363
+ }
364
+ if (Object.hasOwn(result, "isError") && typeof result.isError !== "boolean") {
365
+ return import_effect.Result.fail(invalidResponse(
366
+ server,
367
+ `MCP server "${server}" returned a tools/call result whose isError is not a boolean`
368
+ ));
369
+ }
370
+ let structuredContent;
371
+ if (hasStructuredContent) {
372
+ if (!(0, import_Record.isRecord)(result.structuredContent)) {
373
+ return import_effect.Result.fail(invalidResponse(
374
+ server,
375
+ `MCP server "${server}" returned a tools/call result whose structuredContent is not a JSON object`
376
+ ));
377
+ }
378
+ structuredContent = result.structuredContent;
379
+ if (validate !== void 0) {
380
+ const issue = yield* validate(structuredContent, "structuredContent");
381
+ if (issue !== void 0) {
382
+ diagnostic("invalid-response", { issue, outputSchema });
383
+ return import_effect.Result.fail(invalidResponse(
384
+ server,
385
+ `MCP server "${server}" returned structuredContent that its own outputSchema rejects: ${issue.reason}; property path withheld`
386
+ ));
387
+ }
388
+ }
389
+ }
390
+ return import_effect.Result.succeed({
391
+ content,
392
+ isError: result.isError === true,
393
+ structuredContent
394
+ });
395
+ };
396
+ const renderJsonPath = (path) => typeof path === "string" ? path : `${renderJsonPath(path.parent)}${typeof path.key === "number" ? `[${path.key}]` : `.${path.key}`}`;
397
+ const jsonFailure = (path, reason) => import_effect.Result.fail({ path: renderJsonPath(path), reason });
398
+ const reflect = (thunk) => {
399
+ try {
400
+ return import_effect.Result.succeed(thunk());
401
+ } catch {
402
+ return import_effect.Result.fail("a property that threw when read");
403
+ }
404
+ };
405
+ const ownDescriptors = (object) => reflect(() => Object.getOwnPropertyDescriptors(object));
406
+ const isAccessor = (descriptor) => Object.hasOwn(descriptor, "get") || Object.hasOwn(descriptor, "set");
407
+ const spendJson = (budget, bytes) => {
408
+ budget.remaining -= bytes;
409
+ return budget.remaining >= 0;
410
+ };
411
+ const snapshotJson = (value, path, ancestors, budget) => {
412
+ const bytes = typeof value === "string" ? value.length + 2 : typeof value === "number" || typeof value === "boolean" ? String(value).length : value === null ? 4 : 2;
413
+ if (!spendJson(budget, bytes)) return jsonFailure(path, "JSON expansion exceeds the outbound frame budget");
414
+ if (value === null) return import_effect.Result.succeed(null);
415
+ if (typeof value === "boolean" || typeof value === "string") return import_effect.Result.succeed(value);
416
+ if (typeof value === "number") {
417
+ return Number.isFinite(value) ? import_effect.Result.succeed(value) : jsonFailure(path, "a non-finite number");
418
+ }
419
+ if (typeof value === "undefined") return jsonFailure(path, "undefined");
420
+ if (typeof value === "bigint") return jsonFailure(path, "a bigint");
421
+ if (typeof value === "function") return jsonFailure(path, "a function");
422
+ if (typeof value === "symbol") return jsonFailure(path, "a symbol");
423
+ if (ancestors.has(value)) return jsonFailure(path, "a cyclic reference");
424
+ if (ancestors.size + 3 > maxJsonDepth) return jsonFailure(path, `JSON nesting exceeds ${maxJsonDepth} containers`);
425
+ const array = reflect(() => Array.isArray(value));
426
+ if (import_effect.Result.isFailure(array)) return jsonFailure(path, array.failure);
427
+ if (array.success) {
428
+ const length = reflect(() => value.length);
429
+ if (import_effect.Result.isFailure(length)) return jsonFailure(path, length.failure);
430
+ if (!Number.isSafeInteger(length.success) || length.success < 0) {
431
+ return jsonFailure(path, "a property that threw when read");
432
+ }
433
+ if (!spendJson(budget, Math.max(0, length.success - 1))) {
434
+ return jsonFailure(path, "JSON expansion exceeds the outbound frame budget");
435
+ }
436
+ const descriptors2 = ownDescriptors(value);
437
+ if (import_effect.Result.isFailure(descriptors2)) return jsonFailure(path, descriptors2.failure);
438
+ ancestors.add(value);
439
+ const copied2 = [];
440
+ const memberPath2 = { parent: path, key: 0 };
441
+ for (let index = 0; index < length.success; index += 1) {
442
+ memberPath2.key = index;
443
+ const descriptor = Object.hasOwn(descriptors2.success, index) ? descriptors2.success[index] : void 0;
444
+ if (descriptor !== void 0 && isAccessor(descriptor)) {
445
+ ancestors.delete(value);
446
+ return jsonFailure(memberPath2, "an accessor property");
447
+ }
448
+ const member = descriptor === void 0 ? void 0 : descriptor.value;
449
+ const snapshot = snapshotJson(member, memberPath2, ancestors, budget);
450
+ if (import_effect.Result.isFailure(snapshot)) {
451
+ ancestors.delete(value);
452
+ return snapshot;
453
+ }
454
+ copied2.push(snapshot.success);
455
+ }
456
+ ancestors.delete(value);
457
+ return import_effect.Result.succeed(copied2);
458
+ }
459
+ const object = value;
460
+ const prototype = reflect(() => Object.getPrototypeOf(object));
461
+ if (import_effect.Result.isFailure(prototype)) return jsonFailure(path, prototype.failure);
462
+ if (prototype.success !== Object.prototype && prototype.success !== null) {
463
+ return jsonFailure(path, "an object with a non-plain prototype");
464
+ }
465
+ const symbols = reflect(() => Object.getOwnPropertySymbols(object));
466
+ if (import_effect.Result.isFailure(symbols)) return jsonFailure(path, symbols.failure);
467
+ const descriptors = ownDescriptors(object);
468
+ if (import_effect.Result.isFailure(descriptors)) return jsonFailure(path, descriptors.failure);
469
+ for (const key of symbols.success) {
470
+ if (Object.hasOwn(descriptors.success, key) && descriptors.success[key].enumerable) {
471
+ return jsonFailure(path, "a symbol-keyed property");
472
+ }
473
+ }
474
+ ancestors.add(object);
475
+ const copied = {};
476
+ let members = 0;
477
+ const memberPath = { parent: path, key: "" };
478
+ for (const key of Object.keys(descriptors.success)) {
479
+ memberPath.key = key;
480
+ const descriptor = descriptors.success[key];
481
+ if (descriptor.enumerable !== true) continue;
482
+ if (!spendJson(budget, key.length + 3 + Math.min(1, members++))) {
483
+ ancestors.delete(object);
484
+ return jsonFailure(path, "JSON expansion exceeds the outbound frame budget");
485
+ }
486
+ if (isAccessor(descriptor)) {
487
+ ancestors.delete(object);
488
+ return jsonFailure(memberPath, "an accessor property");
489
+ }
490
+ const snapshot = snapshotJson(descriptor.value, memberPath, ancestors, budget);
491
+ if (import_effect.Result.isFailure(snapshot)) {
492
+ ancestors.delete(object);
493
+ return snapshot;
494
+ }
495
+ Object.defineProperty(copied, key, {
496
+ configurable: true,
497
+ enumerable: true,
498
+ value: snapshot.success,
499
+ writable: true
500
+ });
501
+ }
502
+ ancestors.delete(object);
503
+ return import_effect.Result.succeed(copied);
504
+ };
505
+ const snapshotArguments = (server, args, diagnostic, maxBytes) => {
506
+ const snapshot = snapshotJson(args, "arguments", /* @__PURE__ */ new Set(), { remaining: maxBytes });
507
+ if (import_effect.Result.isFailure(snapshot)) {
508
+ diagnostic("invalid-arguments", snapshot.failure);
509
+ return import_effect.Result.fail(Limits.protocolError(
510
+ server,
511
+ `MCP server "${server}" was sent a tool argument that is not JSON: ${snapshot.failure.reason}; property path withheld`
512
+ ));
513
+ }
514
+ if (!(0, import_Record.isRecord)(snapshot.success)) {
515
+ return import_effect.Result.fail(Limits.protocolError(server, `MCP server "${server}" tool arguments must be a JSON object`));
516
+ }
517
+ return import_effect.Result.succeed(snapshot.success);
518
+ };
519
+ const connect = (options) => import_effect.Effect.acquireUseRelease(
520
+ import_effect.Effect.flatMap(import_effect.Effect.scope, import_effect.Scope.fork),
521
+ (scope) => import_effect.Effect.gen(function* () {
522
+ const diagnostic = yield* DiagnosticReporter.make(options.server);
523
+ const decodeResponse = (response, decoded) => import_effect.Effect.fromResult(decoded).pipe(
524
+ import_effect.Effect.tapError(() => import_effect.Effect.sync(() => diagnostic("invalid-response", response)))
525
+ );
526
+ const handshakeTimeoutMs = options.handshakeTimeoutMs ?? defaultHandshakeTimeoutMs;
527
+ const maxArgumentBytes = options.maxOutboundFrameBytes ?? defaultMaxOutboundFrameBytes;
528
+ const maxTools = options.maxTools ?? defaultMaxTools;
529
+ const maxToolNameBytes = options.maxToolNameBytes ?? defaultMaxToolNameBytes;
530
+ const maxToolDocumentBytes = options.maxToolDocumentBytes ?? defaultMaxToolDocumentBytes;
531
+ const maxCatalogPages = options.maxCatalogPages ?? defaultMaxCatalogPages;
532
+ yield* Limits.checkPositiveIntegers(options.server, [
533
+ ["handshakeTimeoutMs", handshakeTimeoutMs],
534
+ ["maxTools", maxTools],
535
+ ["maxToolNameBytes", maxToolNameBytes],
536
+ ["maxToolDocumentBytes", maxToolDocumentBytes],
537
+ ["maxCatalogPages", maxCatalogPages]
538
+ ]);
539
+ const transport = yield* isHttp(options) ? HttpTransport.connect(options) : StdioTransport.connect(options);
540
+ const initialized = yield* transport.request(
541
+ "initialize",
542
+ {
543
+ protocolVersion: supportedProtocolVersions[0],
544
+ capabilities: {},
545
+ clientInfo
546
+ },
547
+ handshakeTimeoutMs
548
+ );
549
+ yield* decodeResponse(initialized, asInitialize(options.server, initialized));
550
+ yield* transport.notify("notifications/initialized", void 0, handshakeTimeoutMs);
551
+ const tools = [];
552
+ const toolNames = /* @__PURE__ */ new Set();
553
+ const cursors = /* @__PURE__ */ new Set();
554
+ let params = {};
555
+ let pageCount = 0;
556
+ while (true) {
557
+ const listed = yield* transport.request("tools/list", params, handshakeTimeoutMs);
558
+ pageCount += 1;
559
+ const page = yield* decodeResponse(
560
+ listed,
561
+ asToolPage(
562
+ options.server,
563
+ listed,
564
+ { maxTools, maxToolNameBytes, maxToolDocumentBytes },
565
+ toolNames,
566
+ tools
567
+ )
568
+ );
569
+ if (page.nextCursor === void 0) break;
570
+ if (cursors.has(page.nextCursor)) {
571
+ diagnostic("invalid-response", listed);
572
+ return yield* import_effect.Effect.fail(invalidResponse(
573
+ options.server,
574
+ `MCP server "${options.server}" repeated a tools/list cursor`
575
+ ));
576
+ }
577
+ if (pageCount >= maxCatalogPages) {
578
+ return yield* import_effect.Effect.fail(invalidResponse(
579
+ options.server,
580
+ `MCP server "${options.server}" returned more than ${maxCatalogPages} tools/list pages`
581
+ ));
582
+ }
583
+ cursors.add(page.nextCursor);
584
+ params = { cursor: page.nextCursor };
585
+ }
586
+ JsonLimits.freezeParsed(tools);
587
+ const validators = /* @__PURE__ */ new Map();
588
+ for (const tool of tools) {
589
+ if (tool.outputSchema !== void 0) {
590
+ validators.set(tool.name, yield* runJsonWork(compileOutputSchema(tool.outputSchema)));
591
+ }
592
+ }
593
+ const callTool = (name, args) => {
594
+ const tool = tools.find((candidate) => candidate.name === name);
595
+ if (tool === void 0) {
596
+ return import_effect.Effect.fail(
597
+ new import_McpError.McpError({
598
+ code: "tool_not_found",
599
+ message: `MCP server "${options.server}" has no requested tool`,
600
+ server: options.server
601
+ })
602
+ );
603
+ }
604
+ const snapshot = snapshotArguments(options.server, args, diagnostic, maxArgumentBytes);
605
+ if (import_effect.Result.isFailure(snapshot)) {
606
+ return import_effect.Effect.fail(snapshot.failure);
607
+ }
608
+ return import_effect.Effect.flatMap(
609
+ transport.request("tools/call", { name, arguments: snapshot.success }),
610
+ (result) => import_effect.Effect.flatMap(
611
+ runJsonWork(asToolResult(options.server, result, tool.outputSchema, diagnostic, validators.get(name))),
612
+ (decoded) => decodeResponse(result, decoded)
613
+ )
614
+ );
615
+ };
616
+ return { server: options.server, tools, callTool };
617
+ }).pipe(import_effect.Scope.provide(scope)),
618
+ // Closing a failed attempt also detaches it from the caller's scope.
619
+ // Successful sessions remain owned by that scope until it closes.
620
+ (scope, exit) => import_effect.Exit.isFailure(exit) ? import_effect.Scope.close(scope, exit) : import_effect.Effect.void
621
+ );
622
+ //# sourceMappingURL=McpClient.js.map