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:

tools/call request (recorded)
{
  "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"
  }
}
tools/call result, October 8, 2026
{
  "_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:

server/discover result (recorded)
{
  "_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:

tools/list result (recorded)
{
  "_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/listen stream, 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 (GET and DELETE get 405). They see the change on their next tools/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.