--- name: effect-v4-mcp description: "Use when building, wiring, testing or reviewing an MCP server on Effect v4 — effect/ai's McpServer, Tool and Toolkit plus the @effected/mcp boundary that keeps stdout the JSON-RPC wire and makes tool failures readable to an agent. Also use when: MCP server, McpServer, Tool.make, Toolkit, layerStdio, tools/call, server/discover, initialize, Tool.Strict, unknown keys, isError, structuredContent, InvalidParams, MCP resource, mimeType, stdio server exits 130, JSON-RPC on stdout, McpHarness, McpProbe, crash guard" --- # Effect v4 MCP servers Core owns the protocol, tool registration and the wire format: `effect/ai`'s `McpServer`, `Tool` and `Toolkit` parse requests, serve JSON Schema, and shape a `tools/call` result. `@effected/mcp` owns the stdio boundary — keeping every log line and every crash report off stdout, which is the JSON-RPC wire — plus tool-failure shaping and strict-by-default registration. `@effected/mcp/testing` owns the in-process and spawned test clients. | construct | import | reach for it when | | --- | --- | --- | | `McpServer`, `Tool`, `Toolkit`, `McpSchema` | `effect/ai` | declaring the server, its tools, and the protocol-level schemas | | `McpStdio` | `@effected/mcp` | launching a stdio server without a stray log line or crash report reaching the wire | | `McpToolkit` | `@effected/mcp` | registering a toolkit strict-by-default; core's strict decode names every bad key and field in one response, and the layer appends each offending level's accepted params | | `ToolFailure` | `@effected/mcp` | folding remediation into a declared failure's message — core sends only `error.message` | | `ToolInputSchema` | `@effected/mcp` | naming unknown keys in a `Tool.dynamic` tool's raw payload, inside its own handler — core never validates a raw JSON Schema | | `McpToolkit.unionTool` / `unionHandler` | `@effected/mcp` | a tool whose parameters are a `Schema.Union` of objects, rejected like a `Tool.make` decode failure | | `McpGuard` | `@effected/mcp/guard` | crash guards installed before the server graph loads, with an exit-before-connect policy | | `ProcessGuard` | `@effected/engine/guard` | the same guards for a server on another transport (an LSP), which reports `markConnected` itself | | `Remediation`, `LaunchContext` | `@effected/engine` | a structured `{ hint, suggestedTool? }` shape, or resolving an agent-launched project directory | | `McpHarness`, `McpProcess`, `McpProbe`, `McpToolAudit` | `@effected/mcp/testing` | testing the server layer in process, a spawned child, a packed install, or auditing what `tools/list` actually serves | ## Standards - Launch with `McpStdio.launch` and `NodeRuntime.runMain(program, { teardown: McpStdio.teardown })` — never a bare `Layer.launch`. - Register toolkits through `McpToolkit.layer`, not core's `McpServer.toolkit` directly, so every tool is strict unless it opts out. - Fold remediation into a declared failure's message with `ToolFailure` at construction — nothing else reaches the agent. - Declare every service a handler uses in `Tool.make`'s `dependencies` option. - Test the server layer in process with `McpHarness`; test a **built** bin with `McpProcess`/`McpProbe`. ## Footguns - Hand-wiring `McpServer.layerStdio` without `McpStdio.layer` leaves a malformed line unanswered — core skips it silently where JSON-RPC requires a `-32700`/`-32600` reply — and one `{"jsonrpc":"2.0","method":"@effect/rpc/Eof"}` line from a client silently stops the server — see [The stdin guard](./references/server-wiring.md#stdin-guard). - Two stdio servers in one process share one stdio protocol and one tool registry unless each **whole** bundle (toolkit + server) is wrapped in `Layer.fresh`: merged or nested without it, the second never reads its own stdin; `Layer.fresh` around the server alone, toolkit outside, serves no tools — see [`McpStdio.layer`](./references/server-wiring.md#mcpstdiolayer). - `runMain`'s own failure report runs outside anything the program provides and lands on stdout, the wire — see [`McpStdio.launch`](./references/server-wiring.md). - Stdin EOF interrupts the main fiber; the default teardown exits `130` — see [`McpStdio.teardown`](./references/server-wiring.md). - A declared failure reaches the agent as message text only, never `structuredContent`, under either failure mode, so `ToolRefusal.refuse` takes no data argument — see [Failures on the wire](./references/tools.md#failures-on-the-wire). - `injectCrash: { at: "connected" }` reports on a later tick, after the first responses a test may read; wait with `McpProcess.stderrUntil`, never one `stderrSoFar` read — see [`McpGuard.run`](./references/server-wiring.md#mcpguardrun-packages-both-policies). - `InvalidParams` moves to an `isError` tool result on the newer protocol revisions only for a known tool's own bad parameters — an unknown tool or non-object `arguments` stays a JSON-RPC error on every revision — see [Failures on the wire](./references/tools.md#failures-on-the-wire). - A top-level union `parameters` schema dies the server at registration, not at the first call — use `McpToolkit.unionTool` — see [Failures on the wire](./references/tools.md#failures-on-the-wire). - A pattern-keyed `Schema.Record` parameter is served open unless its key RegExp has the `u` flag, while core still rejects a non-matching key — see [Strict input](./references/tools.md#strict-input). - A `Schema.String` success is sent as raw text with no `structuredContent` on the stateful revisions, and as JSON-quoted text plus a string `structuredContent` on `2026-07-28` — see [Failures on the wire](./references/tools.md#failures-on-the-wire). - `Schema.Struct({})` is not `Tool.EmptyParams` — it fails server registration outright — see [Defining a tool](./references/tools.md#defining-a-tool). - Closing stdin does not cancel requests in flight: the server answers them, then stops — so `McpHarness.close` never releases a wait on a request that never completes; `McpHarness.stop` does — see [`McpStdio.teardown`](./references/server-wiring.md). - A bare-string resource `content` loses its `mimeType` on the read itself, even though it still appears in `resources/list` — see [The `mimeType` trap](./references/resources.md#the-mimetype-trap). - A resource URI template variable cannot span a slash — an id containing one needs a static resource per id, not a template — see [A template variable cannot span a slash](./references/resources.md#a-template-variable-cannot-span-a-slash). - An `Effect.timeout` guard inside `it.effect` never fires — `TestClock` never advances on its own, so the test hangs until vitest's own default timeout kills it instead. Use `it.live` or a real-clock `layer(...)` — see [Timeouts](./references/testing.md#timeouts). - Code placed after a completed `Effect.provide` of a stdio server never runs — core's stdio protocol interrupts the fiber that built it once the server stops, so a hand-written test provides-then-asserts and silently checks nothing. Use `McpHarness` — see [The in-process harness](./references/testing.md#the-in-process-harness). ## Additional resources - [server-wiring.md](./references/server-wiring.md) — the complete `main.ts`, `McpStdio.layer`/`launch`/`teardown`, protocol ordering, crash guards, and launch-context project-directory resolution. Load when: assembling or reviewing a server's `main.ts`, or debugging why a failure or a log line reached the wire. - [tools.md](./references/tools.md) — defining a tool, strict input reporting, failures on the wire, and the `ok: false` structured-remediation envelope. Load when: declaring a `Tool.make`, wiring a `Toolkit`, or a client is seeing the wrong failure shape. - [resources.md](./references/resources.md) — `McpServer.resource`'s single and URI-template forms, the `mimeType` trap, and why a slash-containing id needs a static resource instead of a template. Load when: registering an MCP resource, or a client's read is missing a `mimeType` it expects. - [testing.md](./references/testing.md) — the in-process harness, the protocol matrix, spawned clients, the packed-install proof, and the tool audit. Load when: writing a test against an MCP server, or choosing between `McpHarness`, `McpProcess`, `McpProbe` and `McpToolAudit`. - `effected-packages`' [mcp.md](../effected-packages/references/mcp.md) — the `@effected/mcp` package surface as a routing reference (import table, full API, Usage block). Load when: you need the package-level index rather than the teaching depth here. Anchors in this skill and its references cite the vendored tag at `.repos/effect/packages/effect/src/`; a consumer without that tree searches `node_modules/effect/src` by symbol name instead of by line number.