MCP 2026-07-28 notes
Kervan serves the 2026-07-28 revision of MCP and the 2025 revisions (up to 2025-11-25) from the same app. This page explains what differs.
Where this fits: Run and operate. The protocol details behind the transports.
On this page
Stateless requests
In 2026-07-28 there is no initialize handshake and no session. Every request carries its own
context in params._meta: the protocol version and the client’s capabilities. Over HTTP the
method also travels in headers (MCP-Protocol-Version, MCP-Method, and MCP-Name for tool
calls), so proxies can route without reading the body.
Here is a real request to the example server and its answer, recorded from kervan run --http:
{
"id": 3,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
},
"arguments": {
"name": "Ankara"
},
"name": "search_city"
}
}{
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "open-meteo",
"version": "0.1.0"
}
},
"content": [
{
"text": "[{\"name\":\"Ankara\",\"country\":\"Republic of Türkiye\",\"latitude\":39.91987,\"longitude\":32.85427},{\"name\":\"Krist’esi\",\"country\":\"Georgia\",\"latitude\":42.55768,\"longitude\":43.398},{\"name\":\"Enakara Ambony\",\"country\":\"Madagascar\",\"latitude\":-24.63333,\"longitude\":46.9},{\"name\":\"Ankara\",\"country\":\"Madagascar\",\"latitude\":-25.46667,\"longitude\":45.68333},{\"name\":\"Ankara\",\"country\":\"Madagascar\",\"latitude\":-25.43333,\"longitude\":45.71667}]",
"type": "text"
}
],
"resultType": "complete"
}server/discover
A client that wants to know what a server offers before calling it sends server/discover. The
answer lists the supported versions, the capabilities, the server’s instructions (a spec’s
description) and its name and version in _meta:
{
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "open-meteo",
"version": "0.1.0"
}
},
"cacheScope": "private",
"capabilities": {
"tools": {
"listChanged": true
}
},
"instructions": "Weather and city lookup backed by the public Open-Meteo APIs (no API key needed).",
"resultType": "complete",
"supportedVersions": [
"2026-07-28"
],
"ttlMs": 0
}tools/list answers with each tool’s name, title, description, input and output schemas and
annotations:
{
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "open-meteo",
"version": "0.1.0"
}
},
"cacheScope": "private",
"resultType": "complete",
"tools": [
{
"annotations": {
"openWorldHint": true,
"readOnlyHint": true
},
"description": "Finds up to 5 places by name and returns their coordinates.",
"inputSchema": {
"properties": {
"name": {
"description": "City name, e.g. Ankara",
"maxLength": 100,
"minLength": 2,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"name": "search_city",
"title": "Search city"
},
{
"annotations": {
"openWorldHint": true,
"readOnlyHint": true
},
"description": "Current temperature (°C), wind speed (km/h) and WMO weather code at a coordinate.",
"inputSchema": {
"properties": {
"latitude": {
"maximum": 90,
"minimum": -90,
"type": "number"
},
"longitude": {
"maximum": 180,
"minimum": -180,
"type": "number"
}
},
"required": [
"latitude",
"longitude"
],
"type": "object"
},
"name": "get_current_weather",
"outputSchema": {
"properties": {
"temperatureC": {
"type": "number"
},
"weatherCode": {
"type": "integer"
},
"windKmh": {
"type": "number"
}
},
"required": [
"temperatureC",
"windKmh",
"weatherCode"
],
"type": "object"
},
"title": "Current weather"
}
],
"ttlMs": 0
}list_changed
When the tool set changes (an app.tool() at runtime, a reloaded spec), clients are told with
notifications/tools/list_changed, only when the list really changed:
- 2026-07-28 clients receive it on their
subscriptions/listenstream, over stdio and HTTP. - 2025-era clients over stdio (and in memory) receive it too.
- 2025-era clients over HTTP do not: Kervan serves them statelessly, without the session
stream a push would need (
GETandDELETEget405). They see the change on their nexttools/list.
Clients of the 2025 revisions
They are served from the same app, statelessly over HTTP: initialize works, sessions are not
created. A server can refuse them with legacy: "reject" on the HTTP handler.
Logging
ctx.log writes to the server’s logger (stderr) only. Forwarding log messages to the client
(notifications/message) is opt-in with protocolLogging, and only happens when the request’s
_meta asks for a logLevel; the logging capability is not declared by default.