@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 +22 -10
- package/McpToolkit.js +11 -0
- package/README.md +3 -3
- package/ToolReplay.js +7 -0
- package/index.d.ts +65 -15
- package/package.json +3 -3
- package/tsdoc-metadata.json +1 -1
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
|
|
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
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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 (
|
|
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
|
-
|
|
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
|
[](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`
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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 (
|
|
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 |
|
|
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
|
-
|
|
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 `
|
|
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.
|
|
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.
|
|
56
|
-
"effect": "
|
|
55
|
+
"@effected/engine": "^0.11.0",
|
|
56
|
+
"effect": "~4.1.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=24.11.0"
|