Skip to content

Protocol Versions ​

The SDK supports two MCP protocol eras via a dual WireCodec architecture.

Version Comparison ​

VersionStatusKey Features
2024-11-05LegacyOriginal spec revision
2025-03-26LegacyStable handshake
2025-06-18LegacyIntermediate
2025-11-25Legacyinitialize handshake, standalone server-to-client requests
2026-07-28Currentserver/discover, per-request _meta, MRTR, subscriptions/listen

WireCodec ​

The WireCodec factory auto-selects the correct codec via string comparison:

cpp
auto codec = MakeWireCodec("2026-07-28");
// Returns Rev2026Codec if version >= "2026-07-28"
// Falls back to Rev2025Codec for legacy versions

SetNegotiatedProtocolVersion(version) both stores the version AND recreates the WireCodec via MakeWireCodec(version), switching between Rev2025Codec (no _meta envelope) and Rev2026Codec (per-request _meta).

HandleDiscover returns EffectiveSupportedVersions() (the full kProtocolVersions table of 5 versions, 2024-11-05 through 2026-07-28, by default; narrowed when ServerOptions::supported_protocol_versions is non-empty) and calls SetNegotiatedProtocolVersion to set the negotiated version (options_.protocol_version when configured, otherwise kLatestProtocolVersion) — the modern client also drives version selection per-request via _meta.protocolVersion as needed.

Key Differences Between Eras ​

Aspect2025-11-252026-07-28
Connectioninitialize handshakeserver/discover per-request _meta
CapabilitiesNegotiated oncePer-request via _meta
SamplingStandalone requestRemoved (use Elicitation)
Logginglogging/setLevel RPCPer-request _meta.logLevel
Subscriptionssubscribe/unsubscribesubscriptions/listen stream
Error codesDirect valuesInternal errors (-32001/-32003/-32004) remapped to InternalError (-32603); protocol codes (-32020/-32021/-32022/-32042) pass through
ResultsPlain JSON (identity encode/decode)Typed with resultType field (auto-stamps "complete")
_meta validationNot requiredRequired on all requests except server/discover

Wire Validation (2026-era) ​

Rev2026Codec::ValidateRequest performs two checks:

  1. Era membership: Returns NotInEra if the method is not in the 2026-era method set.
  2. _meta presence: Returns Invalid if the request (except server/discover) lacks the _meta envelope.

This enforces the stateless protocol design where server/discover is the only bootstrap call without prior context.

Response Validation (2026-era) ​

Rev2026Codec::ValidateResponse validates outgoing responses:

  1. resultType field: Must be present on all responses. Returns Invalid if missing.
  2. List methods: For tools/list, resources/list, resources/templates/list, prompts/list, and skills/list, the resultType must be "complete". List results cannot be partial/input_required.

Task Header Validation ​

WireCodec::ValidateTaskHeaders(method, params, mcp_name) only applies to tasks/get, tasks/update, and tasks/cancel: a missing/empty params.taskId → InvalidParams, an Mcp-Name header value differing from taskId → HeaderMismatch (-32020). The server invokes it in the 2026 era (when the request carries the header) and at the Streamable HTTP layer (where the header is mandatory, 400); the client sends the header from params.taskId for the three task methods.

Notification Validation (2026-era) ​

Rev2026Codec::ValidateNotification ensures notifications do not contain id, result, or error fields — only method and params are allowed.

Result Encoding ​

  • 2025-era: EncodeResult/DecodeResult are identity — raw JSON passes through unchanged.
  • 2026-era: EncodeResult auto-stamps resultType: "complete" on the result if not already present. The resultType field enables downstream discrimination of normal results vs input_required (MRTR) results.

IncomingRequestMeta ​

The IncomingRequestMeta struct extracts these fields from the 2026-era _meta envelope:

Field_meta Key
protocol_versionio.modelcontextprotocol/protocolVersion
client_infoio.modelcontextprotocol/clientInfo
client_capabilitiesio.modelcontextprotocol/clientCapabilities
log_levelio.modelcontextprotocol/logLevel
progress_tokenprogressToken
subscription_idio.modelcontextprotocol/subscriptionId
traceparenttraceparent
tracestatetracestate
baggagebaggage

Outgoing request _meta is written by McpSessionHandler::SendRequest via SerializeRequestMeta, which serializes protocolVersion, clientInfo, and clientCapabilities (WireCodec::StampOutgoingRequest likewise stamps only these three fields, and has no call sites). Trace/distributed tracing fields (traceparent, tracestate, baggage) are serialized only when explicitly set on the request meta.

Server-Side Validation of Per-Request _meta ​

Before dispatching, the server validates the inbound _meta envelope at two levels:

  1. Field types: protocolVersion must be a string; progressToken must be a string or an integer; clientInfo and clientCapabilities must be objects; logLevel, traceparent, tracestate, and baggage must be strings. Any mismatch is rejected with InvalidParams (-32602) and the message invalid _meta field: <key>. Unrecognised keys are not validated and pass through untouched.
  2. Version support: for any request other than initialize, a _meta.protocolVersion absent from the server's supported table returns UnsupportedProtocolVersion (-32022), with data carrying both the requested version and the server's supported list. The table defaults to the full kProtocolVersions set and can be narrowed via ServerOptions::supported_protocol_versions (non-empty takes effect, narrowing the discover response and the initialize candidate set as well) or McpSessionHandler::SetSupportedProtocolVersions (an empty array is ignored).

initialize is exempt from this _meta version check — it declares its version in params.protocolVersion.

Era-Gated Methods ​

The codec defines per-era method sets:

SetMethods
Common (both eras)tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, completion/complete
2025-onlyinitialize, ping, resources/subscribe, resources/unsubscribe, logging/setLevel, roots/list, sampling/createMessage, elicitation/create, tasks/get, tasks/update, tasks/cancel, tasks/result, tasks/list
2026-onlyserver/discover, subscriptions/listen, tasks/get, tasks/update, tasks/cancel, skills/list, skills/get, resources/directory/read

Here tasks/get, tasks/update, and tasks/cancel form a dual-era intersection (in 2026, task notifications are routed via subscriptions with a _meta subscriptionId, and the Mcp-Name header is validated); tasks/result and tasks/list remain 2025-only; skills/list, skills/get, and resources/directory/read are 2026-only (the remaining 2025-only methods such as ping and elicitation/create are still unavailable in the 2026 era; server/extensions/list is in no era's method set).

Era-Gated Notifications ​

SetNotifications
Common (both eras)notifications/cancelled, notifications/progress, notifications/message, notifications/resources/updated, notifications/resources/list_changed, notifications/tools/list_changed, notifications/prompts/list_changed
2025-onlynotifications/initialized, notifications/roots/list_changed, notifications/elicitation/complete, notifications/tasks/status
2026-onlynotifications/subscriptions/acknowledged

The remaining notifications/tasks/working, notifications/tasks/completed, notifications/tasks/failed, notifications/tasks/cancelled, and notifications/tasks/input_required notification constants are still defined (Methods.hpp) but belong to no era's set.

Subscription System ​

The 2026-era subscription system uses SubscriptionFilter to declare interest:

cpp
struct SubscriptionFilter {
    std::optional<bool> tools_list_changed;
    std::optional<bool> prompts_list_changed;
    std::optional<bool> resources_list_changed;
    std::vector<std::string> resource_subscriptions;
};

Clients call subscriptions/listen with the filter; the server tracks entries via AddSubscription/AddSubscriptionEntry and dispatches notifications via NotifySubscribers, which matches the notification type against each subscription's filter. Notifications include io.modelcontextprotocol/subscriptionId in _meta (echoing a client-supplied ID, or a server-assigned incrementing ID otherwise). Task notifications (tasks/status|working|completed|failed|cancelled|input_required) go to all current subscribers (no filtering). ServerOptions::subscriptions_enabled is the master switch (default true; when off, subscriptions/listen returns MethodNotFound).

Semantic Helpers ​

McpSessionHandler provides:

cpp
bool IsJuly2026OrLater() const;
// Returns true if negotiated_version_ >= "2026-07-28"

This gates protocol-era-specific behavior in application code.

Protocol Infrastructure ​

MessageChannel ​

MessageChannel provides a bounded async message queue with backpressure, built on std::queue, std::mutex, and std::condition_variable:

  • AsyncReceive(callback) — blocks until a message arrives or channel is closed
  • Send(message) — blocks if buffer full (backpressure)
  • TrySend(message) — non-blocking send
  • Close() — wakes all waiters

Used by McpSessionHandler for the async message loop.

MessageFilter Pipeline ​

FilterPipeline chains multiple MessageFilter instances for interception (auth, audit, rate-limiting, request modification):

cpp
auto pipeline = std::make_shared<FilterPipeline>();
pipeline->AddFilter(std::make_shared<MessageFilterFuncAdapter>(
    [](const JsonRpcMessage& msg, MessageFilterNext next) {
        // Inspect/modify, then call next(filtered) or short-circuit
        next(msg);
    }));

Incoming filters wrap handler dispatch; outgoing filters wrap transport send. Both are optional and configured via ServerOptions::incoming_filters / outgoing_filters.

Error Code Remapping (2026-era) ​

Rev2026Codec::EncodeErrorCode no longer disguises internal errors as protocol error codes:

Error2026 Value
RequestTimeout, ConnectionRefused, TlsHandshakeFailedlogged and remapped to InternalError (-32603)

Protocol error codes (HeaderMismatch -32020, MissingRequiredClientCapability -32021, UnsupportedProtocolVersion -32022, UrlElicitationRequired -32042) pass through unchanged and carry canonical data in their real scenarios (e.g. requiredCapabilities, requested/supported).