@effected/mcp 0.11.0 → 0.12.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/McpStdio.js CHANGED
@@ -16,7 +16,8 @@ import { McpProtocol, McpServer } from "effect/ai";
16
16
  * ```
17
17
  *
18
18
  * - {@link McpStdio.layer} is `McpServer.layerStdio` with `LogToStderr`
19
- * provided and merged into its output, and `Layer.orDie`. It answers a
19
+ * provided and merged into its output, and a bad `protocols` list
20
+ * turned into a defect. It answers a
20
21
  * stdin line that is not JSON with a JSON-RPC `-32700` parse error, and
21
22
  * JSON that is not a JSON-RPC message with `-32600`, and keeps serving.
22
23
  * - {@link McpStdio.launch} reports a launch failure itself, on stderr, and
@@ -57,14 +58,24 @@ var McpStdio = class McpStdio {
57
58
  /**
58
59
  * `McpServer.layerStdio` with `LogToStderr` provided and merged into the
59
60
  * output, so every layer it provides logs to stderr too. A bad
60
- * `protocols` list dies: it is the implementer's defect.
61
+ * `protocols` list dies: it is the implementer's defect. So does a
62
+ * registration that fails with `Cause.IllegalArgumentError`, which core
63
+ * reports for the same class of mistake; any other registration error
64
+ * stays in the error channel.
61
65
  *
62
66
  * @remarks
63
- * Only a layer this one provides inherits `LogToStderr`: compose with
64
- * `Layer.provideMerge(McpStdio.layer(...))`. A sibling merged beside it
65
- * with `Layer.mergeAll` is not provided by it, so its build logs still
67
+ * Pass the server's registration layers as `options.registrations`: they
68
+ * are built before the server reads its first request, under
69
+ * `LogToStderr`. Providing this layer to them, as
70
+ * `Registrations.pipe(Layer.provideMerge(McpStdio.layer(...)))`, also
71
+ * serves them, and they inherit `LogToStderr` the same way. A sibling
72
+ * merged beside it with `Layer.mergeAll` is neither: its build logs still
66
73
  * go through `console.log`, onto the wire.
67
-
74
+ *
75
+ * The output is `McpServer` and the `McpServer.McpRequest<"Requires", Scope>`
76
+ * marker the transport discharges. `McpServerClient` is not in it: the
77
+ * stateless `v2026_07_28` revision has no client session, so a handler
78
+ * reads the client's details from `McpSchema.McpRequestContext` instead.
68
79
  *
69
80
  * `LogToStderr` only reaches a logger that reads it: Effect's default
70
81
  * logger and `Logger.consolePretty`. `Logger.consoleJson`,
@@ -107,8 +118,8 @@ var McpStdio = class McpStdio {
107
118
  * anywhere under the first one's `Effect.provide`: nested `Layer.build`
108
119
  * and `Effect.provide` fork the ambient memo map rather than starting a
109
120
  * new one. The tool registry is shared the same way. Isolate each server
110
- * by wrapping its whole bundle (its toolkit layers together with this
111
- * layer) in `Layer.fresh`, or with its own `ManagedRuntime`,
121
+ * by wrapping its whole bundle (this layer with its `registrations`, or its
122
+ * toolkit layers together with this layer) in `Layer.fresh`, or with its own `ManagedRuntime`,
112
123
  * `Effect.provide(layer, { local: true })` or its own process. Never put
113
124
  * the `Layer.fresh` boundary between a toolkit and this layer: the server
114
125
  * would serve an empty registry.
@@ -126,8 +137,9 @@ var McpStdio = class McpStdio {
126
137
  version: options.version,
127
138
  instructions: options.instructions,
128
139
  description: options.description,
129
- protocols: options.protocols ?? McpStdio.protocols
130
- }).pipe(Layer.provide(makeGuardedStdio()), Layer.provideMerge(Layer.succeed(References.LogToStderr, true)), Layer.orDie);
140
+ protocols: options.protocols ?? McpStdio.protocols,
141
+ registrations: options.registrations
142
+ }).pipe(Layer.provide(makeGuardedStdio()), Layer.provideMerge(Layer.succeed(References.LogToStderr, true)), Layer.catchCause((cause) => Layer.effectContext(Effect.failCause(cause).pipe(Effect.catchIf(Cause.isIllegalArgumentError, (error) => Effect.die(error))))));
131
143
  /**
132
144
  * `Layer.launch`, with any failure other than an interrupt logged on
133
145
  * stderr here and re-raised marked as already reported, keeping its exit
package/McpToolkit.js CHANGED
@@ -232,6 +232,17 @@ var McpToolkit = class {
232
232
  /**
233
233
  * `McpServer.toolkit`'s registration layer, strict by default.
234
234
  * `options.unknownKeyMessage` is deprecated and ignored.
235
+ *
236
+ * @remarks
237
+ * Its requirements are core's `McpServer.ToolkitRequirements` less
238
+ * `McpServer`, which the layer provides itself: the handlers, any service
239
+ * a parameter or success schema needs, and each service a tool declares
240
+ * with `dependencies` or `addDependency` as an
241
+ * `McpServer.McpRequest<"Requires", S>` marker. A marker is met per
242
+ * request by MCP middleware (`McpServer.middleware`, its `layer` provided
243
+ * to this one), never by providing `S`. A service the handlers only need
244
+ * when they are built is captured by the effect form of
245
+ * `Toolkit.toLayer` instead, and declared nowhere.
235
246
  */
236
247
  static layer = (toolkit, options = {}) => Layer.effectDiscard(Effect.gen(function* () {
237
248
  const registry = yield* McpServer.McpServer;
package/README.md CHANGED
@@ -8,10 +8,10 @@ The boundary layer of an `effect/ai` MCP server: stdio wiring that keeps stdout
8
8
  [![TypeScript 7.0](https://img.shields.io/badge/TypeScript-7.0-3178c6.svg)](https://www.typescriptlang.org/)
9
9
 
10
10
  > **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
11
- > Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
11
+ > Effect v4 (`effect` `~4.1.0`) and still in `0.x` development. Stable Effect
12
12
  > makes a kit `1.0.0` possible, not automatic. To keep your `effect` and
13
13
  > `@effect/*` versions on the line the kit is built and tested against, install
14
- > [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
14
+ > [`@effected/pnpm-plugin-effected`](https://www.npmjs.com/package/@effected/pnpm-plugin-effected).
15
15
  >
16
16
  > **Stability: unstable.** This package's API surface is not yet considered
17
17
  > complete and may change across `0.x` releases. Pin an exact version — even a
@@ -486,7 +486,7 @@ with no child process and no sockets. Pass `server` **without** a `Stdio`
486
486
  of its own — a `Stdio` the server provides internally would talk to the
487
487
  real terminal instead of the test's queues, including one that arrives
488
488
  bundled: `@effect/platform-node`'s `NodeServices.layer` brings its own
489
- `Stdio` along with `FileSystem`, `Path`, `ChildProcessSpawner`, `Crypto`
489
+ `Stdio` along with `FileSystem`, `Path`, `ChildProcessSpawner`
490
490
  and `Terminal`, so composing the server with that bundle loses the harness's
491
491
  `Stdio` the same way. Compose the individual platform layers the server
492
492
  needs instead and leave `Stdio` out; the harness supplies it. The harness builds the server
package/ToolReplay.js CHANGED
@@ -15,6 +15,13 @@ import { KeyValueStore } from "effect/persistence";
15
15
  * `get` and `set`, for an application's own table. Then `R` carries no
16
16
  * `KeyValueStore`.
17
17
  *
18
+ * Inside an MCP server, pass the store. A service left in a handler's `R`
19
+ * has to be declared in the tool's `dependencies`, which makes it a
20
+ * request-level dependency that only MCP middleware can meet. Read
21
+ * `KeyValueStore` once when the handlers are built, with the effect form of
22
+ * `Toolkit.toLayer`, and hand it to `wrap` as `store`: a `KeyValueStore`
23
+ * satisfies {@link ToolReplayStore} as is.
24
+ *
18
25
  * Semantics, per call:
19
26
  *
20
27
  * - A key of `Option.none()` runs the handler; nothing is read or stored.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Array, Context, Effect, JsonSchema, Layer, Option, Runtime, Schema, Stdio, Struct } from "effect";
1
+ import { Array, Cause, Context, Effect, JsonSchema, Layer, Option, Runtime, Schema, Scope, Stdio, Struct } from "effect";
2
2
  import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/ai";
3
3
  import { Remediation } from "@effected/engine";
4
4
  import { KeyValueStore } from "effect/persistence";
@@ -7,7 +7,7 @@ import { KeyValueStore } from "effect/persistence";
7
7
  *
8
8
  * @public
9
9
  */
10
- interface McpStdioOptions {
10
+ interface McpStdioOptions<RA = never, RE = never, RR = never> {
11
11
  /** The server name reported to the client. */
12
12
  readonly name: string;
13
13
  /** The server version reported to the client. */
@@ -18,7 +18,28 @@ interface McpStdioOptions {
18
18
  readonly description?: string | undefined;
19
19
  /** Defaults to {@link McpStdio.protocols}. Read its ordering rules before overriding. */
20
20
  readonly protocols?: Array.NonEmptyReadonlyArray<McpProtocol.ProtocolAdapter> | undefined;
21
+ /**
22
+ * The server's registration layers (toolkits, resources, prompts), built
23
+ * before the server reads its first request and under `LogToStderr`.
24
+ *
25
+ * @remarks
26
+ * Core's `registrations` option of `McpServer.layerStdio`. The transport
27
+ * discharges each `McpServer.McpRequest<"Requires", Scope>` marker; any
28
+ * other request-level marker stays in the layer's requirements, to be met
29
+ * by MCP middleware. A registration that needs `Stdio` when it is built
30
+ * gets the guarded one this layer serves over.
31
+ */
32
+ readonly registrations?: Layer.Layer<RA, RE, RR> | undefined;
21
33
  }
34
+ /**
35
+ * The services a registration layer needs that {@link McpStdio.layer} does
36
+ * not supply: everything but `McpServer` and `Stdio` (which the layer
37
+ * requires itself), with each request-level marker but
38
+ * `McpServer.McpRequest<"Requires", Scope>`, which the transport discharges.
39
+ *
40
+ * @public
41
+ */
42
+ type McpStdioRegistrationServices<RR> = Exclude<McpServer.McpRequest.Without<RR>, McpServer.McpServer> | McpServer.McpRequest.From<"Requires", Exclude<McpServer.McpRequest.Only<"Requires", RR>, Scope.Scope>>;
22
43
  /**
23
44
  * Options for {@link McpStdio.launch}.
24
45
  *
@@ -51,7 +72,8 @@ interface McpLaunchOptions {
51
72
  * ```
52
73
  *
53
74
  * - {@link McpStdio.layer} is `McpServer.layerStdio` with `LogToStderr`
54
- * provided and merged into its output, and `Layer.orDie`. It answers a
75
+ * provided and merged into its output, and a bad `protocols` list
76
+ * turned into a defect. It answers a
55
77
  * stdin line that is not JSON with a JSON-RPC `-32700` parse error, and
56
78
  * JSON that is not a JSON-RPC message with `-32600`, and keeps serving.
57
79
  * - {@link McpStdio.launch} reports a launch failure itself, on stderr, and
@@ -88,14 +110,24 @@ export declare class McpStdio {
88
110
  /**
89
111
  * `McpServer.layerStdio` with `LogToStderr` provided and merged into the
90
112
  * output, so every layer it provides logs to stderr too. A bad
91
- * `protocols` list dies: it is the implementer's defect.
113
+ * `protocols` list dies: it is the implementer's defect. So does a
114
+ * registration that fails with `Cause.IllegalArgumentError`, which core
115
+ * reports for the same class of mistake; any other registration error
116
+ * stays in the error channel.
92
117
  *
93
118
  * @remarks
94
- * Only a layer this one provides inherits `LogToStderr`: compose with
95
- * `Layer.provideMerge(McpStdio.layer(...))`. A sibling merged beside it
96
- * with `Layer.mergeAll` is not provided by it, so its build logs still
119
+ * Pass the server's registration layers as `options.registrations`: they
120
+ * are built before the server reads its first request, under
121
+ * `LogToStderr`. Providing this layer to them, as
122
+ * `Registrations.pipe(Layer.provideMerge(McpStdio.layer(...)))`, also
123
+ * serves them, and they inherit `LogToStderr` the same way. A sibling
124
+ * merged beside it with `Layer.mergeAll` is neither: its build logs still
97
125
  * go through `console.log`, onto the wire.
98
-
126
+ *
127
+ * The output is `McpServer` and the `McpServer.McpRequest<"Requires", Scope>`
128
+ * marker the transport discharges. `McpServerClient` is not in it: the
129
+ * stateless `v2026_07_28` revision has no client session, so a handler
130
+ * reads the client's details from `McpSchema.McpRequestContext` instead.
99
131
  *
100
132
  * `LogToStderr` only reaches a logger that reads it: Effect's default
101
133
  * logger and `Logger.consolePretty`. `Logger.consoleJson`,
@@ -138,8 +170,8 @@ export declare class McpStdio {
138
170
  * anywhere under the first one's `Effect.provide`: nested `Layer.build`
139
171
  * and `Effect.provide` fork the ambient memo map rather than starting a
140
172
  * new one. The tool registry is shared the same way. Isolate each server
141
- * by wrapping its whole bundle (its toolkit layers together with this
142
- * layer) in `Layer.fresh`, or with its own `ManagedRuntime`,
173
+ * by wrapping its whole bundle (this layer with its `registrations`, or its
174
+ * toolkit layers together with this layer) in `Layer.fresh`, or with its own `ManagedRuntime`,
143
175
  * `Effect.provide(layer, { local: true })` or its own process. Never put
144
176
  * the `Layer.fresh` boundary between a toolkit and this layer: the server
145
177
  * would serve an empty registry.
@@ -152,7 +184,7 @@ export declare class McpStdio {
152
184
  * Each call mints a fresh layer; bind the result to a `const` or the
153
185
  * server builds twice.
154
186
  */
155
- static readonly layer: (options: McpStdioOptions) => Layer.Layer<McpServer.McpServer | McpSchema.McpServerClient, never, Stdio.Stdio>;
187
+ static readonly layer: <RA = never, RE = never, RR = never>(options: McpStdioOptions<RA, RE, RR>) => Layer.Layer<McpServer.McpServer | McpServer.McpRequest<"Requires", Scope.Scope>, Exclude<RE, Cause.IllegalArgumentError>, Stdio.Stdio | McpStdioRegistrationServices<RR>>;
156
188
  /**
157
189
  * `Layer.launch`, with any failure other than an interrupt logged on
158
190
  * stderr here and re-raised marked as already reported, keeping its exit
@@ -462,8 +494,19 @@ export declare class McpToolkit {
462
494
  /**
463
495
  * `McpServer.toolkit`'s registration layer, strict by default.
464
496
  * `options.unknownKeyMessage` is deprecated and ignored.
465
- */
466
- static readonly layer: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>, options?: McpToolkitOptions) => Layer.Layer<never, never, Tool.HandlersFor<Tools> | Exclude<Tool.HandlerServices<Tools>, McpSchema.McpRequestContext>>;
497
+ *
498
+ * @remarks
499
+ * Its requirements are core's `McpServer.ToolkitRequirements` less
500
+ * `McpServer`, which the layer provides itself: the handlers, any service
501
+ * a parameter or success schema needs, and each service a tool declares
502
+ * with `dependencies` or `addDependency` as an
503
+ * `McpServer.McpRequest<"Requires", S>` marker. A marker is met per
504
+ * request by MCP middleware (`McpServer.middleware`, its `layer` provided
505
+ * to this one), never by providing `S`. A service the handlers only need
506
+ * when they are built is captured by the effect form of
507
+ * `Toolkit.toLayer` instead, and declared nowhere.
508
+ */
509
+ static readonly layer: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>, options?: McpToolkitOptions) => Layer.Layer<never, never, Exclude<McpServer.ToolkitRequirements<Tools>, McpServer.McpServer>>;
467
510
  }
468
511
  /**
469
512
  * How a tool's declared failure should read on the wire.
@@ -802,7 +845,7 @@ export declare class ToolRefusal extends ToolRefusal_base {
802
845
  * Core's `KeyValueStore` already satisfies it structurally, so a
803
846
  * `KeyValueStore` value can be passed as is. An application backed by its
804
847
  * own table implements just these two methods, instead of stubbing the
805
- * `remove`, `clear` and `size` that `KeyValueStore.makeStringOnly` demands.
848
+ * `remove`, `clear`, `size` and `keys` that `KeyValueStore.makeStringOnly` demands.
806
849
  *
807
850
  * `get` answers `undefined` for a missing key, the same contract as
808
851
  * `KeyValueStore.get`. A failure from either method is never a tool error:
@@ -864,6 +907,13 @@ interface ToolReplayOptions<P, S extends Schema.Top, Store extends ToolReplaySto
864
907
  * `get` and `set`, for an application's own table. Then `R` carries no
865
908
  * `KeyValueStore`.
866
909
  *
910
+ * Inside an MCP server, pass the store. A service left in a handler's `R`
911
+ * has to be declared in the tool's `dependencies`, which makes it a
912
+ * request-level dependency that only MCP middleware can meet. Read
913
+ * `KeyValueStore` once when the handlers are built, with the effect form of
914
+ * `Toolkit.toLayer`, and hand it to `wrap` as `store`: a `KeyValueStore`
915
+ * satisfies {@link ToolReplayStore} as is.
916
+ *
867
917
  * Semantics, per call:
868
918
  *
869
919
  * - A key of `Option.none()` runs the handler; nothing is read or stored.
@@ -935,5 +985,5 @@ export declare class ToolReplay {
935
985
  */
936
986
  static readonly wrap: <P, S extends Schema.Top, Store extends ToolReplayStore | undefined = undefined>(options: ToolReplayOptions<P, S, Store>) => (<E, R>(handler: (params: P) => Effect.Effect<S["Type"], E, R>) => (params: P) => Effect.Effect<S["Type"], E, R | ([Store] extends [ToolReplayStore] ? never : KeyValueStore.KeyValueStore) | S["DecodingServices"] | S["EncodingServices"]>);
937
987
  }
938
- export type { FormatUnknownKeysOptions, McpLaunchOptions, McpStdioOptions, McpToolkitOptions, ToolOutcomeEnvelopeOptions, ToolOutcomeErrorInput, ToolOutcomeFailure, ToolOutcomeFailures, ToolReplayOptions, ToolReplayStore, UnionHandlerOptions, UnionTool, UnionToolOptions, UnknownKeysLevel };
988
+ export type { FormatUnknownKeysOptions, McpLaunchOptions, McpStdioOptions, McpStdioRegistrationServices, McpToolkitOptions, ToolOutcomeEnvelopeOptions, ToolOutcomeErrorInput, ToolOutcomeFailure, ToolOutcomeFailures, ToolReplayOptions, ToolReplayStore, UnionHandlerOptions, UnionTool, UnionToolOptions, UnknownKeysLevel };
939
989
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "The boundary layer of an effect/ai MCP server: stdio wiring that keeps stdout the wire, tool-failure shaping, strict-input walkers, and test clients",
6
6
  "keywords": [
@@ -52,8 +52,8 @@
52
52
  "./package.json": "./package.json"
53
53
  },
54
54
  "peerDependencies": {
55
- "@effected/engine": "^0.10.0",
56
- "effect": "^4.0.0"
55
+ "@effected/engine": "^0.11.0",
56
+ "effect": "~4.1.0"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=24.11.0"
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.4"
8
+ "packageVersion": "7.60.0"
9
9
  }
10
10
  ]
11
11
  }