server/discover: the one method every MCP server must implement The 2026-07-28 revision of the Model Context Protocol (MCP) requires every server to implement the server/discover method, which replaces the removed initialize handshake by advertising supported protocol versions, capabilities and identity without any prior handshake. Clients may call server/discover first or negotiate inline by sending their protocol version in each request's _meta, receiving error -32022 for a version the server cannot serve, while older servers answer with -32601 (Method not found) and clients fall back to the legacy handshake. Identity now travels on every message, with clients SHOULD sending clientInfo in each request's _meta and servers SHOULD sending serverInfo in each result's _meta. server/discover: the one method every MCP server must implement When the 2026-07-28 revision of the Model Context Protocol MCP dropped the initialize handshake, a client needed another way to learn what a server supports before it starts using it. That job now belongs to a single method, server/discover https://modelcontextprotocol.io/specification/2026-07-28/server/discover . It’s the one method every 2026-07-28 server MUST implement, which is why it gets a part of its own. This is part 3 of my series on what an MCP server does under the 2026-07-28 specification. Part 2 https://pournasserian.com/writing/mcp-2026-what-changed covered what the revision took away, the handshake among it. This part covers what took its place: what discovery returns, how a client settles on a protocol version with old and new servers alike, and how identity now travels with every message. The facts are as I read them in October 2026. In brief 1. Every 2026-07-28 server MUST implement server/discover . It advertises the protocol versions the server supports, its capabilities and its identity, and a client can call it without any prior handshake. 2. Calling it first is optional for the client. Every request carries its protocol version in meta , so negotiation can also happen inline, and a version the server can’t serve gets error -32022 . 3. The same call tells you when a server is older. It answers server/discover with -32601 Method not found , and the client falls back to the legacy handshake. Software development kits SDKs do this for you. 4. Identity travels on every message. Clients SHOULD send clientInfo in each request’s meta , and servers SHOULD send serverInfo in each result’s meta . 5. Capabilities describe the server, not the connection. My advice is to keep discovery cheap, deterministic and cacheable. What discovery returns A client that calls server/discover can use it to pick a version up front, or as a backward-compatibility probe on standard input and output stdio . The request is a JSON-RPC 2.0 message, and it can be this small: { "jsonrpc": "2.0", "id": 1, "method": "server/discover" } And here is a result, from a documentation server called acme-docs : { "jsonrpc": "2.0", "id": 1, "result": { "resultType": "complete", "supportedVersions": "2026-07-28" , "capabilities": { "tools": { "listChanged": true }, "resources": {}, "prompts": {}, "extensions": { "io.modelcontextprotocol/ui": {}, "io.modelcontextprotocol/tasks": {} } }, "instructions": "Use search before fetch. Results expire after 24 hours.", " meta": { "io.modelcontextprotocol/serverInfo": { "name": "acme-docs", "version": "3.2.0" } }, "ttlMs": 3600000, "cacheScope": "public" } } | Field | What it’s for | |---|---| | supportedVersions | The protocol revisions this server can speak, on a per-request basis | | capabilities | Which primitives and utilities exist tools , resources , prompts and so on and which extensions the server supports | | instructions | Optional plain-text guidance for the model | | meta "io.modelcontextprotocol/serverInfo" | The server’s identity: a name and a version, and optionally a title and icons | | ttlMs , cacheScope | How long, and how widely, the result may be cached | It helps to set it beside MCP Server Cards, which came up in my survey of MCP servers for websites https://pournasserian.com/writing/mcp-servers-for-websites-2026 . A Server Card helps an agent find a server before it connects, and says how to connect. server/discover runs after the client has connected, and tells it what this server can do. Version negotiation, and the fallback to the handshake Each request names the protocol version it uses. When a client asks for a version the server can’t serve, the server returns UnsupportedProtocolVersionError , code -32022 . A server that predates 2026-07-28 doesn’t know server/discover at all, so it answers with error -32601 Method not found , and the client falls back to initialize : You’ll rarely write this yourself. As of October 2026, these hosts and SDKs handle it for you: | Host or SDK | What it does | |---|---| | Claude Code https://code.claude.com/docs/en/mcp | Its v2 MCP runtime asks HTTP and stdio servers whether they support the newer revision, and uses it with those that do | | Ruby SDK https://ruby.sdk.modelcontextprotocol.io/server/discovery/ | Probes server/discover and falls back to the handshake. Its docs note that “the lifecycle is a per-request property, not a server-wide mode” | | C SDK https://csharp.sdk.modelcontextprotocol.io/v2/api/ModelContextProtocol.Server.McpServerOptions.html | Can be pinned to 2026-07-28, and then rejects initialize handshakes | | Python SDK https://py.sdk.modelcontextprotocol.io/protocol-versions/ , mcp 2.3.0 | Client probes server/discover and falls back to initialize in its default mode="auto" . mode="legacy" uses the handshake only, and mode="2026-07-28" the only version string 2.3.0 accepts pins that version without probing | Reading discovery from Python Here is a cut-down acme-docs server one tool, no extensions built with the official Python SDK’s MCPServer . The in-memory Client connects to it and prints what discovery returned: python import anyio from mcp import Client from mcp.server import CacheHint, MCPServer mcp = MCPServer "acme-docs", version="3.2.0", instructions="Use search before fetch. Results expire after 24 hours.", cache hints={"server/discover": CacheHint ttl ms=3 600 000, scope="public" }, @mcp.tool def search query: str - list str : """Search the docs and return matching page ids.""" return the search itself doesn't matter here async def main : async with Client mcp as client: print client.protocol version print client.server capabilities.model dump by alias=True, exclude none=True print client.server info.name, client.server info.version found = client.session.discover result print found.supported versions, found.ttl ms, found.cache scope print found.instructions async with Client mcp, mode="legacy" as old: print old.protocol version, old.session.discover result anyio.run main It prints: 2026-07-28 {'prompts': {'listChanged': True}, 'resources': {'subscribe': True, 'listChanged': True}, 'tools': {'listChanged': True}} acme-docs 3.2.0 '2026-07-28' 3600000 public Use search before fetch. Results expire after 24 hours. 2025-11-25 None The last line is the same server answering the legacy handshake: with mode="legacy" the client settles on 2025-11-25 and holds no discover result. Three SDK defaults are worth knowing, because the sample above overrides two of them: - MCPServer declares tools , resources and prompts whatever you register: a server with nothing registered declares the same three. - Without a CacheHint , its discovery result carries ttlMs: 0 and cacheScope: "private" . - Without version= , it reports an empty version. Identity and capabilities Identity on every message Identity no longer travels only once, at connection time: - Clients SHOULD identify themselves on each request with io.modelcontextprotocol/clientInfo in meta . - Servers SHOULD identify themselves in each result’s meta with io.modelcontextprotocol/serverInfo . The Python SDK does both for you: Client puts clientInfo into every request it makes under 2026-07-28, and MCPServer puts serverInfo into every result it returns under that revision. The spec only asks clients to send clientInfo . My advice goes further: log it with every request. When something works in one host and fails in another, that field tells you who called, and with which version. What to declare The capabilities map names the primitives you offer, the three I described in part 1 https://pournasserian.com/writing/mcp-2026-what-a-server-does , and a few more: | Capability | Declare it when | Notes | |---|---|---| | tools | You expose tools | listChanged: true if the set can change at runtime | | resources | You expose resources, embed resources, or serve Skills | The Skills extension requires it | | prompts | You expose prompts | listChanged as for tools | | completions | You support argument completion for prompts or resource templates | | | extensions | You support any extension | A map from extension identifier to a settings object; {} means no settings | With the Python SDK, the first three rows aren’t your choice: MCPServer declares all three, as the output above showed. Capabilities describe what the server can do. They must not vary per connection or change as a side effect of other requests, though they may vary with the caller’s authorization, because credentials are input to each request. The specification sets the same rule for the tools, resources and prompts a server lists. My advice: make server/discover cheap, deterministic and cacheable, and don’t hit a database to build it. Clients may call it often, and intermediaries may cache it publicly if you set cacheScope: "public" . If your capabilities depend on who is calling, either keep discovery generic and filter at the list level, or mark the result private . Error codes you’ll meet | Code | Name | When | |---|---|---| | -32601 | Method not found | An unknown method, or a legacy method such as tasks/result | | -32602 | Invalid params | Bad arguments, an unknown tool or prompt, or a resource not found which used to be -32002 | | -32603 | Internal error | An unexpected server failure | | -32020 | HeaderMismatch | The HTTP routing headers disagree with the body | | -32021 | MissingRequiredClientCapability | The operation needs a capability or extension the client didn’t declare | | -32022 | UnsupportedProtocolVersion | The requested protocol version isn’t supported | The ranges matter when you add codes of your own. Here is how the specification’s error codes section https://modelcontextprotocol.io/specification/2026-07-28/basic/index divides them: | Range | What the specification says | |---|---| | -32020 to -32099 | Reserved for the MCP specification. Implementations MUST NOT emit codes from it that the specification doesn’t define | | -32000 to -32019 | A legacy range. Codes SDKs already use there are grandfathered, but new codes MUST NOT be allocated in it, and new implementations SHOULD NOT use it at all. Apart from -32002 , receivers MUST NOT assume any specific meaning for its codes | | Codes of your own | SHOULD sit outside the JSON-RPC reserved range, -32768 to -32000 | Method and caveats - Built from my guide to what an MCP server does in 2026, written against the 2026-07-28 specification. - What Claude Code, the Ruby SDK and the C SDK do is as their documentation described it in October 2026. - The Python sample was run against mcp 2.3.0 on 8 October 2026 with the in-memory Client , and the output above is what it printed. The notes on MCPServer ’s defaults come from a second run, with an empty server, and from the package’s source. - Where the specification is more precise than my notes the error-code ranges , this article follows the specification’s error codes section. Series What an MCP server actually does in 2026 Part 3 of 35 1. What an MCP server does in 2026: much more than a list of tools https://pournasserian.com/writing/mcp-2026-what-a-server-does 2. MCP goes stateless: what changed in the 2026-07-28 specification https://pournasserian.com/writing/mcp-2026-what-changed 3. server/discover: the one method every MCP server must implement 4. Server instructions: the paragraph every model reads first https://pournasserian.com/writing/mcp-2026-server-instructions 5. Stateless MCP requests: meta, resultType and explicit handles https://pournasserian.com/writing/mcp-2026-stateless-requests 6. Caching hints and pagination in MCP https://pournasserian.com/writing/mcp-2026-caching-and-pagination 7. MCP transports in 2026: stdio and Streamable HTTP https://pournasserian.com/writing/mcp-2026-transports