@effected/mcp 0.2.0 → 0.4.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/testing.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { Effect, JsonSchema, Layer, PlatformError, Schema, Scope, Stdio } from "effect";
2
- import { McpProtocol } from "effect/unstable/ai";
3
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
+ import { McpProtocol } from "effect/ai";
3
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
4
4
  //#region src/McpTestFailure.d.ts
5
5
  declare const McpTestFailure_base: Schema.Class<McpTestFailure, Schema.TaggedStruct<"McpTestFailure", {
6
+ /** Which failure occurred; see the list above for which client raises which. */
6
7
  readonly reason: Schema.Literals<readonly ["StreamEnded", "ServerStopped", "NotJsonRpc", "NotInitialized", "ErrorResponse"]>;
8
+ /** A human-readable description of what went wrong. */
7
9
  readonly message: Schema.String;
8
10
  }>, import("effect/Cause").YieldableError>;
9
11
  /**
@@ -23,8 +25,8 @@ declare const McpTestFailure_base: Schema.Class<McpTestFailure, Schema.TaggedStr
23
25
  * not `catchTag`.
24
26
  * - `ServerStopped` (`McpHarness`): the in-process server stopped, usually
25
27
  * because stdin closed while the request was in flight.
26
- * - `NotInitialized` (`McpHarness`): a request other than `initialize` was
27
- * made on a stateful revision before `initialize` was sent. The request
28
+ * - `NotInitialized` (`McpHarness`): a request other than `initialize` or
29
+ * `ping` was made on a stateful revision before `initialize` was sent. The request
28
30
  * is never written; the server would only have refused it opaquely:
29
31
  * `-32602 Invalid request metadata` when a stateless adapter is listed
30
32
  * first, `-32603 Internal error` when only stateful revisions are served.
@@ -140,11 +142,27 @@ interface McpHarnessOptions {
140
142
  * anything that went through `console.log`, which in a real server is
141
143
  * the wire. Assert it empty.
142
144
  * - On a stateful revision (the default `2025-11-25`), send `initialize`
143
- * first. Every other request before it fails with `NotInitialized` and
144
- * is never written, `ping` included: the server would only answer an
145
- * opaque refusal, `-32602 Invalid request metadata` when a stateless
146
- * adapter is listed first (as in `McpStdio.protocols`), or
147
- * `-32603 Internal error` when only stateful revisions are served.
145
+ * first. `ping` is the exception: the server answers it `{}` before
146
+ * initialization, so it is written. Every other request before
147
+ * `initialize` fails with `NotInitialized` and is never written: the
148
+ * server would only answer an opaque refusal,
149
+ * `-32602 Invalid request metadata` when a stateless adapter is listed
150
+ * first (as in `McpStdio.protocols`), or `-32603 Internal error` when
151
+ * only stateful revisions are served.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { McpHarness } from "@effected/mcp/testing";
156
+ * import { Effect } from "effect";
157
+ *
158
+ * // `ServerLayer` is the server's layer, built without its own `Stdio`.
159
+ * const program = Effect.gen(function* () {
160
+ * const harness = yield* McpHarness.make(ServerLayer);
161
+ * yield* harness.initialize;
162
+ * const tools = yield* harness.listTools;
163
+ * return tools.map((tool) => tool.name);
164
+ * }).pipe(Effect.scoped);
165
+ * ```
148
166
  *
149
167
  * @public
150
168
  */
@@ -173,7 +191,7 @@ export declare class McpHarness {
173
191
  readonly discover: Effect.Effect<JsonRpcMessage, McpTestFailure>;
174
192
  /** Send a request and wait for its response. */
175
193
  readonly request: (method: string, params?: unknown) => Effect.Effect<JsonRpcMessage, McpTestFailure>;
176
- /** Send a request now; wait for its response later. Fails with `NotInitialized`, sending nothing, on a stateful revision before `initialize`. */
194
+ /** Send a request now; wait for its response later. Fails with `NotInitialized`, sending nothing, on a stateful revision before `initialize` (`ping` excepted). */
177
195
  readonly startRequest: (method: string, params?: unknown) => Effect.Effect<{
178
196
  readonly id: number;
179
197
  readonly response: Effect.Effect<JsonRpcMessage, McpTestFailure>;
@@ -236,8 +254,7 @@ interface McpProbeResult {
236
254
  *
237
255
  * @remarks
238
256
  * Stdin stays open until the id-1 response has arrived. Closing it right
239
- * after writing, as every hand-rolled smoke test did, makes an Effect server
240
- * drop the in-flight response and exit 0, so a slow boot reads as a pass with
257
+ * after writing makes an Effect server drop the in-flight response and exit 0, so a slow boot reads as a pass with
241
258
  * no response. The caller asserts `response.error === undefined`, `stderr` is
242
259
  * empty and `exitCode` is 0; this is the MCP half of a packed-install proof.
243
260
  * Checking stderr and the exit code alone passes a server that answers
@@ -251,6 +268,20 @@ interface McpProbeResult {
251
268
  * - A child that ignores stdin EOF never exits, and the probe waits for it.
252
269
  * Wrap the probe in `Effect.timeout`.
253
270
  *
271
+ * @example
272
+ * ```ts
273
+ * import { McpProbe } from "@effected/mcp/testing";
274
+ * import { Effect } from "effect";
275
+ * import { ChildProcess } from "effect/process";
276
+ *
277
+ * const program = Effect.gen(function* () {
278
+ * const probe = yield* McpProbe.initialize(ChildProcess.make(process.execPath, ["./bin/server.js"]));
279
+ * // A clean boot: no JSON-RPC error, empty stderr, exit code 0.
280
+ * return probe.response.error === undefined && probe.stderr === "" && probe.exitCode === 0;
281
+ * });
282
+ * // Requires a `ChildProcessSpawner` from the platform layer.
283
+ * ```
284
+ *
254
285
  * @public
255
286
  */
256
287
  export declare class McpProbe {
@@ -283,6 +314,23 @@ export declare class McpProbe {
283
314
  * - A request in flight when stdin closes is dropped by an Effect server:
284
315
  * read its response first.
285
316
  *
317
+ * @example
318
+ * ```ts
319
+ * import { McpProcess } from "@effected/mcp/testing";
320
+ * import { Effect } from "effect";
321
+ * import { ChildProcess } from "effect/process";
322
+ *
323
+ * const program = Effect.gen(function* () {
324
+ * const child = yield* McpProcess.spawn(ChildProcess.make(process.execPath, ["./bin/server.js"]));
325
+ * yield* child.handshake();
326
+ * yield* child.send({ jsonrpc: "2.0", id: 2, method: "tools/list" });
327
+ * const { response } = yield* child.readUntilResponse(2);
328
+ * yield* child.closeStdin;
329
+ * return response;
330
+ * }).pipe(Effect.scoped);
331
+ * // Requires a `ChildProcessSpawner` from the platform layer.
332
+ * ```
333
+ *
286
334
  * @public
287
335
  */
288
336
  export declare class McpProcess {
@@ -347,7 +395,7 @@ interface McpToolAuditPolicy {
347
395
  readonly requireOutputSchema?: boolean | undefined;
348
396
  /**
349
397
  * A served `outputSchema` must be rooted at `type: "object"`. Defaults to
350
- * `true` (D10). Only the stateless revision serves a non-object root, so
398
+ * `true`. Only the stateless revision serves a non-object root, so
351
399
  * only there does this fire; a union root (`anyOf` or `oneOf`) names
352
400
  * `ToolOutputSchema.objectRooted` as the fix for a union whose members are
353
401
  * all objects (a union with a primitive member needs an object envelope