
# Discovery

> `server/discover` replaces the `initialize` handshake.

Modern clients do not negotiate. Every request carries what the server needs to know, and a single cacheable `server/discover` call tells the client what the server is.

## Per-Request `_meta`

Instead of handshake state, each request carries reserved keys in `params._meta`:

```jsonc
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "hello",
    "arguments": { "name": "world" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28", // required
      "io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}, // required
      "progressToken": "abc", // optional, opts into a response stream
      "io.modelcontextprotocol/logLevel": "info", // optional, same
    },
  },
}
```

The protocol version and client capabilities are **required** on every modern request; a request without them is rejected at the boundary. `h3-mcp` validates and surfaces them on the request context:

```ts
handler: (event) => {
  const mcp = event.context.mcp!;
  mcp.protocolVersion; // "2026-07-28"
  mcp.era; // "modern"
  mcp.clientInfo; // { name, version, ... } — client SHOULD send this
  mcp.clientCapabilities; // {} — what the client can do
  mcp.trace; // W3C traceparent / tracestate / baggage, if propagated
};
```

Requiring a capability is one call, and throws `-32021` when the client did not declare it:

```ts
event.context.mcp!.requireClientCapability?.("sampling");
```

:read-more{to="/guide/api/request-context" title="Request context reference"}

## `server/discover`

The method is implemented from your handler options — there is nothing to wire up:

```jsonc
// ← result
{
  "resultType": "complete",
  "supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"],
  "capabilities": { "tools": {}, "resources": {}, "prompts": {} },
  "instructions": "...",
  "ttlMs": 3600000,
  "cacheScope": "public",
  "_meta": {
    "io.modelcontextprotocol/serverInfo": { "name": "my-server", "version": "1.0.0" },
  },
}
```

`supportedVersions` reflects the configured [`era`](/guide/eras) — a modern-only server lists only `2026-07-28`. `capabilities` is derived from what you declared, and `instructions`, `title`, `icons`, and `websiteUrl` come from the handler options.

Because discovery output changes rarely, it is worth an explicit cache window:

```ts
defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  cache: { discover: { ttlMs: 3_600_000, cacheScope: "public" } },
});
```

:read-more{to="/guide/modern/caching" title="Caching"}

## Echoed Server Info

Every modern result carries `_meta["io.modelcontextprotocol/serverInfo"]`, so a client that reconnected or lost its discovery cache still knows who answered. Opt out if you would rather not repeat it:

```ts
defineMcpHandler({ name: "my-server", version: "1.0.0", echoServerInfo: false });
```

## Result Type

Modern results also carry `resultType`, which is `"complete"` unless a handler asked the client for more input:

:read-more{to="/guide/modern/mrtr" title="Multi round-trip requests"}
