Skip to content
// EFFECT DAYS 2026

McpServer unstable

Builds Model Context Protocol (MCP) servers with Effect.

The McpServer service stores the tools, resources, resource templates, prompts, completions, and outgoing notifications exposed by a server. This module also includes the server runner, custom protocol, stdio, and HTTP layers, registration helpers, and APIs that let handlers ask the connected client for structured input or read its advertised capabilities.

39 exports Since v4.0.0 Source

Accessors

Since v4.0.0

Accesses the current client's capabilities.

Signature

declare const clientCapabilities: Effect.Effect<ClientCapabilities, never, McpRequestContext>

elicit

unstable
Since v4.0.0

Collects structured input from the current MCP client and decodes the accepted response with schema.

Details

Accepted content is decoded with the supplied schema, declined requests fail with ElicitationDeclined, and canceled requests interrupt the effect.

Signature

declare const elicit: <S extends Schema.ConstraintEncoder<Record<string, unknown>, unknown>>(options: {
readonly message: string;
readonly schema: S;
}) => Effect.Effect<S["Type"], ElicitationDeclined, McpServerClient | S["DecodingServices"]>

Decoding

Since v4.0.0

Decodes an Mcp-Name header, including base64-wrapped values. Returns undefined for malformed values.

Details

Use in HttpRouter.middleware to identify the tool, prompt or resource targeted by a 2026-07-28 request. Earlier protocols carry the name only in the JSON-RPC body.

Signature

declare function decodeNameHeader(value: string): string | undefined

Handlers

Since v4.0.0

Registers an MCP prompt from an Effect program.

Details

Decodes parameters and encodes completion suggestions using their schemas. String content becomes a user text message.

See

  • prompt for the layer-based prompt registration wrapper

Signature

declare function registerPrompt<E, R, Params extends Fields = {}, Completions extends { [K in string | number | symbol]: (input: string, context: {
readonly arguments?: {
[key: string]: string;
};
} | undefined) => Effect<Array<Params[K]["Type"]>, any, any> } = {}>(options: {
readonly annotations?: Context<never>;
readonly completion?: ValidateCompletions<Completions, Extract<keyof Params, string>>;
readonly content: (params: View<Params>) => Effect<string | Array<PromptMessage>, E, R>;
readonly description?: string;
readonly name: string;
readonly parameters?: Params;
readonly title?: string;
}): Effect<void, never, PromptRequirements<Params, R, Completions>>
Since v4.0.0

Registers an MCP resource or resource template from an Effect program.

See

  • resource for the layer-based resource registration wrapper

Signature

declare const registerResource: {
<E, R>(options: {
readonly annotations?: Context.Context<never>;
readonly audience?: ReadonlyArray<"user" | "assistant">;
readonly content: Effect.Effect<ReadResourceResult | string | Uint8Array, E, R>;
readonly description?: string;
readonly mimeType?: string;
readonly name: string;
readonly priority?: number;
readonly uri: string;
}): Effect<void, never, ResourceRequirements<R>>;
<Schemas extends readonly Array<Constraint>>(segments: TemplateStringsArray, ...schemas: Schemas): <E, R, Completions extends Partial<ResourceCompletions<Schemas>> = {}>(options: {
readonly annotations?: Context.Context<never>;
readonly audience?: ReadonlyArray<"user" | "assistant">;
readonly completion?: ValidateCompletions<Completions, keyof ResourceCompletions<Schemas>>;
readonly content: (uri: string, ...params: { [K in keyof Schemas]: Schemas[K]["Type"] }) => Effect.Effect<ReadResourceResult | string | Uint8Array, E, R>;
readonly description?: string;
readonly mimeType?: string;
readonly name: string;
readonly priority?: number;
}) => Effect<void, never, ResourceTemplateRequirements<Schemas, R, Completions>>;
}
Since v4.1.1

Registers an Agent Skill under the MCP Skills extension (SEP-2640).

Details

Each file is registered as a resource, and the skill is listed by skills/list and skills/get with a manifest of every file's SHA-256 digest and size, computed from the bytes served. Every directory of the skill namespace is readable with resources/directory/read. The server declares the extension once any skill is registered.

Fails when the skill breaks the extension's rules: a name that does not match the URI, a SKILL.md without YAML frontmatter, frontmatter that is not valid McpSchema.SkillFrontmatter, a file path that leaves the skill, or more than 512 files or 16 MiB in all. It also fails when the skill's directory contains or lies inside another skill's, or one of its files is already registered as a resource. Registering a resource at the URI of a skill's file afterwards is a defect.

SHA-256 runs on crypto.subtle.

See

  • skill for the layer-based skill registration wrapper

Signature

declare const registerSkill: (options: SkillOptions) => Effect.Effect<void, Cause.IllegalArgumentError, McpServer>
Since v4.0.0

Registers a Toolkit with the McpServer.

Details

The MCP middleware in the current context, provided with Effect.provide(middleware.layer), runs around every call to the toolkit.

See

Signature

declare const registerToolkit: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>) => Effect.Effect<void, never, ToolkitRequirements<Tools>>

Layers

layer

unstable
Since v4.0.0

Creates a layer that starts an MCP server over an existing RpcServer.Protocol and provides McpServer. Request handlers receive McpRequestContext; initialized legacy requests additionally receive McpServerClient.

When to use

Use when you already have a custom or externally provided RpcServer.Protocol and want to start an MCP server as part of a layer graph.

Details

The returned layer forks run(options) in the layer scope and merges McpServer.layer, so registration layers can use the McpServer service while the server is running.

Pass registration layers as registrations to build them before the server reads its first request. The transport discharges the request-level dependencies it supplies (see ProvidedServices); any other McpRequest marker stays in the layer's requirements and must be met by MCP middleware.

Gotchas

Unlike layerStdio and layerHttp, this layer does not install a concrete transport. The surrounding layer graph must provide RpcServer.Protocol.

See

  • run for the effect form used by this layer
  • layerStdio for a stdio-backed layer that installs the MCP protocol and NDJSON-RPC serialization
  • layerHttp for an HTTP-backed layer that registers with HttpRouter and installs JSON-RPC serialization

Signature

declare function layer<Protocols extends readonly [ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>, ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>], RA = never, RE = never, RR = never>(options: {
readonly cacheHints?: CacheHints;
readonly description?: string;
readonly extensions?: {
[key: `${string}/${string}`]: unknown;
};
readonly icons?: readonly Array<Icon>;
readonly instructions?: string;
readonly name: string;
readonly protocols: Protocols;
readonly registrations?: Layer<RA, RE, RR>;
readonly version: string;
readonly websiteUrl?: string;
}): Layer<McpServer | McpRequest<"Requires", Scope> | From<"Requires", Protocols[number] extends StatefulProtocolAdapter ? McpServerClient : never>, IllegalArgumentError | RE, Protocol | Exclude<Without<RR>, McpServer> | From<"Requires", Exclude<Only<"Requires", RR>, ProvidedServices<Protocols, false>>>>

layerHttp

unstable
Since v4.0.0

Registers a Streamable HTTP MCP endpoint at options.path.

When to use

Use to expose an MCP server through an existing HttpRouter.

Details

POST serves JSON-RPC and accepted notification-only requests return 202. Header mismatches, unsupported protocol versions, and invalid session-header usage return JSON-RPC errors with status 400. Unknown modern RPC methods return a JSON-RPC method-not-found error with status 404. Session requests without MCP-Protocol-Version use the version negotiated at initialization. Unsupported HTTP methods return 405. Requests with an Origin header are rejected unless the exact origin appears in allowedOrigins; Origin-less non-browser clients remain valid. The surrounding HTTP server remains responsible for binding to an appropriate interface and installing authentication.

With allowSessionTermination, a DELETE carrying Mcp-Session-Id ends that session with 204 and interrupts its in-flight requests; later requests with that id get 404. DELETE validates the session and MCP-Protocol-Version headers like POST. Without the option, or when only sessionless revisions such as v2026_07_28 are configured, DELETE returns 405. Any caller holding a session id can end that session, so authenticate requests in the surrounding router.

Set allowSubscriptions: false to disable v2026_07_28 subscriptions. Discovery advertises listChanged: false, and subscriptions/listen returns Method not found. Request-scoped progress and logs, legacy protocols, and default subscription behavior are unchanged.

The layer runs one long-lived MCP server that every POST shares. For serverless hosts where execution must belong to each request, use layerHttpRequestScoped instead.

Pass registration layers as registrations to build them before the endpoint serves its first request. The transport discharges the request-level dependencies it supplies (see ProvidedServices) and converts the remaining McpRequest markers into HttpRouter.Request markers, so HttpRouter.middleware can provide them, for example to authenticate the request and resolve the current user.

layerHttp always implements the single-endpoint Streamable HTTP topology. Using v2024_11_05 here is a custom compatibility transport for that revision's schema. It does not implement the historical two-endpoint HTTP+SSE transport, GET SSE, event resumption, or session expiry.

Gotchas

This layer starts a scoped server fiber. For sharing it across Cloudflare Workers requests, see HttpRouter.toWebHandler for setup and limitations.

See

  • layerStdio for exposing the server over stdio
  • layer for the base MCP server layer without a transport protocol

Signature

declare function layerHttp<Protocols extends readonly [ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>, ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>], RA = never, RE = never, RR = never>(options: {
readonly allowedOrigins?: readonly Array<string>;
readonly allowSessionTermination?: boolean;
readonly allowSubscriptions?: boolean;
readonly cacheHints?: CacheHints;
readonly description?: string;
readonly extensions?: {
[key: `${string}/${string}`]: unknown;
};
readonly icons?: readonly Array<Icon>;
readonly instructions?: string;
readonly name: string;
readonly path: PathInput;
readonly protocols: Protocols;
readonly registrations?: Layer<RA, RE, RR>;
readonly version: string;
readonly websiteUrl?: string;
}): Layer<McpServer | McpRequest<"Requires", Scope> | McpRequest<"Requires", HttpServerRequest> | From<"Requires", Protocols[number] extends StatefulProtocolAdapter ? McpServerClient : never>, IllegalArgumentError | RE, HttpRouter | Exclude<Without<RR>, McpServer> | From<"Requires", Exclude<Only<"Requires", RR>, ProvidedServices<Protocols, true>>>>

Registers a request-scoped Streamable HTTP MCP endpoint at options.path.

When to use

Use when serving on serverless hosts such as Cloudflare Workers, where execution must belong to the HTTP request rather than a shared long-lived server.

Details

Each POST runs its own server in the request scope, with no background fiber, timer, or queue consumer left after the response body completes. Only sessionless protocols such as v2026_07_28 are accepted. Tools, resources, prompts, completions, multi round-trip input requests, and the request's own progress and log notifications are supported. Legacy reverse requests through McpServer.elicit are not available.

Discovery advertises listChanged: false, subscriptions/listen returns Method not found, and DELETE returns 405. Request-scoped clients receive no broadcast notifications such as list changes. Those still reach clients of a layerHttp endpoint sharing the same McpServer, and are dropped when no such endpoint exists. Use layerHttp on a long-lived host for sessions, subscriptions, or legacy reverse requests.

HTTP admission, origin checks, and JSON-RPC errors follow layerHttp. Configuring a sessionful protocol through an untyped caller fails layer construction with IllegalArgumentError.

Registrations passed as registrations are built once, before the routes are added, and shared by every POST. Remaining McpRequest markers become HttpRouter.Request markers, as with layerHttp.

See

  • layerHttp for the long-lived HTTP transport and HTTP admission policy

Signature

declare function layerHttpRequestScoped<Protocols extends readonly [ProtocolAdapter<ProtocolVersion, StatelessRuntimeDescriptor>, ProtocolAdapter<ProtocolVersion, StatelessRuntimeDescriptor>], RA = never, RE = never, RR = never>(options: {
readonly allowedOrigins?: readonly Array<string>;
readonly cacheHints?: CacheHints;
readonly description?: string;
readonly extensions?: {
[key: `${string}/${string}`]: unknown;
};
readonly icons?: readonly Array<Icon>;
readonly instructions?: string;
readonly name: string;
readonly path: PathInput;
readonly protocols: Protocols;
readonly registrations?: Layer<RA, RE, RR>;
readonly version: string;
readonly websiteUrl?: string;
}): Layer<McpServer | McpRequest<"Requires", Scope> | McpRequest<"Requires", HttpServerRequest> | From<"Requires", Protocols[number] extends StatefulProtocolAdapter ? McpServerClient : never>, IllegalArgumentError | RE, HttpRouter | Exclude<Without<RR>, McpServer> | From<"Requires", Exclude<Only<"Requires", RR>, ProvidedServices<Protocols, true>>>>

layerStdio

unstable
Since v4.0.0

Creates a layer that runs an MCP server over standard input and output.

When to use

Use when an MCP client launches the server as a subprocess and communicates through newline-delimited JSON-RPC messages.

Details

The selected protocol adapter controls the dated RPC schemas and JSON-RPC batch policy. The layer provides McpServer, supplies request-scoped McpRequestContext and legacy McpServerClient services to handlers, and requires Stdio.

Pass registration layers as registrations to build them before the server reads its first request. The transport discharges the request-level dependencies it supplies (see ProvidedServices); any other McpRequest marker stays in the layer's requirements and must be met by MCP middleware.

See

  • layer for running over an existing RpcServer.Protocol
  • layerHttp for the single-endpoint HTTP transport

Signature

declare function layerStdio<Protocols extends readonly [ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>, ProtocolAdapter<ProtocolVersion, RuntimeDescriptor>], RA = never, RE = never, RR = never>(options: {
readonly cacheHints?: CacheHints;
readonly description?: string;
readonly extensions?: {
[key: `${string}/${string}`]: unknown;
};
readonly icons?: readonly Array<Icon>;
readonly instructions?: string;
readonly name: string;
readonly protocols: Protocols;
readonly registrations?: Layer<RA, RE, RR>;
readonly version: string;
readonly websiteUrl?: string;
}): Layer<McpServer | McpRequest<"Requires", Scope> | From<"Requires", Protocols[number] extends StatefulProtocolAdapter ? McpServerClient : never>, IllegalArgumentError | RE, Stdio | Exclude<Without<RR>, McpServer> | From<"Requires", Exclude<Only<"Requires", RR>, ProvidedServices<Protocols, false>>>>

prompt

unstable
Since v4.0.0

Creates a layer that registers an MCP prompt.

Details

Services read by the content and completion handlers are request-level, so providing them with Layer.provide doesn't satisfy them. Read construction-time services in an enclosing Layer.unwrap and close over them, and provide per-request services with MCP middleware.

See

Signature

declare function prompt<E, R, Params extends Fields = {}, Completions extends { [K in string | number | symbol]: (input: string, context: {
readonly arguments?: {
[key: string]: string;
};
} | undefined) => Effect<Array<Params[K]["Type"]>, any, any> } = {}>(options: {
readonly annotations?: Context<never>;
readonly completion?: ValidateCompletions<Completions, Extract<keyof Params, string>>;
readonly content: (params: View<Params>) => Effect<string | Array<PromptMessage>, E, R>;
readonly description?: string;
readonly name: string;
readonly parameters?: Params;
readonly title?: string;
}): Layer<never, never, PromptRequirements<Params, R, Completions>>

resource

unstable
Since v4.0.0

Creates a layer that registers an MCP resource or resource template.

Details

Services read by the content and completion handlers are request-level, so providing them with Layer.provide doesn't satisfy them. Read construction-time services in an enclosing Layer.unwrap and close over them, and provide per-request services with MCP middleware.

See

Signature

declare const resource: {
<E, R>(options: {
readonly audience?: ReadonlyArray<"user" | "assistant">;
readonly content: Effect.Effect<ReadResourceResult | string | Uint8Array, E, R>;
readonly description?: string;
readonly mimeType?: string;
readonly name: string;
readonly priority?: number;
readonly uri: string;
}): Layer<never, never, ResourceRequirements<R>>;
<Schemas extends readonly Array<Constraint>>(segments: TemplateStringsArray, ...schemas: Schemas): <E, R, Completions extends Partial<ResourceCompletions<Schemas>> = {}>(options: {
readonly audience?: ReadonlyArray<"user" | "assistant">;
readonly completion?: ValidateCompletions<Completions, keyof ResourceCompletions<Schemas>>;
readonly content: (uri: string, ...params: { [K in keyof Schemas]: Schemas[K]["Type"] }) => Effect.Effect<ReadResourceResult | string | Uint8Array, E, R>;
readonly description?: string;
readonly mimeType?: string;
readonly name: string;
readonly priority?: number;
}) => Layer<never, never, ResourceTemplateRequirements<Schemas, R, Completions>>;
}

skill

unstable
Since v4.1.1

Creates a layer that registers an Agent Skill under the MCP Skills extension (SEP-2640).

See

Signature

declare function skill(options: SkillOptions): Layer<never, IllegalArgumentError, McpServer>

Example Publishing a skill with a supporting file

import { Layer } from "effect"
import { McpProtocol, McpServer } from "effect/ai"
const RefundsSkill = McpServer.skill({
uri: "skill://billing/refunds/SKILL.md",
files: {
"SKILL.md": `---
name: refunds
description: Process refunds using the billing tools
---
Read references/policy.md before issuing a refund.
`,
"references/policy.md": "Refunds above CHF 500 require manager approval.\n"
}
})
const ServerLayer = RefundsSkill.pipe(
Layer.provide(McpServer.layerHttp({
name: "billing",
version: "1.0.0",
path: "/mcp",
protocols: [McpProtocol.v2026_07_28, McpProtocol.v2025_11_25]
}))
)

toolkit

unstable
Since v4.0.0

Creates a layer that registers a Toolkit with the McpServer.

Details

The layer requires McpServer. Pass it to a transport through the registrations option, or provide the transport layer to it.

See

Signature

declare function toolkit<Tools extends Record<string, Any>>(toolkit: Toolkit<Tools>): Layer<never, never, ToolkitRequirements<Tools>>

Middleware

middleware

unstable
Since v4.0.0

Creates MCP middleware that runs around tools/call, resources/read, prompts/get and completion/complete requests.

Details

Middleware is how request-level dependencies are satisfied: declare the services it provides with middleware<{ provides: S }>() and provide them to the handler effect. It receives the request headers, session id and request context, so it can resolve the current user per request.

Pass a function, or an Effect that builds the function. Services the Effect reads are requirements of the middleware layer. Services the function reads per request, other than McpRequestContext, are per-request requirements of the result; see Middleware for how they are met.

Failures must be McpErrorBase values. A middleware failure is sent as a JSON-RPC error with its code, message and data, never as a tool result. Pick custom codes from the implementation-defined range (-32000 to -32019): any numeric code is sent as is. Defects are logged, reported to the ErrorReporter and sent as InternalError("Internal error"). Interruption propagates.

Local middleware runs inside global middleware, and middleware provided both locally and globally runs once. Middleware never runs for initialize. It runs for list requests only when it wraps a visibility, so that the visibility check can read the services it provides.

Signature

declare const middleware: middleware.Make<never> & <Config extends {
provides?: any;
} = {}>() => middleware.Make<Config extends {
provides: infer P;
} ? P : never>

Example Resolving the current user per request

import { Context, Effect, Layer, Schema } from "effect"
import { McpSchema, McpServer, Tool, Toolkit } from "effect/ai"
class CurrentUser extends Context.Service<CurrentUser, { readonly id: string }>()("CurrentUser") {}
class Unauthorized extends Schema.Error<Unauthorized>("Unauthorized")({
...McpSchema.McpErrorBase.fields,
_tag: Schema.tag("Unauthorized"),
code: Schema.tag(-32001)
}) {}
const WhoAmI = Tool.make("WhoAmI", { success: Schema.String, dependencies: [CurrentUser] })
const Tools = Toolkit.make(WhoAmI)
const Auth = McpServer.middleware<{ provides: CurrentUser }>()((effect, { headers }) => {
const token = headers["authorization"]
return token === undefined
? Effect.fail(new Unauthorized({ message: "Missing credentials" }))
: Effect.provideService(effect, CurrentUser, { id: token })
})
const Registrations = McpServer.toolkit(Tools).pipe(
Layer.provide(Auth.layer),
Layer.provide(Tools.toLayer({
WhoAmI: () => Effect.map(CurrentUser, (user) => user.id)
}))
)

middleware

unstable
Since v4.0.0

Types used by middleware.

Middleware

interface unstable
Since v4.0.0

Composable MCP middleware.

Details

Provide layer to registration layers, or to the Effect form of a registration, to run the middleware for those registrations. Use layerGlobal to run it for every request on the server.

Services the middleware reads per request are McpRequest<"Requires", S> requirements of layer and layerGlobal, like the request-level dependencies of a registration. A middleware that provides S meets them when it runs outside this one: a.combine(b) runs b outside a, and so does providing b.layer or b.layerGlobal outside a.layer, though layerGlobal installs b for the whole server. The transport discharges the services it supplies, and layerHttp turns the rest into HttpRouter.Request requirements that an HttpRouter.middleware can meet.

See

Signature

interface Middleware<Config extends MiddlewareConfig> {
readonly "~effect/ai/McpServer/Middleware": Config;
readonly combine: <Config2 extends MiddlewareConfig>(other: Middleware<Config2>) => Middleware<{
error: Config2["error"] | Config["error"];
layerError: Config["layerError"] | Config2["layerError"];
layerRequires: Config["layerRequires"] | Config2["layerRequires"];
provides: Config["provides"] | Config2["provides"];
requires: Exclude<Config["requires"], Config2["provides"]> | Config2["requires"];
}>;
readonly layer: MiddlewareLayer<Config, never>;
readonly layerGlobal: MiddlewareLayer<Config, McpServer>;
}

MiddlewareMethod

type unstable
Since v4.0.0

The request methods that run MCP middleware. More methods may be added, so don't switch on it exhaustively without a default case.

Signature

type MiddlewareMethod = "tools/call" | "resources/read" | "prompts/get" | "completion/complete" | "tools/list" | "resources/list" | "resources/templates/list" | "prompts/list" | "skills/list" | "skills/get" | "resources/directory/read"

MiddlewareOptions

interface unstable
Since v4.0.0

Request metadata passed to MCP middleware.

Details

target is the tool name, the requested resource URI, or the prompt name. For completion/complete it is the prompt name, or the URI template of the resource template being completed. For skills/get it is the URI of the skill's SKILL.md. payload holds the request parameters.

On list requests, middleware runs only when it wraps a visibility, once for each registration it wraps. target is then the tool name, the resource URI, the URI template or the prompt name of that registration, and payload is undefined. skills/list and resources/directory/read are list requests whose target is the SKILL.md URI of each skill; a directory read checks only the skills that have the directory. A skill's files are resources, checked as resources/list and resources/read with their own URIs.

headers are the HTTP request headers. They are empty on stdio, on custom transports and for in-process calls. sessionId is the Mcp-Session-Id header, so it is undefined outside sessionful HTTP.

Signature

interface MiddlewareOptions {
readonly headers: Headers;
readonly method: MiddlewareMethod;
readonly payload: unknown;
readonly requestContext: {
readonly clientCapabilities: ClientCapabilities;
readonly clientId: number;
readonly clientInfo?: Implementation;
readonly inputResponses?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
readonly protocolVersion: string;
readonly requestMetadata?: Readonly<Record<string, unknown>> | {
[key: string]: unknown;
readonly progressToken?: string | number;
};
readonly requestState?: string;
};
readonly sessionId: string | undefined;
readonly target: string;
}

MiddlewareSuccess

interface unstable
Since v4.0.0

Opaque handler result. Middleware can wrap execution and provide services, but cannot inspect or replace the result.

Signature

interface MiddlewareSuccess {
readonly _: typeof _;
}

visibility

unstable
Since v4.0.0

Creates middleware that hides registrations per request.

Details

The check runs for every registration the middleware is provided to, on list requests and on calls, reads, gets and completions. When it returns false the registration is left out of list results, and requests for it fail as if it was not registered.

The result is a Middleware and composes like one. The check receives the same MiddlewareOptions as middleware, and services it reads per request, other than McpRequestContext, are met the same way. Scope a visibility to some tools by providing its layer to their toolkit only, or branch on target.

On a list request, the check and the middleware outside the innermost visibility run once for each registration. Middleware inside it does not run. A failure of that middleware or of a check fails the whole list request. It never hides a registration.

Unlike EnabledWhen, a visibility runs the middleware around it on list requests, and middleware sees requests for hidden registrations.

See

  • middleware for providing the services a check reads

Signature

declare const visibility: visibility.Make

Example Hiding admin tools from other users

import { Context, Effect, Layer, Schema } from "effect"
import { McpServer, Tool, Toolkit } from "effect/ai"
class CurrentUser extends Context.Service<CurrentUser, { readonly isAdmin: boolean }>()("CurrentUser") {}
const AdminTools = Toolkit.make(Tool.make("DeleteUser", { success: Schema.String }))
const Auth = McpServer.middleware<{ provides: CurrentUser }>()((effect, { headers }) =>
Effect.provideService(effect, CurrentUser, { isAdmin: headers["x-role"] === "admin" })
)
const AdminOnly = McpServer.visibility(() => Effect.map(CurrentUser, (user) => user.isAdmin))
const Registrations = McpServer.toolkit(AdminTools).pipe(
Layer.provide(AdminOnly.combine(Auth).layer),
Layer.provide(AdminTools.toLayer({ DeleteUser: () => Effect.succeed("deleted") }))
)

visibility

unstable
Since v4.0.0

Types used by visibility.

Models

CacheHint

interface unstable
Since v4.1.0

A client cache hint for one kind of v2026_07_28 result.

Details

ttl is how long a client may treat the result as fresh. It is sent as a whole number of milliseconds, rounded down, so it must be finite, nonnegative and at most Number.MAX_SAFE_INTEGER milliseconds. Only "private" scope is supported, so shared caches must not store the result.

Signature

interface CacheHint {
readonly cacheScope: "private";
readonly ttl: Input;
}

CacheHints

interface unstable
Since v4.1.0

Client cache hints for v2026_07_28 discovery and list results.

Details

Each entry sets the freshness and cacheScope of one result kind: server/discover, tools/list, resources/list, resources/templates/list and prompts/list. skills covers the Skills extension's skills/list, skills/get and resources/directory/read. Omitted entries, and resources/read results, keep { ttlMs: 0, cacheScope: "private" }. Earlier protocol revisions have no cache hints and ignore this option.

These are hints for clients. The server does not cache results, and visibility and invocation checks still run on every request.

Gotchas

A positive ttl lets a client keep showing a list after registrations or per-request visibility change, until the hint expires. Private scope does not key a client's cache by user, so a client shared between users can reuse one user's list for another.

Signature

interface CacheHints {
readonly discover?: CacheHint;
readonly prompts?: CacheHint;
readonly resources?: CacheHint;
readonly resourceTemplates?: CacheHint;
readonly skills?: CacheHint;
readonly tools?: CacheHint;
}

ResourceCompletions

type unstable
Since v4.0.0

Completion-handler map for a resource URI template.

Details

Each schema interpolation contributes a parameter key, using an explicit Param name when present or paramN otherwise, and each handler returns candidate values for that parameter.

Signature

type ResourceCompletions<Schemas extends ReadonlyArray<Schema.Constraint>> = { [K in Extract<keyof Schemas, `${number}`>]: (input: string, context: CompletionContext) => Effect.Effect<Array<Schemas[K]["Type"]>, any, any> }

SkillFile

type unstable
Since v4.1.1

A file of a skill: its text, its bytes, or either with an explicit MIME type. Without one, the MIME type is inferred from the file extension.

Signature

type SkillFile = string | Uint8Array | {
readonly content: string | Uint8Array;
readonly mimeType?: string;
}

SkillOptions

interface unstable
Since v4.1.1

Options for registerSkill and skill.

Details

uri addresses the skill's SKILL.md, as skill://<skill-path>/SKILL.md. Its last path segment is the skill's name, which must equal the frontmatter's name. Other schemes work the same way.

files maps paths relative to the skill root to the skill's files, SKILL.md included. Files are served verbatim, and the skill's listed frontmatter is parsed from SKILL.md. Quote frontmatter strings that a YAML 1.1 parser reads as another type, such as yes or 1.0.

Signature

interface SkillOptions {
readonly annotations?: Context<never>;
readonly files: {
readonly "SKILL.md": SkillFile;
} & Readonly<Record<string, SkillFile>>;
readonly uri: string;
}

Running

run

unstable
Since v4.0.0

Runs an MCP server over the current RpcServer.Protocol.

Details

The server serves registered tools, resources, and prompts. The selected MCP runtime handles protocol lifecycle state and determines which clients receive queued server notifications.

Signature

declare const run: (options: {
readonly cacheHints?: CacheHints;
readonly description?: string;
readonly extensions?: ServerExtensions;
readonly icons?: ReadonlyArray<McpSchema.Icon>;
readonly instructions?: string;
readonly name: string;
readonly protocols: Arr.NonEmptyReadonlyArray<McpProtocol.ProtocolAdapter>;
readonly version: string;
readonly websiteUrl?: string;
}) => Effect.Effect<never, Cause.IllegalArgumentError, McpServer | RpcServer.Protocol>

Services

McpServer

unstable
Since v4.0.0

Service that stores and serves an MCP server's registered tools, resources, prompts, completions, and outgoing notifications.

Details

Handlers use this service to register capabilities and resolve incoming MCP requests.

Signature

declare class McpServer extends Shape<"effect/ai/McpServer", {
readonly addPrompt: (options: {
readonly annotations: Context<never>;
readonly completions: Record<string, (input: string, context: CompletionContext) => Effect.Effect<CompleteResult, InternalError, McpRequestContext>>;
readonly handle: (params: Record<string, string>) => Effect<GetPromptResult | InputRequired, InvalidParams | InternalError, McpRequestContext>;
readonly prompt: Prompt;
readonly services?: Context<never>;
}) => Effect<void>;
readonly addResource: (options: {
readonly annotations: Context<never>;
readonly handle: Effect<ReadResourceResult, InternalError, McpRequestContext>;
readonly resource: Resource;
readonly services?: Context<never>;
}) => Effect<void>;
readonly addResourceTemplate: (options: {
readonly annotations: Context<never>;
readonly completions: Record<string, (input: string, context: CompletionContext) => Effect.Effect<CompleteResult, InternalError>>;
readonly handle: (uri: string, params: Array<string>) => Effect<ReadResourceResult, InvalidParams | InternalError, McpRequestContext>;
readonly routerPath: string;
readonly services?: Context<never>;
readonly template: ResourceTemplate;
}) => Effect<void>;
readonly addTool: (options: {
readonly annotations: Context<never>;
readonly handle: (payload: any) => Effect<CallToolResult | InputRequired, InvalidParams | InternalError, McpRequestContext>;
readonly services?: Context<never>;
readonly tool: Tool;
}) => Effect<void>;
readonly callTool: (requests: {
readonly _meta?: {
[key: string]: unknown;
readonly progressToken?: string | number;
};
readonly arguments?: {
[key: string]: any;
};
readonly name: string;
}) => Effect<CallToolResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly completion: (complete: {
readonly argument: {
readonly name: string;
readonly value: string;
};
readonly context?: {
readonly arguments?: {
[key: string]: string;
};
};
readonly ref: ResourceReference | PromptReference;
}) => Effect<CompleteResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly findResource: (uri: string) => Effect<ReadResourceResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly getPromptResult: (request: {
readonly _meta?: {
[key: string]: unknown;
readonly progressToken?: string | number;
};
readonly arguments?: {
[key: string]: string;
};
readonly name: string;
readonly title?: string;
}) => Effect<GetPromptResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly notifications: {
"notifications/cancelled": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly reason?: string;
readonly requestId: string | number;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/message": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly data: any;
readonly level: "error" | "debug" | "info" | "notice" | "warning" | "critical" | "alert" | "emergency";
readonly logger?: string;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/progress": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly message?: string;
readonly progress: number;
readonly progressToken: string | number;
readonly total?: number;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/prompts/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/resources/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/resources/updated": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly uri: string;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/tools/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
};
readonly notifyElicitationComplete: (options: {
readonly clientId: number;
readonly elicitationId: string;
}) => Effect<void>;
readonly prompts: readonly Array<{
readonly annotations: Context<never>;
readonly prompt: Prompt;
}>;
readonly resources: readonly Array<{
readonly annotations: Context<never>;
readonly resource: Resource;
}>;
readonly resourceTemplates: readonly Array<{
readonly annotations: Context<never>;
readonly template: ResourceTemplate;
}>;
readonly tools: readonly Array<{
readonly annotations: Context<never>;
readonly tool: Tool;
}>;
}, this> {
constructor(_: never);
static readonly layer: Layer<McpServer>;
static readonly make: Effect<{
readonly addPrompt: (options: {
readonly annotations: Context<never>;
readonly completions: Record<string, (input: string, context: CompletionContext) => Effect.Effect<CompleteResult, InternalError, McpRequestContext>>;
readonly handle: (params: Record<string, string>) => Effect<GetPromptResult | InputRequired, InvalidParams | InternalError, McpRequestContext>;
readonly prompt: Prompt;
readonly services?: Context<never>;
}) => Effect<void>;
readonly addResource: (options: {
readonly annotations: Context<never>;
readonly handle: Effect<ReadResourceResult, InternalError, McpRequestContext>;
readonly resource: Resource;
readonly services?: Context<never>;
}) => Effect<void>;
readonly addResourceTemplate: (options: {
readonly annotations: Context<never>;
readonly completions: Record<string, (input: string, context: CompletionContext) => Effect.Effect<CompleteResult, InternalError>>;
readonly handle: (uri: string, params: Array<string>) => Effect<ReadResourceResult, InvalidParams | InternalError, McpRequestContext>;
readonly routerPath: string;
readonly services?: Context<never>;
readonly template: ResourceTemplate;
}) => Effect<void>;
readonly addTool: (options: {
readonly annotations: Context<never>;
readonly handle: (payload: any) => Effect<CallToolResult | InputRequired, InvalidParams | InternalError, McpRequestContext>;
readonly services?: Context<never>;
readonly tool: Tool;
}) => Effect<void>;
readonly callTool: (requests: {
readonly _meta?: {
[key: string]: unknown;
readonly progressToken?: string | number;
};
readonly arguments?: {
[key: string]: any;
};
readonly name: string;
}) => Effect<CallToolResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly completion: (complete: {
readonly argument: {
readonly name: string;
readonly value: string;
};
readonly context?: {
readonly arguments?: {
[key: string]: string;
};
};
readonly ref: ResourceReference | PromptReference;
}) => Effect<CompleteResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly findResource: (uri: string) => Effect<ReadResourceResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly getPromptResult: (request: {
readonly _meta?: {
[key: string]: unknown;
readonly progressToken?: string | number;
};
readonly arguments?: {
[key: string]: string;
};
readonly name: string;
readonly title?: string;
}) => Effect<GetPromptResult, McpErrorBase | InvalidParams | InternalError, McpServerClient>;
readonly notifications: {
"notifications/cancelled": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly reason?: string;
readonly requestId: string | number;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/message": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly data: any;
readonly level: "error" | "debug" | "info" | "notice" | "warning" | "critical" | "alert" | "emergency";
readonly logger?: string;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/progress": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly message?: string;
readonly progress: number;
readonly progressToken: string | number;
readonly total?: number;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/prompts/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/resources/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/resources/updated": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
readonly uri: string;
}, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
"notifications/tools/list_changed": <AsQueue extends boolean = false, Discard = false>(input: {
readonly _meta?: {
[key: string]: unknown;
};
} | undefined, options?: {
readonly context?: Context<never>;
readonly discard?: Discard;
readonly headers?: Input;
}) => Effect<Discard extends true ? void : void, Discard extends true ? never : never, never>;
};
readonly notifyElicitationComplete: (options: {
readonly clientId: number;
readonly elicitationId: string;
}) => Effect<void>;
readonly prompts: readonly Array<{
readonly annotations: Context<never>;
readonly prompt: Prompt;
}>;
readonly resources: readonly Array<{
readonly annotations: Context<never>;
readonly resource: Resource;
}>;
readonly resourceTemplates: readonly Array<{
readonly annotations: Context<never>;
readonly template: ResourceTemplate;
}>;
readonly tools: readonly Array<{
readonly annotations: Context<never>;
readonly tool: Tool;
}>;
}, never, Scope>;
}

Utility Types

CompletionServices

type unstable
Since v4.0.0

Utility type that collects the services required by the handlers of a completion-handler record, including handlers declared as optional.

Signature

type CompletionServices<Completions> = { [K in keyof Completions]: NonNullable<Completions[K]> extends (...args: any) => infer Ret ? Effect.Services<Ret> : never }[keyof Completions]

McpRequest

unstable
Since v4.0.0

Helper types for McpRequest markers.

McpRequest

interface unstable
Since v4.0.0

Marks a request-level dependency of an MCP registration or middleware.

Details

Registrations carry handler dependencies as McpRequest<"Requires", S>. MCP middleware and transports discharge these markers; providing S with Layer.provide or Effect.provide does not.

layerHttp and layerHttpRequestScoped convert the markers they don't discharge into HttpRouter.Request markers, so HttpRouter.middleware can satisfy them. On stdio and custom transports an undischarged marker is a compile error.

See

  • middleware for discharging request-level dependencies

Signature

interface McpRequest<Kind extends string, T> {
readonly _: typeof _;
readonly kind: Kind;
readonly type: T;
}

PromptRequirements

type unstable
Since v4.0.0

The requirements of a prompt registration.

Details

Services read by the content and completion handlers are request-level. Parameter decoding services, and the encoding services of parameters with a completion handler, are required when the prompt is registered, unless a handler also reads them.

Signature

type PromptRequirements<Params extends Schema.Struct.Fields, R, Completions> = RegistrationRequirements<PromptSchemaServices<Params, Completions>, R | CompletionServices<Completions>>

ProvidedServices

type unstable
Since v4.0.0

Request-level services supplied by a transport.

Details

Every transport supplies Scope. McpServerClient is supplied only when every configured protocol is known to be sessionful: a v2026_07_28 request has no client session, and a protocol array widened to ProtocolAdapter might contain one. HTTP transports also supply HttpServerRequest.

Signature

type ProvidedServices<Protocols extends ReadonlyArray<McpProtocol.ProtocolAdapter>, Http extends boolean = false> = Scope.Scope | Protocols[number] extends StatefulProtocolAdapter ? McpServerClient : never | Http extends true ? HttpServerRequest.HttpServerRequest : never
Since v4.0.0

The requirements of a resource registration: the content handler's services are request-level.

Signature

type ResourceRequirements<R> = RegistrationRequirements<never, R>
Since v4.0.0

The requirements of a resource template registration.

Details

Services read by the content and completion handlers are request-level. Parameter decoding services, and the encoding services of parameters with a completion handler, are required when the template is registered, unless a handler also reads them.

Signature

type ResourceTemplateRequirements<Schemas extends ReadonlyArray<Schema.Constraint>, R, Completions> = RegistrationRequirements<TemplateSchemaServices<Schemas, Completions>, R | CompletionServices<Completions>>

ToolkitRequirements

type unstable
Since v4.0.0

The requirements of a toolkit registration.

Details

Services a tool declares with dependencies or Tool.addDependency are request-level: they are carried as McpRequest<"Requires", S> markers and must be provided by MCP middleware or the transport. Parameter decoding and result encoding services are required when the toolkit is registered, unless a tool also declares them.

Signature

type ToolkitRequirements<Tools extends Record<string, Tool.Any>> = 0 extends 1 & Tool.Requirements<Tools[keyof Tools]> ? any : Tool.HandlersFor<Tools> | RegistrationRequirements<Tool.SchemaServices<Tools[keyof Tools]>, Tool.Requirements<Tools[keyof Tools]>>

ValidateCompletions

type unstable
Since v4.0.0

Utility type that validates a completion-handler record against the allowed parameter keys.

Signature

type ValidateCompletions<Completions, Keys extends string> = Completions & { [K in keyof Completions]: K extends Keys ? (input: string, context: CompletionContext) => any : never }