Skip to content

feat: add v2026-07-28 protocol adapter - #7265

Draft
lloydrichards wants to merge 8 commits into
Effect-TS:mainfrom
lloydrichards:feat/v2026-07-28
Draft

feat: add v2026-07-28 protocol adapter#7265
lloydrichards wants to merge 8 commits into
Effect-TS:mainfrom
lloydrichards:feat/v2026-07-28

Conversation

@lloydrichards

@lloydrichards lloydrichards commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Type

  • Refactor
  • Feature
  • Bug Fix

Description

Add support the McpProtocol.v2026_07_28 which introduced the 'modern' stateless mcp architecture, while still supporting the previous session-based protocol versions. To support both lifecycles, we extract the protocol lifecycle handling into dedicated runtimes. McpServer selects the appropriate runtime for each adapter: older adapters keep their stateful initialization and session behavior, while v2026_07_28 selects and decodes the protocol from each request without creating session state.

The main user-facing additions are:

  • Stateless server/discover negotiation, with no initialization handshake or session state.
  • McpRequestContext gives handlers access to request and client capabilities neutral to the runtime of the protocol.
  • Multi-round-trip tool results (MRTR) where handlers can return McpSchema.InputRequired, and clients can provide keyed elicitation, sampling, or roots responses on a later request.
  • subscriptions/listen, delivering filtered server change notifications over stdio or Server-Sent Events.
  • Tool output schemas and structuredContent for every JSON value in the 2026-07-28 adapter. Earlier adapters continue to project only the shapes they support.
  • Request-selected streaming RPC HTTP responses and typed server-to-client notifications.
A toy example of the stateless server and JSON tool output:
const Greet = Tool.make("greet", {
  description: "Return a greeting for the supplied name",
  parameters: Schema.Struct({ name: Schema.String }),
  success: Schema.Struct({ greeting: Schema.String })
})

// New in 2026-07-28: a tool output schema may describe any JSON value, not
// only an object. The adapter returns this array as `structuredContent`.
const ProjectFiles = Tool.make("project_files", {
  description: "Return JSON array output and the request-scoped client name",
  parameters: Tool.EmptyParams,
  success: Schema.Array(Schema.String),
  dependencies: [McpSchema.McpRequestContext]
})

const InspectorToolkit = Toolkit.make(Greet, ProjectFiles)

// New in 2026-07-28: the server selects this adapter from each request's
// metadata and headers; there is no initialize handshake or session ID.
const McpHttp = McpServer.layerHttp({
  name: "effect-mcp-2026-07-28-scratchpad",
  version: "1.0.0",
  description: "Modern-only MCP server for Inspector interoperability checks",
  path: "/mcp",
  protocols: [McpProtocol.v2026_07_28]
}).pipe(Layer.provide(HttpRouter.layer))

const Registrations = McpServer.toolkit(InspectorToolkit).pipe(
  Layer.provideMerge(InspectorToolkit.toLayer(InspectorToolkit.of({
    greet: ({ name }) => Effect.succeed({ greeting: `Hello, ${name}!` }),
    project_files: () =>
      McpSchema.McpRequestContext.pipe(
        // Handlers receive client facts from the current request rather than
        // recovering them from the legacy McpServerClient session facade.
        Effect.map((request) => [
          "README.md",
          "package.json",
          `served-for-${request.clientInfo?.name ?? "unknown-client"}.txt`
        ])
      )
  })))
)

const HttpLive = Registrations.pipe(
  Layer.provideMerge(McpHttp),
  Layer.provide(HttpRouter.serve(McpHttp, { disableLogger: true })),
  Layer.provide(NodeHttpServer.layer(createServer, { host: "127.0.0.1", port: 3001 }))
)

NodeRuntime.runMain(Layer.launch(HttpLive))

It wasn't possible to model a purely incremental session-era adapter due to the runtime changes, so this PR introduces a few structural changes to both Mcp and Rpc:

  • ProtocolAdapters now declare whether they use a stateful or stateless lifecycle policy.
  • McpServer owns lifecycle runtime state, separating the existing sessionful path from the 2026-07-28 request-scoped path.
  • Dated protocol selection happens from raw request metadata before protocol-specific decoding.
  • Common handlers receive McpRequestContext; legacy reverse operations continue to use McpServerClient.
  • RPC HTTP transport support now has a request-selected streaming response path and typed notification.

Supporting subscriptions/listen required a reusable RPC transport api for streaming responses and typed server-to-client notifications. The RPC change adds on top of the existing buffered HTTP behavior, while MCP can opt into streaming responses and notifications.

How to Review

The files that deserve the most scrutiny should be:

  • mcpProtocol/v2026_07_28.ts + mcpSchema/v2026_07_28.ts -> dated wire behavior
  • mcpRuntime.ts + mcpStatefulRuntime.ts -> stateless and preserved session lifecycles
  • RpcServer.ts -> streaming HTTP responses and notifications

But its probably going to be easiest to just go through each commit ad there is a clear set of packages of work across each:

1. Runtime extraction

  • refactor: introduce MCP lifecycle runtimes and request context

Extracts the lifecycle ownership from the protocol adapters into McpServer runtimes.

This separates the existing sessionful protocol path from the new stateless path without retrofitting sessions onto 2026-07-28. It also introduces McpRequestContext, allowing common handlers to read per-request protocol, capability, and client facts while legacy reverse operations keep using McpServerClient.

2. Adding v2026-07-28

  • feat: add MCP 2026-07-28 protocol adapter

Adds the dated wire schemas and adapter for MCP 2026-07-28.

The adapter is selected from raw HTTP headers and request _meta before dated payload decoding. It serves server/discover, requires self-contained routing metadata, and deliberately creates or consults no Mcp-Session-Id state.

3. Adding MRTR

  • feat: support MCP multi-round-trip tool results

Adds multi-round-trip tool results (MRTR).

Tool handlers can return McpSchema.InputRequired to ask for keyed elicitation, sampling, or roots input. A later self-contained request provides the corresponding responses through McpRequestContext, preserving the stateless model rather than holding a reverse-RPC session open.

4. Adding subscriptions

  • feat: support RPC HTTP streaming and server notifications
  • feat: add MCP 2026-07-28 subscriptions

First adds the transport primitive: request-selected streaming HTTP responses and typed server-to-client notifications for HTTP and stdio.

The protocol layer then uses that primitive for subscriptions/listen, which delivers filtered MCP change notifications over SSE or stdio without changing legacy notification behavior.

5. Clean up and compatibility hardening

  • feat: support JSON-valued MCP tool outputs
  • test: organize MCP conformance suites by protocol behavior
  • fix: make MCP list-change scheduling deterministic

Finishes the user-facing output model and makes version compatibility explicit:

  • 2026-07-28 tools can declare output schemas and return structuredContent for any JSON value; earlier adapters project only the shapes they support.
  • Conformance tests are split into named, behavior-owned suites. Each dated entrypoint selects the suites that apply, rather than embedding revision branches in shared tests.
  • List-change notification scheduling is coalesced deterministically so delayed registration events do not leak into later subscriptions.

WIP

Related

@changeset-bot

changeset-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 96e534e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/vitest Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 6.96 KB 6.96 KB 0.00 KB (0.00%)
batching.ts 9.76 KB 9.76 KB 0.00 KB (0.00%)
brand.ts 6.55 KB 6.55 KB 0.00 KB (0.00%)
cache.ts 10.67 KB 10.67 KB 0.00 KB (0.00%)
config.ts 21.10 KB 21.10 KB 0.00 KB (0.00%)
differ.ts 20.04 KB 20.04 KB 0.00 KB (0.00%)
http-client.ts 21.64 KB 21.64 KB 0.00 KB (0.00%)
logger.ts 10.91 KB 10.91 KB 0.00 KB (0.00%)
metric.ts 8.89 KB 8.89 KB 0.00 KB (0.00%)
optic.ts 6.71 KB 6.71 KB 0.00 KB (0.00%)
pubsub.ts 14.94 KB 14.94 KB 0.00 KB (0.00%)
queue.ts 11.61 KB 11.61 KB 0.00 KB (0.00%)
schedule.ts 10.77 KB 10.77 KB 0.00 KB (0.00%)
schema-class.ts 19.66 KB 19.66 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 29.61 KB 29.61 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.85 KB 25.85 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.53 KB 13.53 KB 0.00 KB (0.00%)
schema-string.ts 11.03 KB 11.03 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.33 KB 15.33 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 21.78 KB 21.78 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.21 KB 24.21 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.00 KB 19.00 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.82 KB 18.82 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.69 KB 18.69 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.85 KB 22.85 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.33 KB 19.33 KB 0.00 KB (0.00%)
schema.ts 18.91 KB 18.91 KB 0.00 KB (0.00%)
stm.ts 12.69 KB 12.69 KB 0.00 KB (0.00%)
stream.ts 9.71 KB 9.71 KB 0.00 KB (0.00%)

@IMax153 IMax153 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lloydrichards - honestly this looks pretty good to me. The example you provided is quite nice for describing an MCP server.

What is our current test coverage with the 2026-07-28 features? Do we cover the entire spec? Or are we missing pieces currently.

description: tool.description,
inputSchema: tool.inputSchema,
outputSchema: tool.outputSchema,
outputSchema: Schema.is(McpSchema.Tool.fields.outputSchema)(tool.outputSchema)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These schema compilers should be extracted to layer / module scope where possible to avoid re-compiling every time.

@arjunyel

Copy link
Copy Markdown

@lloydrichards you are a legend, thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support MCP protocol version 2026-07-28 (v4)

3 participants