Skip to content
5 changes: 5 additions & 0 deletions .changeset/calm-pandas-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Allow MCP tool handlers to return `McpSchema.InputRequired` so stateless protocol clients can supply keyed elicitation, sampling, or roots responses through `McpRequestContext` on a later request.
5 changes: 5 additions & 0 deletions .changeset/modern-mice-discover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Add server support for MCP protocol version 2026-07-28 through `McpProtocol.v2026_07_28`. Modern requests use stateless, per-request negotiation without creating or consulting legacy sessions.
5 changes: 5 additions & 0 deletions .changeset/neat-eyes-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

MCP protocol adapters now declare stateful or stateless lifecycle policy while `McpServer` owns the corresponding runtime state. `McpRequestContext` exposes request facts to handlers without requiring an initialized session. Common handlers and `clientCapabilities` now use this request context, while legacy reverse operations continue to use `McpServerClient`. `EnabledWhen` receives the selected protocol and capabilities with optional client information.
5 changes: 5 additions & 0 deletions .changeset/swift-tools-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Allow MCP tools to declare output schemas and structured results for every JSON value in the 2026-07-28 protocol while earlier adapters continue projecting only object-shaped values.
5 changes: 5 additions & 0 deletions .changeset/tidy-clocks-notify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

MCP servers now coalesce registration list-change notifications deterministically so delayed setup events do not leak into later subscriptions.
5 changes: 5 additions & 0 deletions .changeset/tidy-rpcs-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Add request-selected streaming responses to the RPC HTTP protocol and typed server-to-client notifications for JSON-RPC HTTP and stdio transports.
5 changes: 5 additions & 0 deletions .changeset/yellow-dingos-stick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Long-lived MCP 2026-07-28 `subscriptions/listen` requests deliver filtered change notifications over stdio or Server-Sent Events while legacy notification behavior remains unchanged.
115 changes: 102 additions & 13 deletions packages/effect/src/unstable/ai/McpProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { protocol as protocol2024_11_05 } from "./internal/mcpProtocol/v2024_11_
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"
import { protocol as protocol2025_11_25 } from "./internal/mcpProtocol/v2025_11_25.ts"
import { protocol as protocol2026_07_28 } from "./internal/mcpProtocol/v2026_07_28.ts"
import type * as McpSchema from "./McpSchema.ts"

/**
Expand All @@ -21,7 +22,15 @@ import type * as McpSchema from "./McpSchema.ts"
* @category models
* @since 4.0.0
*/
export type ProtocolVersion = "2024-11-05" | "2025-03-26" | "2025-06-18" | "2025-11-25"
export type ProtocolVersion = "2024-11-05" | "2025-03-26" | "2025-06-18" | "2025-11-25" | "2026-07-28"

/**
* MCP protocol versions that use initialization and server-managed sessions.
*
* @category models
* @since 4.0.0
*/
export type StatefulProtocolVersion = Exclude<ProtocolVersion, "2026-07-28">

/**
* Payload codecs used by a protocol adapter.
Expand Down Expand Up @@ -65,18 +74,80 @@ export interface ErasedClientRpcGroup extends ErasedRpcGroup {
readonly prefix: (prefix: string) => RpcGroup.RpcGroup<any>
}

/**
* Transport behavior declared by an MCP runtime descriptor.
*
* @category models
* @since 4.0.0
*/
export interface TransportPolicy {
readonly jsonRpc: {
readonly acceptsBatches: boolean
}
readonly http: {
readonly requiresVersionHeader?: boolean | undefined
}
}

/**
* Request information decoded by a stateless MCP runtime.
*
* @category models
* @since 4.0.0
*/
export interface StatelessRuntimeProfile {
readonly protocolVersion: string
readonly clientCapabilities: Schema.JsonObject
readonly clientInfo?: McpSchema.Implementation | undefined
readonly requestMetadata: Schema.JsonObject
}

/**
* Stateful lifecycle runtime declaration carried by a protocol adapter.
*
* @category models
* @since 4.0.0
*/
export interface StatefulRuntimeDescriptor {
readonly _tag: "Stateful"
readonly transport: TransportPolicy
}

/**
* Stateless lifecycle runtime declaration carried by a protocol adapter.
*
* @category models
* @since 4.0.0
*/
export interface StatelessRuntimeDescriptor {
readonly _tag: "Stateless"
readonly transport: TransportPolicy
readonly profileFromRequestMetadata: (
metadata: unknown
) => Effect.Effect<StatelessRuntimeProfile, unknown>
}

/**
* Lifecycle runtime declaration carried by a protocol adapter.
*
* @category models
* @since 4.0.0
*/
export type RuntimeDescriptor = StatefulRuntimeDescriptor | StatelessRuntimeDescriptor

/**
* The operational shape shared by protocol adapters.
*
* @category models
* @since 4.0.0
*/
export interface AnyProtocolAdapter<out Version extends string = string, HandlerRequirements = unknown> {
export interface AnyProtocolAdapter<
out Version extends string = string,
HandlerRequirements = unknown,
out Runtime extends RuntimeDescriptor = RuntimeDescriptor
> {
readonly protocolVersion: Version
readonly transport: {
readonly acceptsJsonRpcBatches: boolean
readonly requiresVersionHeader: boolean
}
readonly runtime: Runtime
readonly clientRpcs: ErasedClientRpcGroup
readonly clientNotificationRpcs: ErasedRpcGroup
readonly serverRequestRpcs: RpcGroup.Any
Expand All @@ -102,33 +173,51 @@ export interface AnyProtocolAdapter<out Version extends string = string, Handler
* @category models
* @since 4.0.0
*/
export interface ProtocolAdapter<out Version extends ProtocolVersion = ProtocolVersion>
extends AnyProtocolAdapter<Version>
{}
export interface ProtocolAdapter<
out Version extends ProtocolVersion = ProtocolVersion,
out Runtime extends RuntimeDescriptor = RuntimeDescriptor
> extends AnyProtocolAdapter<Version, unknown, Runtime> {}

/**
* The MCP 2026-07-28 protocol implementation.
*
* **Details**
*
* This revision uses request-scoped metadata instead of initialization and
* protocol-level sessions.
*
* When the selected transport supports server notifications, discovery
* advertises change-notification capabilities and `subscriptions/listen`
* delivers the requested notifications over the long-lived response.
*
* @category protocols
* @since 4.0.0
*/
export const v2026_07_28: ProtocolAdapter<"2026-07-28", StatelessRuntimeDescriptor> = protocol2026_07_28

/**
* The MCP 2025-11-25 protocol implementation.
*
* @category protocols
* @since 4.0.0
*/
export const v2025_11_25: ProtocolAdapter<"2025-11-25"> = protocol2025_11_25
export const v2025_11_25: ProtocolAdapter<"2025-11-25", StatefulRuntimeDescriptor> = protocol2025_11_25

/**
* The MCP 2025-06-18 protocol implementation.
*
* @category protocols
* @since 4.0.0
*/
export const v2025_06_18: ProtocolAdapter<"2025-06-18"> = protocol2025_06_18
export const v2025_06_18: ProtocolAdapter<"2025-06-18", StatefulRuntimeDescriptor> = protocol2025_06_18

/**
* The MCP 2025-03-26 protocol implementation.
*
* @category protocols
* @since 4.0.0
*/
export const v2025_03_26: ProtocolAdapter<"2025-03-26"> = protocol2025_03_26
export const v2025_03_26: ProtocolAdapter<"2025-03-26", StatefulRuntimeDescriptor> = protocol2025_03_26

/**
* The MCP 2024-11-05 protocol implementation.
Expand All @@ -143,4 +232,4 @@ export const v2025_03_26: ProtocolAdapter<"2025-03-26"> = protocol2025_03_26
* @category protocols
* @since 4.0.0
*/
export const v2024_11_05: ProtocolAdapter<"2024-11-05"> = protocol2024_11_05
export const v2024_11_05: ProtocolAdapter<"2024-11-05", StatefulRuntimeDescriptor> = protocol2024_11_05
107 changes: 97 additions & 10 deletions packages/effect/src/unstable/ai/McpSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type * as Scope from "../../Scope.ts"
import * as Rpc from "../rpc/Rpc.ts"
import * as RpcGroup from "../rpc/RpcGroup.ts"
import * as RpcMiddleware from "../rpc/RpcMiddleware.ts"
import type { ProtocolVersion } from "./McpProtocol.ts"
import type { ProtocolVersion, StatefulProtocolVersion } from "./McpProtocol.ts"

/**
* Schema type returned by `optionalWithDefault`.
Expand Down Expand Up @@ -1519,7 +1519,7 @@ export class ToolAnnotations extends Schema.Opaque<ToolAnnotations>()(Schema.Str
})) {}

/**
* Object-root JSON Schema used by MCP tool inputs and outputs.
* Object-root JSON Schema used by MCP tool inputs.
*
* **Details**
*
Expand Down Expand Up @@ -1550,6 +1550,24 @@ export const ToolJsonSchema: Schema.Codec<ToolJsonSchema> = Schema.StructWithRes
[Schema.Record(Schema.String, Schema.Json)]
)

/**
* JSON Schema used by MCP tool outputs.
*
* Unlike tool inputs, tool outputs may use any JSON Schema root type.
*
* @category tools
* @since 4.0.0
*/
export type ToolOutputJsonSchema = Schema.JsonObject

/**
* Schema for {@link ToolOutputJsonSchema}.
*
* @category tools
* @since 4.0.0
*/
export const ToolOutputJsonSchema: Schema.Codec<ToolOutputJsonSchema> = Schema.Record(Schema.String, Schema.Json)

/**
* Schema for the definition of a tool the client can call.
*
Expand Down Expand Up @@ -1577,7 +1595,7 @@ export class Tool extends Schema.Class<Tool>(
/**
* An optional JSON Schema object defining the structure of the tool output.
*/
outputSchema: optional(ToolJsonSchema),
outputSchema: optional(ToolOutputJsonSchema),
/**
* Optional additional tool information.
*/
Expand Down Expand Up @@ -1836,7 +1854,7 @@ export class ToolResultContent extends Schema.Class<ToolResultContent>("@effect/
/**
* Optional structured result returned by the tool.
*/
structuredContent: optional(Schema.Record(Schema.String, Schema.Unknown)),
structuredContent: optional(Schema.Json),
/**
* Whether tool execution ended in an error.
*/
Expand Down Expand Up @@ -2552,6 +2570,46 @@ export const ElicitResult = Schema.Union([
ElicitDeclineResult
])

/**
* Version-neutral request for additional MCP client input.
*
* @category models
* @since 4.0.0
*/
export type McpInputRequest =
| { readonly method: "roots/list"; readonly params?: Schema.JsonObject | undefined }
| { readonly method: "sampling/createMessage"; readonly params: Schema.JsonObject }
| { readonly method: "elicitation/create"; readonly params: Schema.JsonObject }

/**
* Version-neutral response supplied by an MCP client for a prior input request.
*
* @category models
* @since 4.0.0
*/
export type McpInputResponse = Schema.JsonObject

/**
* Indicates that an MCP operation requires additional keyed input from the client.
*
* **When to use**
*
* Use when a handler cannot complete until the client supplies roots, sampling,
* or elicitation input in a later request.
*
* **Details**
*
* `requestState` is opaque to the client and is returned unchanged with the
* keyed input responses.
*
* @category models
* @since 4.0.0
*/
export class InputRequired extends Data.TaggedClass("InputRequired")<{
readonly inputRequests: Readonly<Record<string, McpInputRequest>>
readonly requestState?: string | undefined
}> {}

/**
* Sent from the server asking the client to collect structured input from the
* user.
Expand Down Expand Up @@ -2647,6 +2705,30 @@ export interface McpReverseClient {
) => Effect.Effect<typeof ElicitResult.Type, McpReverseOperationError | McpReverseOperationUnsupported>
}

/**
* Protocol-neutral context available while handling an MCP request.
*
* **Details**
*
* Unlike `McpServerClient`, this service does not imply an initialized session
* or support for server-initiated requests.
*
* @category services
* @since 4.0.0
*/
export class McpRequestContext extends Context.Service<McpRequestContext, {
readonly clientId: number
readonly protocolVersion: string
readonly clientCapabilities: ClientCapabilities
readonly clientInfo?: Implementation | undefined
readonly requestMetadata?:
| NonNullable<typeof Initialize.payloadSchema.Type["_meta"]>
| Schema.JsonObject
| undefined
readonly inputResponses?: Readonly<Record<string, McpInputResponse>> | undefined
readonly requestState?: string | undefined
}>()("effect/ai/McpSchema/McpRequestContext") {}

/**
* Service available while handling an MCP client request.
*
Expand All @@ -2660,7 +2742,7 @@ export interface McpReverseClient {
*/
export class McpServerClient extends Context.Service<McpServerClient, {
readonly clientId: number
readonly protocolVersion: ProtocolVersion
readonly protocolVersion: StatefulProtocolVersion
readonly clientCapabilities: ClientCapabilities
readonly clientInfo: Implementation
readonly initializePayload: typeof Initialize.payloadSchema["Type"]
Expand Down Expand Up @@ -3021,8 +3103,13 @@ export function param<const Name extends string, S extends Schema.Constraint>(
* @category services
* @since 4.0.0
*/
export class EnabledWhen
extends Context.Service<EnabledWhen, Predicate.Predicate<typeof Initialize.payloadSchema.Type>>()(
"effect/unstable/ai/McpSchema/EnabledWhen"
)
{}
export class EnabledWhen extends Context.Service<
EnabledWhen,
Predicate.Predicate<{
readonly protocolVersion: string
readonly capabilities: ClientCapabilities
readonly clientInfo?: Implementation | undefined
}>
>()(
"effect/unstable/ai/McpSchema/EnabledWhen"
) {}
Loading