@effected/mcp 0.12.0 → 0.13.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/index.d.ts CHANGED
@@ -1,989 +1,21 @@
1
- import { Array, Cause, Context, Effect, JsonSchema, Layer, Option, Runtime, Schema, Scope, Stdio, Struct } from "effect";
2
- import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/ai";
3
- import { Remediation } from "@effected/engine";
4
- import { KeyValueStore } from "effect/persistence";
1
+ import { Distribution } from "@effected/kit";
5
2
  /**
6
- * Options for {@link McpStdio.layer}.
3
+ * Options a carrier's bin shim passes to `main`. `distribution` names
4
+ * the package the `effected-mcp` bin was installed through; omitted for a
5
+ * direct install of `@effected/mcp`.
7
6
  *
8
7
  * @public
9
8
  */
10
- interface McpStdioOptions<RA = never, RE = never, RR = never> {
11
- /** The server name reported to the client. */
12
- readonly name: string;
13
- /** The server version reported to the client. */
14
- readonly version: string;
15
- /** Surfaced in `initialize` and `server/discover`; tell the agent how to use the tools. */
16
- readonly instructions?: string | undefined;
17
- /** A one-line description of the server, reported to the client. */
18
- readonly description?: string | undefined;
19
- /** Defaults to {@link McpStdio.protocols}. Read its ordering rules before overriding. */
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;
9
+ interface MainOptions {
10
+ readonly distribution?: Distribution;
33
11
  }
34
12
  /**
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.
13
+ * The `effected-mcp` server's version. The bundler replaces the exact expression
14
+ * `process.env.__PACKAGE_VERSION__` with the package version at build time, so
15
+ * it must stay spelled this way.
39
16
  *
40
17
  * @public
41
18
  */
42
- type McpStdioRegistrationServices<RR> = Exclude<McpServer.McpRequest.Without<RR>, McpServer.McpServer> | McpServer.McpRequest.From<"Requires", Exclude<McpServer.McpRequest.Only<"Requires", RR>, Scope.Scope>>;
43
- /**
44
- * Options for {@link McpStdio.launch}.
45
- *
46
- * @public
47
- */
48
- interface McpLaunchOptions {
49
- /**
50
- * Runs once the whole layer has built, and before the launch waits
51
- * forever. By then the stdio protocol is reading stdin: the server is
52
- * serving, although no client may have sent `initialize` yet.
53
- *
54
- * @remarks
55
- * `McpGuard.run` (`@effected/mcp/guard`) uses it as its "connected"
56
- * signal. A layer built after the server layer, as in
57
- * `Layer.effectDiscard(ready).pipe(Layer.provide(Main))`, gives the same
58
- * signal, but only if every consumer re-derives that `Layer.provide`
59
- * builds its dependency first.
60
- */
61
- readonly onReady?: Effect.Effect<void> | undefined;
62
- }
63
- /**
64
- * Serve an MCP server over stdio without ever writing a log line or a failure
65
- * report onto stdout, which is the JSON-RPC wire.
66
- *
67
- * @remarks
68
- * The one-line `main.ts`:
69
- *
70
- * ```ts
71
- * NodeRuntime.runMain(McpStdio.launch(Main), { teardown: McpStdio.teardown })
72
- * ```
73
- *
74
- * - {@link McpStdio.layer} is `McpServer.layerStdio` with `LogToStderr`
75
- * provided and merged into its output, and a bad `protocols` list
76
- * turned into a defect. It answers a
77
- * stdin line that is not JSON with a JSON-RPC `-32700` parse error, and
78
- * JSON that is not a JSON-RPC message with `-32600`, and keeps serving.
79
- * - {@link McpStdio.launch} reports a launch failure itself, on stderr, and
80
- * hides it from `runMain`, whose own report is written outside anything
81
- * the program can provide.
82
- * - {@link McpStdio.teardown} maps stdin EOF, the normal end of every
83
- * session, to exit 0 instead of 130.
84
- *
85
- * Never `NodeRuntime.runMain(Layer.launch(Main).pipe(Effect.provideService(References.LogToStderr, true)))`:
86
- * it typechecks and serves, but a launch failure is still reported by
87
- * `runMain` through `console.log`, straight onto the wire, where the client
88
- * reads it as a malformed frame instead of an error.
89
- *
90
- * @public
91
- */
92
- export declare class McpStdio {
93
- private constructor();
94
- /**
95
- * `[v2026_07_28, v2025_11_25, v2025_06_18]` — the stateless revision
96
- * first, then the two newest stateful ones.
97
- *
98
- * @remarks
99
- * Real clients open with either handshake: some (Claude Code, for one)
100
- * open a stdio server with the stateless `server/discover`, while others
101
- * still send `initialize`. The order is load-bearing:
102
- *
103
- * - Stateless first: a request with no session and no `_meta` falls to
104
- * `protocols[0]`.
105
- * - Never a single entry: `initialize` only matches stateful adapters, so
106
- * a stateless-only list refuses every client that opens with it.
107
- * - At most one stateless adapter; a second fails the layer.
108
- */
109
- static readonly protocols: Array.NonEmptyReadonlyArray<McpProtocol.ProtocolAdapter>;
110
- /**
111
- * `McpServer.layerStdio` with `LogToStderr` provided and merged into the
112
- * output, so every layer it provides logs to stderr too. A bad
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.
117
- *
118
- * @remarks
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
125
- * go through `console.log`, onto the wire.
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.
131
- *
132
- * `LogToStderr` only reaches a logger that reads it: Effect's default
133
- * logger and `Logger.consolePretty`. `Logger.consoleJson`,
134
- * `Logger.consoleLogFmt` and `Logger.consoleStructured` are built with
135
- * `Logger.withConsoleLog`, which writes through `console.log` whatever
136
- * `LogToStderr` says, so a server that installs one puts every log line on
137
- * the wire. Never install them under this layer; wrap the formatter in
138
- * `Logger.withConsoleError` instead, as in
139
- * `Logger.layer([Logger.withConsoleError(Logger.formatJson)])`.
140
- *
141
- * The server reads stdin through a guard that frames it exactly as core's
142
- * decoder does: one streaming UTF-8 decoder, a byte-order mark stripped
143
- * only at the start of the stream, lines split on `\n`. Core itself skips
144
- * a line it cannot use and keeps serving, but sends no reply, where
145
- * JSON-RPC 2.0 requires one; the guard supplies it. A line that is not
146
- * JSON is answered on stdout with a parse error, code `-32700` and
147
- * `id: null`, and never reaches core. So is a line longer than core's cap
148
- * of 16 Mi UTF-16 code units: it is answered once, as soon as it passes
149
- * the cap, and the rest of it is discarded up to its newline (core would
150
- * log it on stderr and drop it). A line of JSON whitespace (space, tab,
151
- * carriage return) is ignored.
152
- *
153
- * A line that is JSON but no JSON-RPC message core can handle is answered
154
- * with an Invalid Request, code `-32600` and `id: null`, and never reaches
155
- * core: a value that is neither an object nor an array, an object whose
156
- * `method` is not a string and whose `id` is absent or `null`, and an
157
- * object with neither `method` nor `id`, which is neither a request nor a
158
- * response.
159
- *
160
- * Everything else goes to core: an array, which core answers `-32600`
161
- * itself because it serves no batches; an object with an `id` and no
162
- * `method`, which is a response and gets no reply; and a request with an
163
- * `id`, which core answers even when its `method` is malformed.
164
- *
165
- * Give each server a fresh layer memo map. Core's stdio protocol layer
166
- * is a shared constant, so a second `McpStdio.layer` server whose build
167
- * sees the first one's memo map shares its protocol: only the first
168
- * server reads stdin, and the second never answers. Merging both into
169
- * one graph does that, and so does building or providing the second
170
- * anywhere under the first one's `Effect.provide`: nested `Layer.build`
171
- * and `Effect.provide` fork the ambient memo map rather than starting a
172
- * new one. The tool registry is shared the same way. Isolate each server
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`,
175
- * `Effect.provide(layer, { local: true })` or its own process. Never put
176
- * the `Layer.fresh` boundary between a toolkit and this layer: the server
177
- * would serve an empty registry.
178
- *
179
- * Code after a completed `Effect.provide` of a stdio server never runs:
180
- * core's stdio protocol interrupts the fiber that built it when its stdin
181
- * loop ends, which closing the provide's scope does. In a test, serve the
182
- * layer through `McpHarness` from `@effected/mcp/testing` instead.
183
- *
184
- * Each call mints a fresh layer; bind the result to a `const` or the
185
- * server builds twice.
186
- */
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>>;
188
- /**
189
- * `Layer.launch`, with any failure other than an interrupt logged on
190
- * stderr here and re-raised marked as already reported, keeping its exit
191
- * code.
192
- *
193
- * @remarks
194
- * `runMain` logs an unhandled failure from outside the program, where no
195
- * `Effect.provideService` reaches, and Effect's default logger writes
196
- * through `console.log` unless `LogToStderr` is set. For an MCP server
197
- * that is stdout, which is the wire.
198
- *
199
- * `options.onReady` runs after the layer has built and before the launch
200
- * waits forever, as {@link McpLaunchOptions.onReady} describes.
201
- */
202
- static readonly launch: <ROut, E, R>(layer: Layer.Layer<ROut, E, R>, options?: McpLaunchOptions) => Effect.Effect<never, Error, R>;
203
- /**
204
- * Success or an interrupt-only exit maps to 0. Anything else goes to
205
- * `Runtime.defaultTeardown`.
206
- *
207
- * @remarks
208
- * Stdin EOF, the normal end of every session, ends in an interrupt-only
209
- * exit, and so do SIGINT and SIGTERM: `runMain` interrupts the program on
210
- * either signal. All three exit 0 here, not the 130 a default teardown
211
- * reports for an interrupt.
212
- */
213
- static readonly teardown: Runtime.Teardown;
214
- }
215
- /**
216
- * The unknown keys found at one object level of a tool-call payload.
217
- *
218
- * @public
219
- */
220
- interface UnknownKeysLevel {
221
- /** Keys from the payload root to this object; empty at the root. Array positions are decimal strings. */
222
- readonly path: ReadonlyArray<string>;
223
- /** Keys the payload carries that this level does not accept, in payload order. */
224
- readonly unknown: ReadonlyArray<string>;
225
- /** Keys this level declares, in schema order. */
226
- readonly accepted: ReadonlyArray<string>;
227
- /**
228
- * The `patternProperties` patterns this level also accepts keys by, in
229
- * schema order. Present only when the level has at least one.
230
- */
231
- readonly acceptedPatterns?: ReadonlyArray<string>;
232
- }
233
- /**
234
- * Options for {@link ToolInputSchema.formatUnknownKeys}.
235
- *
236
- * @public
237
- */
238
- interface FormatUnknownKeysOptions {
239
- /** The cap on each echoed key path, in UTF-16 code units. Defaults to `ToolFailure.ECHO_LIMIT`. */
240
- readonly echoLimit?: number | undefined;
241
- }
242
- /**
243
- * Pure walkers over a tool's served JSON Schema: every unknown key at every
244
- * depth, a message naming them all, and an object-rooted form of a union.
245
- *
246
- * @remarks
247
- * For a `Tool.make` tool, core's strict decode already names every unknown
248
- * key, with every missing or invalid field, in one response. Core never
249
- * validates a `Tool.dynamic` tool's raw JSON Schema, so these walkers are
250
- * for that case: a handler runs them against the schema it registered.
251
- * They run over the schema as served, so what they accept matches what the
252
- * tool advertises:
253
- *
254
- * - Only `additionalProperties: false` closes a node, as in JSON Schema; core
255
- * emits it on every object node of a strict tool and `true` otherwise.
256
- * - `allOf` members' properties merge; `oneOf`/`anyOf` members are chosen by an
257
- * `action`, `kind`, `_tag` or `type` literal (tried in that order), or by
258
- * being the only object member. A payload whose discriminant matches no
259
- * member, or is missing, is skipped and left for decoding.
260
- * - `items`, `prefixItems`, `$ref` into `$defs` (JSON Pointer escapes
261
- * honoured), and `patternProperties` are followed; a key matched by a
262
- * pattern is accepted and walked with the first matching pattern's schema.
263
- * Effect serves a pattern-keyed `Schema.Record` with `patternProperties`
264
- * only when the key pattern's RegExp has the `u` flag; without it the
265
- * record is served open, and every key is accepted.
266
- * - The payload is untrusted: the walk stops descending at depth 256 rather
267
- * than overflowing the stack, so a deeper unknown key goes unreported and
268
- * is left for decoding. Every hop counts toward that cap: each property or
269
- * array position, and also each union member selection, which adds a hop
270
- * without adding a path segment.
271
- *
272
- * @public
273
- */
274
- export declare class ToolInputSchema {
275
- private constructor();
276
- /** Every level of `payload` that carries a key `schema` does not accept. */
277
- static readonly unknownKeys: (payload: unknown, schema: JsonSchema.JsonSchema) => ReadonlyArray<UnknownKeysLevel>;
278
- /**
279
- * `Unrecognized parameter(s): a, b.c. Accepted params: x, y.` per level,
280
- * followed by `Accepted keys matching: ^x-.` when the level also accepts
281
- * keys by pattern. A level that accepts keys only by pattern names its
282
- * patterns alone, never `Accepted params: (none).` Each echoed key and
283
- * pattern is truncated, and at most 20 keys, 20 patterns and 20 levels are
284
- * named; a trailing `(and N more levels).` counts the levels left out.
285
- */
286
- static readonly formatUnknownKeys: (levels: ReadonlyArray<UnknownKeysLevel>, options?: FormatUnknownKeysOptions) => string;
287
- /**
288
- * `schema` with a root `$ref` inlined and a top-level union of objects
289
- * sharing an `action`, `kind`, `_tag` or `type` literal rewritten to an
290
- * object root carrying `type: "object"`, `oneOf` and `"x-discriminator"` —
291
- * MCP requires an object root, and core dies at boot on a union root.
292
- * Returned as the same value when nothing changes.
293
- */
294
- static readonly objectRooted: (schema: JsonSchema.JsonSchema) => JsonSchema.JsonSchema;
295
- }
296
- /**
297
- * Options for {@link McpToolkit.layer}.
298
- *
299
- * @public
300
- */
301
- interface McpToolkitOptions {
302
- /**
303
- * `"all"` (the default): every tool without its own `Tool.Strict`
304
- * annotation is served and decoded strict. `"annotated"`: only tools
305
- * annotated `Tool.Strict` true are. In both modes an explicit annotation,
306
- * true or false, always wins, and a `Tool.dynamic` tool is never
307
- * re-annotated (core dies at registration on a strict dynamic tool).
308
- */
309
- readonly strict?: "all" | "annotated" | undefined;
310
- /**
311
- * Ignored. Core's strict decode reports every excess key at every depth,
312
- * together with every missing or invalid field, in one `InvalidParams`,
313
- * and the layer appends a fixed `Accepted params at <path>: …` line per
314
- * level that carries an unknown key; this option changes neither.
315
- *
316
- * @deprecated A no-op, scheduled for removal in a later minor.
317
- */
318
- readonly unknownKeyMessage?: ((levels: ReadonlyArray<UnknownKeysLevel>) => string) | undefined;
319
- }
320
- /**
321
- * A `Tool.dynamic` made by {@link McpToolkit.unionTool}: served with the
322
- * strict, object-rooted JSON Schema of a `Schema.Union` of objects, and
323
- * carrying that union so {@link McpToolkit.unionHandler} can decode it.
324
- *
325
- * @remarks
326
- * `annotate` and `addDependency` keep the type, so a chained
327
- * `.annotate(Tool.Title, …)` still hands `unionHandler` the union. The
328
- * declared failure is `failure` together with `McpSchema.InvalidParams`.
329
- *
330
- * @public
331
- */
332
- interface UnionTool<Name extends string, P extends Schema.Decoder<unknown>, S extends Schema.Constraint, F extends Schema.Constraint, R = never> extends Tool.Dynamic<Name, {
333
- readonly parameters: JsonSchema.JsonSchema;
334
- readonly success: S;
335
- readonly failure: Schema.Union<readonly [F, typeof McpSchema.InvalidParams]>;
336
- readonly failureMode: "error";
337
- }, R> {
338
- /** The union the tool decodes its arguments with. */
339
- readonly unionParameters: P;
340
- /** Add an annotation, keeping the union. */
341
- annotate<I, V>(tag: Context.Key<I, V>, value: V): UnionTool<Name, P, S, F, R>;
342
- /** Add a request-level dependency, keeping the union. */
343
- addDependency<Identifier, Service>(tag: Context.Key<Identifier, Service>): UnionTool<Name, P, S, F, Identifier | R>;
344
- }
345
- /**
346
- * Options for {@link McpToolkit.unionTool}.
347
- *
348
- * @public
349
- */
350
- interface UnionToolOptions<P extends Schema.Decoder<unknown>, S extends Schema.Constraint, F extends Schema.Constraint, Dependencies extends ReadonlyArray<Context.Key<unknown, unknown>>> {
351
- /** What the tool does, for the agent. */
352
- readonly description?: string | undefined;
353
- /** A `Schema.Union` of object schemas, discriminated by an `action`, `kind`, `_tag` or `type` literal. */
354
- readonly parameters: P;
355
- /** The success schema. Defaults to `Schema.Void`; object-root a union whose members are all objects with `ToolOutputSchema.objectRooted`. */
356
- readonly success?: S | undefined;
357
- /** Declared failures besides `InvalidParams`, such as `ToolRefusal`. Defaults to `Schema.Never`. */
358
- readonly failure?: F | undefined;
359
- /** Services the handler needs, as with `Tool.make`. */
360
- readonly dependencies?: Dependencies | undefined;
361
- }
362
- /**
363
- * Options for {@link McpToolkit.unionHandler}.
364
- *
365
- * @public
366
- */
367
- interface UnionHandlerOptions {
368
- /**
369
- * Ignored. The union decode reports every excess, missing and invalid
370
- * field in one `InvalidParams`, followed by the same fixed
371
- * `Accepted params at <path>: …` lines {@link McpToolkit.layer} appends;
372
- * this option changes neither.
373
- *
374
- * @deprecated A no-op, scheduled for removal in a later minor.
375
- */
376
- readonly unknownKeyMessage?: ((levels: ReadonlyArray<UnknownKeysLevel>) => string) | undefined;
377
- }
378
- /**
379
- * Register a toolkit exactly as core's `McpServer.toolkit` does, except that
380
- * every tool is strict by default and a {@link McpToolkit.unionTool} gets its
381
- * union decoded.
382
- *
383
- * @remarks
384
- * Rejection and reporting are core's: a tool annotated `Tool.Strict` is
385
- * served with `additionalProperties: false` on every object node and decoded
386
- * with `onExcessProperty: "error"` and `errors: "all"`, so one
387
- * `InvalidParams` names every excess key at every depth together with every
388
- * missing or invalid field. The layer appends to that report, never in place
389
- * of it: for every object level of the payload that carries a key the served
390
- * input schema does not accept, one line naming what that level does accept,
391
- * `Accepted params at the root: query, filter.` or
392
- * `Accepted params at ["filter"]: kind, tag.` (the path as core writes it),
393
- * with `keys matching <pattern>` for keys a `patternProperties` level
394
- * accepts, and `This tool accepts no params.` for a zero-parameter tool. An
395
- * agent can fix the call from the reply alone. A failure with no unknown key
396
- * (a missing or mistyped field only) is core's report unchanged. The lines
397
- * are appended to core's `InvalidParams` as it leaves the tool's registered
398
- * handler, the one failure that handler has for bad parameters.
399
- *
400
- * On top of that is the policy: under
401
- * `strict: "all"` (the default) it annotates every tool that carries no
402
- * `Tool.Strict` annotation of its own as strict, skipping `Tool.dynamic`
403
- * tools (core dies at registration on a strict dynamic tool). It runs core's
404
- * `registerToolkit` unchanged under a registration-scoped `McpServer` whose
405
- * `addTool` puts the union decode in front of a union tool's handler and
406
- * passes every other registration through untouched. It patches no core
407
- * internals: it uses only `McpServer.registerToolkit` and `addTool`.
408
- *
409
- * Registration goes through core's module-level `McpServer.McpServer.layer`,
410
- * shared by reference with `McpStdio.layer`'s own copy — provide both into
411
- * the same graph, as with `McpServer.toolkit`. Never wrap that layer in
412
- * `Layer.fresh`: tools would register into a second registry nobody serves.
413
- * To give two servers separate registries, wrap each whole bundle (this
414
- * layer together with `McpStdio.layer`) in `Layer.fresh` instead.
415
- *
416
- * Each call mints a fresh layer; bind the result to a `const` or the
417
- * registration runs twice.
418
- *
419
- * @example
420
- * ```ts
421
- * import { Layer } from "effect";
422
- * import { McpStdio, McpToolkit } from "@effected/mcp";
423
- *
424
- * const ToolsLayer = McpToolkit.layer(MyToolkit).pipe(Layer.provide(MyHandlers));
425
- * const ServerLayer = ToolsLayer.pipe(Layer.provideMerge(McpStdio.layer({ name: "my-server", version: "1.0.0" })));
426
- * ```
427
- *
428
- * @public
429
- */
430
- export declare class McpToolkit {
431
- private constructor();
432
- /**
433
- * A tool whose parameters are a `Schema.Union` of objects, which
434
- * `Tool.make` cannot take: core dies at registration on a non-object
435
- * `parameters` root.
436
- *
437
- * @remarks
438
- * The tool is a `Tool.dynamic` served with Effect's strict JSON Schema
439
- * document for `parameters` (`additionalProperties: false` on every
440
- * object node, definitions as `$defs`), rewritten by
441
- * `ToolInputSchema.objectRooted` to an object root carrying `type`,
442
- * `oneOf` and `x-discriminator`. Write its handler with
443
- * {@link McpToolkit.unionHandler}, which receives the decoded member.
444
- *
445
- * Registered through {@link McpToolkit.layer}, a union tool gets the same
446
- * treatment as a strict `Tool.make` tool: a strict decode with
447
- * `errors: "all"` before the handler runs, reporting every excess, missing
448
- * and invalid field of the matched member in one `InvalidParams`, with the
449
- * same `Accepted params at <path>: …` lines appended, so bad
450
- * arguments answer JSON-RPC `-32602` on `2025-06-18` and an `isError`
451
- * result on the later revisions, exactly as a `Tool.make` decode failure
452
- * does. Registered through core's `McpServer.toolkit` it still decodes
453
- * strictly, in the handler, but the `InvalidParams` is then a declared
454
- * failure: an `isError` result on every revision.
455
- *
456
- * Never annotate it `Tool.Strict` true: core dies at registration on a
457
- * strict tool with a raw JSON Schema.
458
- *
459
- * @example
460
- * ```ts
461
- * import { McpToolkit, ToolOutputSchema, ToolRefusal } from "@effected/mcp";
462
- * import { Schema } from "effect";
463
- * import { Tool } from "effect/ai";
464
- *
465
- * // `AddNote`, `ListNotes`, `Added`, `Listed` are object schemas; `Kit` is a Toolkit that includes `Note`.
466
- * const Note = McpToolkit.unionTool("note", {
467
- * parameters: Schema.Union([AddNote, ListNotes]),
468
- * success: ToolOutputSchema.objectRooted(Schema.Union([Added, Listed])),
469
- * failure: ToolRefusal,
470
- * }).annotate(Tool.Title, "Note")
471
- * const handlers = Kit.toLayer({ note: McpToolkit.unionHandler(Note, (params) => handleNote(params)) })
472
- * ```
473
- */
474
- static readonly unionTool: <const Name extends string, P extends Schema.Decoder<unknown>, S extends Schema.Constraint = typeof Schema.Void, F extends Schema.Constraint = typeof Schema.Never, const Dependencies extends ReadonlyArray<Context.Key<unknown, unknown>> = []>(name: Name, options: UnionToolOptions<P, S, F, Dependencies>) => UnionTool<Name, P, S, F, Context.Service.Identifier<Dependencies[number]>>;
475
- /**
476
- * The handler for a {@link McpToolkit.unionTool}: decodes the raw payload
477
- * exactly as {@link McpToolkit.layer} does, and passes the decoded member
478
- * to `handler`.
479
- *
480
- * @remarks
481
- * A payload that does not decode strictly fails with one
482
- * `McpSchema.InvalidParams`, worded as core words a `Tool.make` decode
483
- * failure, naming every excess key at every depth together with every
484
- * missing or invalid field of the matched member, then one
485
- * `Accepted params at <path>: …` line per level with an unknown key. It
486
- * is the same
487
- * implementation the layer runs, so a handler called directly (a test
488
- * helper, or a toolkit registered through core's `McpServer.toolkit`)
489
- * reports what a client of the layer sees. Under {@link McpToolkit.layer}
490
- * the layer has already rejected a bad call before the handler runs.
491
- * `options.unknownKeyMessage` is deprecated and ignored.
492
- */
493
- static readonly unionHandler: <Name extends string, P extends Schema.Decoder<unknown>, S extends Schema.Constraint, F extends Schema.Constraint, R, A, E, RH>(tool: UnionTool<Name, P, S, F, R>, handler: (params: P["Type"]) => Effect.Effect<A, E, RH>, _options?: UnionHandlerOptions) => ((payload: unknown) => Effect.Effect<A, E | McpSchema.InvalidParams, RH>);
494
- /**
495
- * `McpServer.toolkit`'s registration layer, strict by default.
496
- * `options.unknownKeyMessage` is deprecated and ignored.
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>>;
510
- }
511
- /**
512
- * How a tool's declared failure should read on the wire.
513
- *
514
- * @remarks
515
- * Core sends a declared failure that is an `Error` instance — every
516
- * `Schema.TaggedError` is — as `isError: true` with `error.message` as the only
517
- * text and no `structuredContent` (effect `ai/McpServer.ts`,
518
- * `declaredFailureResult`). So the remediation must be folded into `message`
519
- * when the error is constructed: whatever is not in the message never reaches
520
- * the agent.
521
- *
522
- * Spread {@link ToolFailure.fields} into your tool's `Schema.TaggedError`, build
523
- * `message` with {@link ToolFailure.message}, and pass every caller-supplied
524
- * value through {@link ToolFailure.truncate} before echoing it. For a plain
525
- * refusal with no fields of its own, `ToolRefusal` is this shape ready-made.
526
- *
527
- * Only a declared failure keeps its message: core scrubs an undeclared
528
- * failure or a defect to "Tool execution failed due to an internal server
529
- * error.", so audit every throw path a tool can take and declare each one the
530
- * agent should read.
531
- *
532
- * @example
533
- * ```ts
534
- * import { ToolFailure } from "@effected/mcp";
535
- * import { Schema } from "effect";
536
- *
537
- * class NotFound extends Schema.TaggedError<NotFound>()("NotFound", { ...ToolFailure.fields, id: Schema.String }) {}
538
- * const remediation = { hint: "List the ids first.", suggestedTool: "list_things" }
539
- * new NotFound({ id, remediation, message: ToolFailure.message(`No thing "${ToolFailure.truncate(id)}".`, remediation) })
540
- * ```
541
- *
542
- * @public
543
- */
544
- export declare class ToolFailure {
545
- private constructor();
546
- /** The cap, in UTF-16 code units, on a caller-supplied value echoed into a message. */
547
- static readonly ECHO_LIMIT: number;
548
- /** The larger cap for a value the engine produced (a path, a diagnostic), not one a caller typed. */
549
- static readonly ENGINE_ECHO_LIMIT: number;
550
- /**
551
- * The `message` and `remediation` fields to spread into a consumer's
552
- * `Schema.TaggedError`, each carrying a default `description` that a served
553
- * JSON Schema (a tool's `outputSchema`) shows the agent.
554
- */
555
- static readonly fields: {
556
- readonly message: Schema.String;
557
- readonly remediation: Schema.Struct<{
558
- readonly hint: Schema.String;
559
- readonly suggestedTool: Schema.optionalKey<Schema.String>;
560
- readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
561
- }>;
562
- };
563
- /**
564
- * `"<raw> <hint> Try <suggestedTool>."`, dropping any empty part, so a
565
- * missing tool or an empty hint never leaves a double space.
566
- */
567
- static readonly message: (raw: string, remediation: Remediation) => string;
568
- /**
569
- * `value` cut to `limit` code units with a trailing ellipsis, backing off one
570
- * unit rather than splitting a surrogate pair. A hostile multi-megabyte
571
- * argument is otherwise echoed into the agent's context whole.
572
- */
573
- static readonly truncate: (value: string, limit?: number) => string;
574
- }
575
- /**
576
- * What a {@link ToolOutcome.catchAsEnvelope} case returns for one caught error:
577
- * the same `message` and `remediation` a {@link ToolRefusal} carries, plus any
578
- * structured fields the agent branches on.
579
- *
580
- * @remarks
581
- * `message` is the plain sentence. The hint stays in `remediation`, where it
582
- * reaches the agent as data; unlike a refusal's message, nothing is folded in.
583
- *
584
- * @public
585
- */
586
- interface ToolOutcomeErrorInput {
587
- readonly message: string;
588
- readonly remediation: Remediation;
589
- readonly [field: string]: unknown;
590
- }
591
- /**
592
- * The `ok: false` member of an envelope: `{ ok: false, error: { _tag, message, remediation, ...fields } }`.
593
- *
594
- * @public
595
- */
596
- interface ToolOutcomeFailure<Tag extends string, I extends ToolOutcomeErrorInput> {
597
- readonly ok: false;
598
- readonly error: {
599
- readonly _tag: Tag;
600
- } & I;
601
- }
602
- /**
603
- * The failure values {@link ToolOutcome.catchAsEnvelope} produces for a set of
604
- * cases: one {@link ToolOutcomeFailure} per handled tag.
605
- *
606
- * @public
607
- */
608
- type ToolOutcomeFailures<Cases> = { [K in keyof Cases]: Cases[K] extends ((...args: ReadonlyArray<never>) => infer I) ? I extends ToolOutcomeErrorInput ? ToolOutcomeFailure<K & string, I> : never : never; }[keyof Cases];
609
- /**
610
- * {@link ToolOutcome.envelope}'s options: annotations for the `ok: false`
611
- * member, so a served `outputSchema` can name and describe it.
612
- *
613
- * @public
614
- */
615
- interface ToolOutcomeEnvelopeOptions {
616
- /**
617
- * The failure member's schema identifier. Core's JSON Schema output then
618
- * emits it once under `$defs` by this name and refers to it with `$ref`.
619
- */
620
- readonly identifier?: string;
621
- /**
622
- * The failure member's description, replacing the default one (which says
623
- * that `ok: false` carries an expected error to branch on).
624
- */
625
- readonly description?: string;
626
- }
627
- /**
628
- * The `ok: false` structured-outcome envelope: an expected error an agent
629
- * should branch on, returned on a tool's success channel so it reaches the
630
- * client as `structuredContent` instead of message text.
631
- *
632
- * @remarks
633
- * Two channels, two jobs, one error shape:
634
- *
635
- * - {@link ToolRefusal} is a declared failure: the call itself was wrong, and
636
- * the agent reads one sentence (`isError: true`, message text only; core
637
- * never sends a failure's `structuredContent`).
638
- * - `ToolOutcome` is an expected domain outcome: an id that does not exist, a
639
- * transition that is not allowed. The agent branches on its `_tag` and
640
- * fields (`isError: false`, `structuredContent` carries the envelope).
641
- *
642
- * Both carry {@link ToolFailure.fields} (`message`, `remediation`), so an error
643
- * can move between channels without reshaping.
644
- *
645
- * Success members are not reshaped. Spread {@link ToolOutcome.ok} into each
646
- * success struct so a client can discriminate on `ok`; a member that
647
- * discriminates another way (`found: true | false`) stays legal.
648
- *
649
- * @example
650
- * ```ts
651
- * import { ToolOutcome } from "@effected/mcp"
652
- * import { Effect, Schema } from "effect"
653
- * import { Tool } from "effect/ai"
654
- *
655
- * const NotFound = ToolOutcome.error("GoalNotFound", { id: Schema.Number })
656
- * const Lookup = Tool.make("goal", {
657
- * parameters: Schema.Struct({ id: Schema.Number }),
658
- * success: ToolOutcome.envelope(Schema.Struct({ ...ToolOutcome.ok, goal: Goal }), [NotFound]),
659
- * })
660
- * // in the handler:
661
- * findGoal(id).pipe(
662
- * Effect.map((goal) => ({ ok: true as const, goal })),
663
- * ToolOutcome.catchAsEnvelope({
664
- * GoalNotFound: (e) => ({ id: e.id, message: `No goal ${e.id}.`, remediation: { hint: "List goals first.", suggestedTool: "goal" } }),
665
- * }),
666
- * )
667
- * ```
668
- *
669
- * @public
670
- */
671
- export declare class ToolOutcome {
672
- private constructor();
673
- /** `{ ok: true }` as schema fields, to spread into each success member. */
674
- static readonly ok: {
675
- readonly ok: Schema.Literal<true>;
676
- };
677
- /**
678
- * The schema of one envelope error: `_tag`, {@link ToolFailure.fields} and
679
- * `fields`. A field named `_tag`, `message` or `remediation` is rejected by
680
- * the type, because it would replace the shared one.
681
- */
682
- static readonly error: <const Tag extends string, const Fields extends Schema.Struct.Fields = Record<never, never>>(tag: Tag, fields?: Fields & { readonly [K in "_tag" | "message" | "remediation"]?: never; }) => Schema.Struct<{
683
- readonly _tag: Schema.Literal<Tag>;
684
- } & typeof ToolFailure.fields & Fields>;
685
- /**
686
- * A tool's success schema: `success`, or `{ ok: false, error }` where `error`
687
- * is one of `errors`. Object-rooted with {@link ToolOutputSchema.objectRooted},
688
- * so core serves its `outputSchema` on every protocol revision.
689
- *
690
- * @remarks
691
- * The failure member, its `ok` and its `error` carry default descriptions.
692
- * `options` names the member (`identifier`, served once under `$defs`) or
693
- * replaces its description.
694
- */
695
- static readonly envelope: <S extends Schema.Top, const Errors extends ReadonlyArray<Schema.Top>>(success: S, errors: Errors, options?: ToolOutcomeEnvelopeOptions) => Schema.Union<readonly [S, Schema.Struct<{
696
- readonly ok: Schema.Literal<false>;
697
- readonly error: Schema.Union<Errors>;
698
- }>]>;
699
- /** One `ok: false` value, for a handler that decides on a failure without catching an error. */
700
- static readonly failure: <const Tag extends string, const I extends ToolOutcomeErrorInput>(tag: Tag, error: I) => ToolOutcomeFailure<Tag, I>;
701
- /**
702
- * Turns each handled tagged error into its `ok: false` value on the success
703
- * channel; every other error stays in the error channel. Typed like core's
704
- * `Effect.catchTags`: a case for a tag the effect cannot fail with is a type
705
- * error, and the handled tags leave `E`.
706
- *
707
- * @remarks
708
- * Write it inline in the effect's `pipe`. `E` is inferred from the effect
709
- * being piped, so `effect.pipe(ToolOutcome.catchAsEnvelope({ … }))` checks
710
- * every case name against the effect's real tags. Bound to a `const` first,
711
- * the combinator never sees that effect: `E` infers as `unknown`, the
712
- * unknown-tag check switches off, and a misspelled tag compiles and never
713
- * matches. Core's `catchTags` looks a case up only among `cases`' own keys,
714
- * so an error tagged `"toString"` or `"constructor"` never hits an inherited
715
- * `Object.prototype` member and stays in the error channel.
716
- */
717
- static readonly catchAsEnvelope: <E, Cases extends { [K in Extract<E, {
718
- readonly _tag: string;
719
- }>["_tag"]]+?: (error: Extract<E, {
720
- readonly _tag: K;
721
- }>) => ToolOutcomeErrorInput; } & (unknown extends E ? Record<never, never> : { [K in Exclude<keyof Cases, Extract<E, {
722
- readonly _tag: string;
723
- }>["_tag"]>]: never; })>(cases: Cases) => <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | ToolOutcomeFailures<Cases>, Exclude<E, {
724
- readonly _tag: keyof Cases;
725
- }>, R>;
726
- /**
727
- * Whether `value` is an `ok: false` envelope member: an object whose `ok` is
728
- * exactly `false`. A success member that discriminates some other way, or a
729
- * non-object, is not a failure. Narrows to `{ ok: false }` only: it checks
730
- * the discriminant, not the `error` payload.
731
- */
732
- static readonly isFailure: (value: unknown) => value is {
733
- readonly ok: false;
734
- };
735
- }
736
- /**
737
- * Shaping for a tool's `success` schema, so the `outputSchema` core serves
738
- * is one every client accepts.
739
- *
740
- * @remarks
741
- * MCP requires a tool's `outputSchema` to be rooted in an object, and a
742
- * top-level `Schema.Union` emits a bare `anyOf`. Core drops such an
743
- * `outputSchema` from `tools/list` on the stateful revisions and serves it
744
- * verbatim on the stateless one, where strict clients reject it.
745
- * `McpToolAudit`'s `objectRootedOutput` check reports it either way.
746
- *
747
- * {@link ToolOutputSchema.objectRooted} is the fix when every member of the
748
- * union is an object shape; never apply it to a union with a primitive or
749
- * array member. It works on the Effect
750
- * schema, not on JSON Schema — unlike `ToolInputSchema.objectRooted`, which
751
- * rewrites the served input document of a `Tool.dynamic`.
752
- *
753
- * @public
754
- */
755
- export declare class ToolOutputSchema {
756
- private constructor();
757
- /**
758
- * `schema` with `type: "object"` added beside the `anyOf` at its JSON
759
- * Schema root, through a check that always passes. Decoding, encoding and
760
- * the schema's type are unchanged.
761
- *
762
- * @remarks
763
- * Order against `.annotate({ identifier })` does not matter. A check added
764
- * after an identifier starts a new node that would drop the definition
765
- * name, so the identifier the schema already resolves is carried onto the
766
- * new check, and the served documents are equal either way (key order
767
- * can differ when the schema carries a title or description). Applying it
768
- * twice is a no-op.
769
- *
770
- * Every union member must be an object shape: the added `type: "object"`
771
- * is a claim about the whole union, and nothing checks it. Do not use it
772
- * on a union with a primitive or array member — the served schema would
773
- * contradict the values the tool returns; give such a tool an object
774
- * envelope instead.
775
- *
776
- * @example
777
- * ```ts
778
- * const Result = ToolOutputSchema.objectRooted(Schema.Union([Found, Missing])).annotate({ identifier: "Result" })
779
- * Tool.make("lookup", { parameters, success: Result })
780
- * ```
781
- */
782
- static readonly objectRooted: <S extends Schema.Top>(schema: S) => S;
783
- }
784
- declare const ToolRefusal_base: Schema.Class<ToolRefusal, Schema.TaggedStruct<"ToolRefusal", {
785
- readonly message: Schema.String;
786
- readonly remediation: Schema.Struct<{
787
- readonly hint: Schema.String;
788
- readonly suggestedTool: Schema.optionalKey<Schema.String>;
789
- readonly suggestedArgs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
790
- }>;
791
- }>, import("effect/Cause").YieldableError>;
792
- /**
793
- * A tool call refused for a reason the caller can fix: the kit's ready-made
794
- * declared failure, built on {@link ToolFailure.fields}.
795
- *
796
- * @remarks
797
- * Core scrubs every undeclared failure and every defect to one generic
798
- * internal-error sentence, so a refusal the agent must act on (an unknown id,
799
- * a missing argument, a precondition) reaches it only when the tool declares
800
- * it. Declare `ToolRefusal` in the tool's `failure` schema and fail with
801
- * {@link ToolRefusal.refuse}; its message already carries the remediation,
802
- * because a declared `Error` failure sends `message` and nothing else.
803
- *
804
- * Audit every throw path of a tool: any failure left undeclared loses its
805
- * message on the wire, however carefully it was worded.
806
- *
807
- * **Structured data on a refusal is deliberately unreachable: put it in the
808
- * message.** Core's `McpServer.registerToolkit` builds every failed call's
809
- * result with `structuredContent` unset and no `_meta`, on every protocol
810
- * revision. Under the default `failureMode: "error"` a declared `Error`
811
- * failure sends `message` as the only text; under `failureMode: "return"`
812
- * it sends the whole encoded failure JSON-stringified into the text, still
813
- * with no `structuredContent`, which buries the human message in JSON a
814
- * client must parse. So `refuse` takes no data argument: a list of valid
815
- * names, the root that was searched, the id that was not found all belong
816
- * in `reason`, truncated with {@link ToolFailure.truncate}. When an agent
817
- * must branch on the data itself, return it on the success channel as an
818
- * `ok: false` envelope, where `structuredContent` does reach the client.
819
- *
820
- * @example
821
- * ```ts
822
- * import { ToolFailure, ToolRefusal } from "@effected/mcp";
823
- * import { Tool } from "effect/ai";
824
- *
825
- * const Lookup = Tool.make("lookup", { parameters, success, failure: ToolRefusal })
826
- * // in the handler:
827
- * return yield* ToolRefusal.refuse(`No run "${ToolFailure.truncate(id)}".`, { hint: "List runs first.", suggestedTool: "list_runs" })
828
- * ```
829
- *
830
- * @public
831
- */
832
- export declare class ToolRefusal extends ToolRefusal_base {
833
- /**
834
- * A refusal for `reason`, with `remediation` folded into its message by
835
- * {@link ToolFailure.message}. `reason` must already truncate any
836
- * caller-supplied value it echoes, with {@link ToolFailure.truncate}.
837
- */
838
- static readonly refuse: (reason: string, remediation: Remediation) => ToolRefusal;
839
- }
840
- /**
841
- * The narrow store {@link ToolReplay.wrap} reads and writes: `get` and `set`
842
- * on string values, nothing else.
843
- *
844
- * @remarks
845
- * Core's `KeyValueStore` already satisfies it structurally, so a
846
- * `KeyValueStore` value can be passed as is. An application backed by its
847
- * own table implements just these two methods, instead of stubbing the
848
- * `remove`, `clear`, `size` and `keys` that `KeyValueStore.makeStringOnly` demands.
849
- *
850
- * `get` answers `undefined` for a missing key, the same contract as
851
- * `KeyValueStore.get`. A failure from either method is never a tool error:
852
- * a failed `get` is a miss and a failed `set` is a skipped write, both
853
- * logged at debug level, so the error channel is `unknown`.
854
- *
855
- * @public
856
- */
857
- interface ToolReplayStore {
858
- /** The stored value for `key`, or `undefined` when nothing is stored under it. */
859
- readonly get: (key: string) => Effect.Effect<string | undefined, unknown>;
860
- /** Stores `value` under `key`, replacing any earlier value. */
861
- readonly set: (key: string, value: string) => Effect.Effect<void, unknown>;
862
- }
863
- /**
864
- * What {@link ToolReplay.wrap} needs to know about one tool.
865
- *
866
- * @typeParam Store - The type of {@link ToolReplayOptions.store}; when it is
867
- * omitted, `wrap` requires `KeyValueStore` in `R` instead.
868
- *
869
- * @public
870
- */
871
- interface ToolReplayOptions<P, S extends Schema.Top, Store extends ToolReplayStore | undefined = undefined> {
872
- /** The served tool name. It must not contain `:`, the separator of the stored key ({@link ToolReplay.storeKey}). */
873
- readonly tool: string;
874
- /**
875
- * The tool's success schema. Results are stored encoded through it and
876
- * decoded through it on replay. It must declare {@link ToolReplay.marker} on
877
- * each object member, or core's encoder drops the marker from the reply:
878
- * build each member with {@link ToolReplay.success} so it cannot be left out.
879
- */
880
- readonly success: S;
881
- /**
882
- * The idempotency key for one call; `Option.none()` runs the handler with
883
- * nothing stored or replayed.
884
- *
885
- * @remarks
886
- * The key must identify the request completely. A replay compares nothing
887
- * else: not the params, not the schema version. So a reused key with
888
- * different params returns the FIRST call's result. When the params under
889
- * one caller-supplied id can vary, fold a digest of them into the key.
890
- */
891
- readonly key: (params: P) => Option.Option<string>;
892
- /**
893
- * Where results are stored. Omitted, `wrap` reads core's `KeyValueStore`
894
- * from `R`; given, it uses this store and `KeyValueStore` leaves `R`.
895
- */
896
- readonly store?: Store;
897
- }
898
- /**
899
- * Idempotent replay for a write tool: a repeated call with the same key returns
900
- * the first call's stored result, marked `_idempotentReplay: true`, instead of
901
- * running the mutation again.
902
- *
903
- * @remarks
904
- * The store is core's `KeyValueStore` (`effect/persistence`), required in `R`
905
- * (`KeyValueStore.layerMemory`, `layerFileSystem` or `layerSql`), unless
906
- * {@link ToolReplayOptions.store} supplies a {@link ToolReplayStore}: just
907
- * `get` and `set`, for an application's own table. Then `R` carries no
908
- * `KeyValueStore`.
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
- *
917
- * Semantics, per call:
918
- *
919
- * - A key of `Option.none()` runs the handler; nothing is read or stored.
920
- * - A stored value that decodes through `success` is returned with the marker,
921
- * and the handler does not run.
922
- * - A miss runs the handler and stores its result, best-effort. A store that
923
- * fails, a row that no longer decodes (written under an older schema, or
924
- * corrupt), and a failed write are each a miss or a skipped write, never a
925
- * tool error. Each is logged at debug level with the tool name.
926
- * - A handler failure stores nothing, so a retry runs it again.
927
- * - An `ok: false` result (a {@link ToolOutcome} envelope failure) is never
928
- * stored: a "not found" must not replay after the thing exists.
929
- *
930
- * This is replay, not exactly-once: two concurrent calls with one key both
931
- * miss and both run.
932
- *
933
- * The key is the whole identity of a request. A hit compares neither the
934
- * params nor the schema version (a row written under an older schema replays
935
- * whenever it still decodes), and rows never expire: the store keeps a result
936
- * until something removes it. A key reused with different params therefore
937
- * replays the first result, so {@link ToolReplayOptions.key} must fold in a
938
- * digest of any params that can vary under one id.
939
- *
940
- * Wrap it inside {@link ToolOutcome.catchAsEnvelope}, so the envelope sees the
941
- * handler's real errors and replay sees only real successes.
942
- *
943
- * @example
944
- * ```ts
945
- * import { ToolReplay } from "@effected/mcp"
946
- * import { Option, Schema } from "effect"
947
- *
948
- * const Created = ToolReplay.success(Schema.Struct({ ok: Schema.Literal(true), id: Schema.Number }))
949
- * const create = ToolReplay.wrap({
950
- * tool: "goal_create",
951
- * success: Created,
952
- * key: (params: { readonly requestId?: string }) => Option.fromUndefinedOr(params.requestId),
953
- * })(createGoal)
954
- * ```
955
- *
956
- * @public
957
- */
958
- export declare class ToolReplay {
959
- private constructor();
960
- /**
961
- * The marker field, to spread into each object member of a replayable
962
- * success schema. {@link ToolReplay.success} adds it for you.
963
- */
964
- static readonly marker: {
965
- readonly _idempotentReplay: Schema.optionalKey<Schema.Literal<true>>;
966
- };
967
- /**
968
- * A struct with {@link ToolReplay.marker} added. Use it for each object
969
- * success member, so core's encoder keeps the marker on a replayed reply.
970
- *
971
- * @remarks
972
- * Built with core's `fieldsAssign`, which, like `mapFields`, does not carry
973
- * the struct's own `.check(...)` refinements over: apply checks to the
974
- * result, not to the struct passed in.
975
- */
976
- static readonly success: <const Fields extends Schema.Struct.Fields>(schema: Schema.Struct<Fields>) => Schema.Struct<Struct.Simplify<Struct.Assign<Fields, typeof ToolReplay.marker>>>;
977
- /**
978
- * The `KeyValueStore` key one call is stored under: `<tool>:<key>`. Split on
979
- * the first `:` to recover both parts; MCP tool names are `[A-Za-z0-9_.-]`.
980
- */
981
- static readonly storeKey: (tool: string, key: string) => string;
982
- /**
983
- * Wraps a handler with replay. Throws at wrap time when `tool` contains
984
- * `:`, a wiring mistake, so it surfaces when the server is built.
985
- */
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"]>);
987
- }
988
- export type { FormatUnknownKeysOptions, McpLaunchOptions, McpStdioOptions, McpStdioRegistrationServices, McpToolkitOptions, ToolOutcomeEnvelopeOptions, ToolOutcomeErrorInput, ToolOutcomeFailure, ToolOutcomeFailures, ToolReplayOptions, ToolReplayStore, UnionHandlerOptions, UnionTool, UnionToolOptions, UnknownKeysLevel };
19
+ export declare const MCP_VERSION: string;
20
+ export type { MainOptions };
989
21
  //# sourceMappingURL=index.d.ts.map