Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mcp-protocol-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

MCP servers now support the 2024-11-05 and 2025-03-26 RPC revisions through version-specific protocol adapters.
10 changes: 7 additions & 3 deletions packages/effect/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,13 @@ The server exposes three main parts:
The part layers are merged into one layer that has a MCP server implementation as dependency.
`McpServer.layerStdio` is used to create a standard I/O–based MCP server identified by its name and
version. Its ordered, non-empty `protocols` declaration names implemented protocol adapters rather
than arbitrary version strings. This release supports `McpProtocol.v2025_06_18`. Because of the
layer architecture the server implementation can be easily exchanged with an HTTP-based implementation
with `McpServer.layerHttp`. Finally, a logging layer is added with
than arbitrary version strings. This release supports `McpProtocol.v2024_11_05`,
`McpProtocol.v2025_03_26`, and `McpProtocol.v2025_06_18`. The `v2024_11_05` adapter implements that
revision's RPC schemas and stdio framing, including its batch policy. It does not implement the
historical two-endpoint HTTP+SSE transport. `McpServer.layerHttp` instead offers the 2024 RPC schema
through the same single-endpoint HTTP compatibility transport used by the 2025 adapters. Because of
the layer architecture the server implementation can be easily exchanged with this HTTP-based
implementation. Finally, a logging layer is added with
`Logger.layer([Logger.consolePretty({ stderr: true })])`, ensuring logs are written to `stderr`.
This is essential when using stdio, as any output to `stdout` would interfere with the protocol
communication.
Expand Down
49 changes: 28 additions & 21 deletions packages/effect/src/unstable/ai/McpProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,48 @@
*
* @since 4.0.0
*/
import type * as RpcGroup from "../rpc/RpcGroup.ts"
import * as Internal from "./internal/mcpProtocol.ts"
import * as McpSchema from "./McpSchema.ts"
import { protocol as protocol2024_11_05 } from "./internal/mcpProtocol/v2024_11_05.ts"
import { protocol as protocol2025_03_26 } from "./internal/mcpProtocol/v2025_03_26.ts"
import { protocol as protocol2025_06_18 } from "./internal/mcpProtocol/v2025_06_18.ts"

/**
* The MCP 2025-06-18 protocol implementation.
*
* @category protocols
* @since 4.0.0
*/
export const v2025_06_18: ProtocolAdapter = Internal.make({
protocolVersion: "2025-06-18",
transport: {
acceptsJsonRpcBatches: false,
requiresVersionHeader: true
},
clientRpcs: McpSchema.ClientRpcs,
clientNotificationRpcs: McpSchema.ClientNotificationRpcs,
serverRequestRpcs: McpSchema.ServerRequestRpcs,
serverNotificationRpcs: McpSchema.ServerNotificationRpcs
})
export const v2025_06_18 = protocol2025_06_18

/**
* The MCP 2025-03-26 protocol implementation.
*
* @category protocols
* @since 4.0.0
*/
export const v2025_03_26 = protocol2025_03_26

/**
* The MCP 2024-11-05 protocol implementation.
*
* **Details**
*
* It provides the dated schema and stdio behavior. When supplied to
* `McpServer.layerHttp`, the server uses its single-endpoint Streamable HTTP
* compatibility transport; it does not implement the historical two-endpoint
* HTTP+SSE transport.
*
* @category protocols
* @since 4.0.0
*/
export const v2024_11_05 = protocol2024_11_05

/**
* An implemented MCP protocol that can be supplied to `McpServer`.
*
* @category models
* @since 4.0.0
*/
export type ProtocolAdapter = Internal.ProtocolAdapter<
"2025-06-18",
RpcGroup.Rpcs<typeof McpSchema.ClientRpcs>,
RpcGroup.Rpcs<typeof McpSchema.ClientNotificationRpcs>,
RpcGroup.Rpcs<typeof McpSchema.ServerRequestRpcs>,
RpcGroup.Rpcs<typeof McpSchema.ServerNotificationRpcs>
>
export type ProtocolAdapter = typeof v2024_11_05 | typeof v2025_03_26 | typeof v2025_06_18

/**
* The MCP protocol versions implemented by this release.
Expand Down
125 changes: 100 additions & 25 deletions packages/effect/src/unstable/ai/McpSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
*
* MCP clients and servers use these schemas to describe the JSON-RPC requests,
* notifications, results, and errors that can cross the protocol boundary. This
* module focuses on message shapes: it defines the shared protocol data model,
* groups related messages for the RPC layer, and provides helpers for optional
* fields and parameter metadata. Transport and server behavior live in other
* modules.
* This is the stable public compatibility and authoring surface. It is not an
* exact dated wire contract: MCP protocol adapters use frozen schemas under
* `internal/mcpSchema` for decoding and encoding. This module groups the
* current public message model for application authors and provides helpers
* for optional fields and parameter metadata. Transport and server behavior
* live in other modules.
*
* @since 4.0.0
*/
import * as Context from "../../Context.ts"
import * as Data from "../../Data.ts"
import * as Effect from "../../Effect.ts"
import { constFalse, constTrue } from "../../Function.ts"
import * as Option from "../../Option.ts"
Expand All @@ -19,11 +22,8 @@ import * as Schema from "../../Schema.ts"
import * as SchemaGetter from "../../SchemaGetter.ts"
import type * as Scope from "../../Scope.ts"
import * as Rpc from "../rpc/Rpc.ts"
import type * as RpcClient from "../rpc/RpcClient.ts"
import type { RpcClientError } from "../rpc/RpcClientError.ts"
import * as RpcGroup from "../rpc/RpcGroup.ts"
import * as RpcMiddleware from "../rpc/RpcMiddleware.ts"
import type * as McpProtocol from "./McpProtocol.ts"

/**
* Schema type returned by `optionalWithDefault`.
Expand Down Expand Up @@ -798,7 +798,7 @@ export class ProgressNotification extends Rpc.make("notifications/progress", {
* The progress thus far. This should increase every time progress is made,
* even if the total is unknown.
*/
progress: optional(Schema.Finite),
progress: Schema.Finite,
/**
* Total number of items to process (or total progress required), if known.
*/
Expand Down Expand Up @@ -959,7 +959,7 @@ export class BlobResourceContents extends Schema.Opaque<BlobResourceContents>()(
/**
* The binary data of the item decoded from a base64-encoded string.
*/
blob: Schema.Uint8Array
blob: Schema.Uint8ArrayFromBase64
})) {}

/**
Expand Down Expand Up @@ -1170,7 +1170,8 @@ export class Prompt extends Schema.Class<Prompt>(
/**
* A list of arguments to use for templating the prompt.
*/
arguments: optional(Schema.Array(PromptArgument))
arguments: optional(Schema.Array(PromptArgument)),
_meta: optional(Schema.Record(Schema.String, Schema.Json))
}) {}

/**
Expand All @@ -1188,7 +1189,8 @@ export class TextContent extends Schema.Opaque<TextContent>()(Schema.Struct({
/**
* Optional annotations for the client.
*/
annotations: optional(Annotations)
annotations: optional(Annotations),
_meta: optional(Schema.Record(Schema.String, Schema.Json))
})) {}

/**
Expand All @@ -1202,7 +1204,7 @@ export class ImageContent extends Schema.Opaque<ImageContent>()(Schema.Struct({
/**
* The image data.
*/
data: Schema.Uint8Array,
data: Schema.Uint8ArrayFromBase64,
/**
* The MIME type of the image. Different providers may support different
* image types.
Expand All @@ -1211,7 +1213,8 @@ export class ImageContent extends Schema.Opaque<ImageContent>()(Schema.Struct({
/**
* Optional annotations for the client.
*/
annotations: optional(Annotations)
annotations: optional(Annotations),
_meta: optional(Schema.Record(Schema.String, Schema.Json))
})) {}

/**
Expand All @@ -1225,7 +1228,7 @@ export class AudioContent extends Schema.Opaque<AudioContent>()(Schema.Struct({
/**
* The audio data.
*/
data: Schema.Uint8Array,
data: Schema.Uint8ArrayFromBase64,
/**
* The MIME type of the audio. Different providers may support different
* audio types.
Expand All @@ -1234,7 +1237,8 @@ export class AudioContent extends Schema.Opaque<AudioContent>()(Schema.Struct({
/**
* Optional annotations for the client.
*/
annotations: optional(Annotations)
annotations: optional(Annotations),
_meta: optional(Schema.Record(Schema.String, Schema.Json))
})) {}

/**
Expand All @@ -1254,7 +1258,8 @@ export class EmbeddedResource extends Schema.Opaque<EmbeddedResource>()(Schema.S
/**
* Optional annotations for the client.
*/
annotations: optional(Annotations)
annotations: optional(Annotations),
_meta: optional(Schema.Record(Schema.String, Schema.Json))
})) {}

/**
Expand Down Expand Up @@ -1449,6 +1454,26 @@ export class ToolAnnotations extends Schema.Opaque<ToolAnnotations>()(Schema.Str
openWorldHint: optionalWithDefault(Schema.Boolean, constTrue)
})) {}

/**
* Schema for the object-root JSON Schema used by MCP tool inputs and outputs.
*
* **Details**
*
* Property definitions and additional root keywords are constrained to JSON
* values. The open root supports generated keywords such as `$defs`.
*
* @category tools
* @since 4.0.0
*/
export const ToolJsonSchema = Schema.StructWithRest(
Schema.Struct({
type: Schema.Literal("object"),
properties: optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Json))),
required: optional(Schema.Array(Schema.String))
}),
[Schema.Record(Schema.String, Schema.Json)]
)

/**
* Schema for the definition of a tool the client can call.
*
Expand All @@ -1472,11 +1497,11 @@ export class Tool extends Schema.Class<Tool>(
/**
* A JSON Schema object defining the expected parameters for the tool.
*/
inputSchema: Schema.Any,
inputSchema: ToolJsonSchema,
/**
* An optional JSON Schema object defining the expected output of the tool.
* An optional JSON Schema object defining the structure of the tool output.
*/
outputSchema: optional(Schema.Any),
outputSchema: optional(ToolJsonSchema),
/**
* Optional additional tool information.
*/
Expand Down Expand Up @@ -1533,7 +1558,10 @@ export class ListTools extends Rpc.make("tools/list", {
export class CallToolResult extends Schema.Class<CallToolResult>("@effect/ai/McpSchema/CallToolResult")({
...ResultMeta.fields,
content: Schema.Array(ContentBlock),
structuredContent: optional(Schema.Any),
/**
* An optional JSON value containing the structured result of the tool call.
*/
structuredContent: optional(Schema.Json),
/**
* Whether the tool call ended in an error.
*
Expand Down Expand Up @@ -1796,7 +1824,8 @@ export class ModelPreferences extends Schema.Class<ModelPreferences>(
export class CreateMessageResult extends Schema.Class<CreateMessageResult>(
"@effect/ai/McpSchema/CreateMessageResult"
)({
...SamplingMessage.fields,
role: Role,
content: Schema.Union([TextContent, ImageContent, AudioContent]),
/**
* The name of the model that generated the message.
*/
Expand Down Expand Up @@ -1898,6 +1927,7 @@ export class PromptReference extends Schema.Opaque<PromptReference>()(Schema.Str
* @since 4.0.0
*/
export class CompleteResult extends Schema.Opaque<CompleteResult>()(Schema.Struct({
...ResultMeta.fields,
completion: Schema.Struct({
/**
* An array of completion values. Must not exceed 100 items.
Expand Down Expand Up @@ -2162,23 +2192,68 @@ export class ElicitationDeclined extends Schema.Error<ElicitationDeclined>("@eff
// McpServerClient
// =============================================================================

/**
* Raised when the negotiated MCP revision or client capabilities do not
* support a server-initiated operation.
*
* @category errors
* @since 4.0.0
*/
export class McpReverseOperationUnsupported extends Data.TaggedError("McpReverseOperationUnsupported")<{
readonly operation: "roots/list" | "sampling/createMessage" | "elicitation/create"
readonly protocolVersion: "2024-11-05" | "2025-03-26" | "2025-06-18"
readonly reason: string
}> {}

/**
* A reverse MCP operation failed while being sent or projected through a
* version adapter.
*
* @category errors
* @since 4.0.0
*/
export class McpReverseOperationError extends Data.TaggedError("McpReverseOperationError")<{
readonly operation: "roots/list" | "sampling/createMessage" | "elicitation/create"
readonly cause: unknown
}> {}

/**
* Version-neutral operations that an MCP server may request from its client.
*
* @category client
* @since 4.0.0
*/
export interface McpReverseClient {
readonly listRoots: (
request?: typeof ListRoots.payloadSchema.Type
) => Effect.Effect<ListRootsResult, McpReverseOperationError | McpReverseOperationUnsupported>
readonly createMessage: (
request: typeof CreateMessage.payloadSchema.Type
) => Effect.Effect<CreateMessageResult, McpReverseOperationError | McpReverseOperationUnsupported>
readonly elicit: (
request: typeof Elicit.payloadSchema.Type
) => Effect.Effect<typeof ElicitResult.Type, McpReverseOperationError | McpReverseOperationUnsupported>
}

/**
* Service available while handling an MCP client request.
*
* **Details**
*
* It exposes the current client id, the client's initialize payload, and a
* scoped RPC client for server-initiated requests back to that client.
* It exposes the current client id, normalized initialization data, and a
* scoped version-neutral facade for server-initiated requests.
*
* @category services
* @since 4.0.0
*/
export class McpServerClient extends Context.Service<McpServerClient, {
readonly clientId: number
readonly protocolVersion: McpProtocol.ProtocolVersion
readonly protocolVersion: "2024-11-05" | "2025-03-26" | "2025-06-18"
readonly clientCapabilities: ClientCapabilities
readonly clientInfo: Implementation
readonly initializePayload: typeof Initialize.payloadSchema["Type"]
readonly getClient: Effect.Effect<
RpcClient.RpcClient<RpcGroup.Rpcs<typeof ServerRequestRpcs>, RpcClientError>,
McpReverseClient,
never,
Scope.Scope
>
Expand Down
Loading