Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
468e952
fix(spec): freeze 2026-07-28 release references
squarepots Sep 24, 2026
e252302
fix(spec): align 2025 anchor provenance with release pin
squarepots Sep 24, 2026
6fcda04
style(spec): format 2026 parity check
squarepots Sep 24, 2026
b8d4873
style(spec): match parity check formatting
squarepots Sep 24, 2026
8abfbf5
refactor(spec): narrow release cutover scope
squarepots Sep 24, 2026
9b24218
docs(review): use revision-matched spec authority
squarepots Sep 24, 2026
cfa09db
fix(client): stop listen() rejections escaping as unhandledRejection …
claude[bot] Sep 28, 2026
f091897
fix(codemod): keep a file's leading comment block above rewritten imp…
axits-lab Sep 28, 2026
e3fb9ed
fix(server): restore onclose after modern exchanges (#2778)
vjymisal0 Sep 28, 2026
c0f7aec
fix(client): inline jose types in the CommonJS declaration file (#2883)
claude[bot] Sep 28, 2026
d992df7
chore: remove CODEOWNERS patterns that match nothing on main (#2884)
claude[bot] Sep 28, 2026
efebf5b
docs(client): correct token endpoint TLS citation (#2768)
web-abin Sep 28, 2026
a4ae2f9
docs(client): correct the registerClient deprecation citation to spec…
claude[bot] Sep 28, 2026
e780e13
fix(client): preserve _meta on input_required results (#2862)
SyedTashfin Sep 28, 2026
9dd722f
fix(core): await the notification send so a failed send is never brie…
claude[bot] Sep 28, 2026
ef39308
fix(client): follow nextCursor until it is absent; stop on a repeated…
claude[bot] Sep 28, 2026
edd12e2
fix(client): deprecate omitting expectedIssuer and apply the SEP-2352…
maxisbey Sep 28, 2026
c55efa6
fix(server): close a listen stream that has honored nothing (#2651)
sushantkumar23 Sep 28, 2026
dd22ba2
Version Packages (#2849)
github-actions[bot] Sep 28, 2026
4d94e7b
fix(server): convert tool schemas on demand, not at registration (#2889)
claude[bot] Sep 29, 2026
7f4c12a
fix(node): make hono a regular dependency (#2897)
claude[bot] Sep 29, 2026
5238fba
fix(core): serve tasks/get and tasks/cancel on 2026-07-28 with an exp…
freya0926 Sep 30, 2026
2237555
Typecheck the three test workspace packages, and fix the registerProm…
sharziki Sep 30, 2026
e765b3b
fix(client): say so when the version probe gets an unusable reply (#2…
claude[bot] Sep 30, 2026
7231cf5
Merge branch 'main' into fix/freeze-2026-07-28-spec-reference
felixweinberger Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/era-gate-explicit-schema-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

A server can now serve, and a client can now call, `tasks/get` and `tasks/cancel` of the Tasks extension (SEP-2663) on a 2026-07-28 connection, when the handler is registered and the request is sent with an explicit schema. Every other method that a protocol revision removed is still refused. If one server factory serves both eras and such a handler is meant for 2025-era clients only, register it only when `ctx.era === 'legacy'`.
5 changes: 5 additions & 0 deletions .changeset/lazy-tool-schema-conversion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---

`registerTool` no longer converts tool schemas up front, so a server built per request stops converting every tool on every request. The warning about an invalid `x-mcp-header` declaration now appears each time tools are listed, not when the tool is registered.
5 changes: 0 additions & 5 deletions .changeset/loopback-localhost-subdomains.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/node-hono-regular-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/node': patch
---

`hono` is now a regular dependency of `@modelcontextprotocol/node`, so installs with strict peer-dependency checking no longer fail on the `hono` peer that `@hono/node-server` requires. No runtime change.
8 changes: 8 additions & 0 deletions .changeset/probe-unusable-2xx-reply-message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@modelcontextprotocol/client': patch
---

With `versionNegotiation` in `'auto'` or pin mode, a `server/discover` probe answered with a 2xx that carries no usable reply (an empty or
non-JSON body, a `204`, a missing or unexpected content type) still rejects `connect()` with `EraNegotiationFailed`. The message now says
`the server answered with an unusable reply (...)` instead of reading like a network failure. To connect to a 2025 server behind a front that
answers the probe this way, pass `connect(transport, { prior: { kind: 'legacy' } })` or use `mode: 'legacy'`.
5 changes: 5 additions & 0 deletions .changeset/register-prompt-context-only-overload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---

`McpServer.registerPrompt()` now types the callback correctly when no `argsSchema` is given: its one parameter is the server context. Before, reading `ctx.mcpReq` there was a type error although it worked at runtime. Prompts registered with an `argsSchema` are unchanged.
7 changes: 0 additions & 7 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,3 @@

# Default owners for everything in the repo
* @modelcontextprotocol/typescript-sdk

# Auth team owns all auth-related code
/src/server/auth/ @modelcontextprotocol/typescript-sdk-auth
/src/client/auth* @modelcontextprotocol/typescript-sdk-auth
/src/shared/auth* @modelcontextprotocol/typescript-sdk-auth
/src/examples/client/simpleOAuthClient.ts @modelcontextprotocol/typescript-sdk-auth
/src/examples/server/demoInMemoryOAuthProvider.ts @modelcontextprotocol/typescript-sdk-auth
95 changes: 0 additions & 95 deletions .github/workflows/update-spec-types.yml

This file was deleted.

4 changes: 2 additions & 2 deletions REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ review rounds and grows over time.
## Checklist

**Protocol & spec**
- Types match [`schema.ts`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) exactly (optional vs required fields)
- Types match the schema for the protocol revision being changed (`schema/<revision>/schema.ts` for released revisions; `schema/draft/schema.ts` only for unreleased work)
- Correct `ProtocolError` codes (enum `ProtocolErrorCode`); HTTP status codes match spec (e.g., 404 vs 410)
- Works for both stdio and Streamable HTTP transports — no transport-specific assumptions
- Cross-SDK consistency: check what `python-sdk` does for the same feature
Expand Down Expand Up @@ -61,7 +61,7 @@ When verifying spec compliance, consult the spec directly rather than relying on

- MCP documentation server: `https://modelcontextprotocol.io/mcp`
- Full spec text (single file, LLM-friendly): `https://modelcontextprotocol.io/llms-full.txt` — fetch to a temp file and grep for the relevant section
- Schema source of truth: [`schema.ts`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts)
- Schema source of truth: the revision-matched `schema.ts` in `modelcontextprotocol/modelcontextprotocol` (`schema/<revision>/schema.ts` for released revisions; `schema/draft/schema.ts` only for unreleased work)

## Recurring Catches

Expand Down
13 changes: 7 additions & 6 deletions docs/clients/machine-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { Client, ClientCredentialsProvider, StreamableHTTPClientTransport } from

const authProvider = new ClientCredentialsProvider({
clientId: 'reporting-job',
clientSecret: 'reporting-job-secret'
clientSecret: 'reporting-job-secret',
expectedIssuer: 'https://auth.example.com'
});

const client = new Client({ name: 'reporting-job', version: '1.0.0' });
Expand All @@ -26,9 +27,7 @@ await client.connect(transport);

`connect` discovers the server's authorization server, posts the grant to its token endpoint, and attaches the access token to every request. On a 401 the provider refreshes the token and the transport retries once. No browser, no end user.

::: tip
Pass `expectedIssuer` to pin the credential to the authorization server it was registered with. If discovery resolves a different issuer, the SDK throws `AuthorizationServerMismatchError` instead of sending the secret.
:::
`expectedIssuer` is the `issuer` of the authorization server the credential was registered with. If discovery resolves a different issuer, the SDK throws `AuthorizationServerMismatchError` instead of sending the secret. `PrivateKeyJwtProvider` and `CrossAppAccessProvider` below take it too; omitting it is deprecated, and the credential then goes to whichever authorization server the MCP server advertises.

## Bring your own bearer token

Expand All @@ -50,7 +49,8 @@ The transport calls `token()` before every request and sets the `Authorization`
const authProvider = new PrivateKeyJwtProvider({
clientId: 'reporting-job',
privateKey: pemEncodedKey,
algorithm: 'RS256'
algorithm: 'RS256',
expectedIssuer: 'https://auth.example.com'
});

const transport = new StreamableHTTPClientTransport(new URL('https://api.example.com/mcp'), { authProvider });
Expand Down Expand Up @@ -80,7 +80,8 @@ const authProvider = new CrossAppAccessProvider({
return grant.jwtAuthGrant;
},
clientId: 'reporting-job',
clientSecret: 'reporting-job-secret'
clientSecret: 'reporting-job-secret',
expectedIssuer: 'https://auth.example.com'
});

const transport = new StreamableHTTPClientTransport(new URL('https://api.example.com/mcp'), { authProvider });
Expand Down
27 changes: 20 additions & 7 deletions docs/migration/support-2026-07-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ falling back — see [Protocol versions](../protocol-versions.md). This holds ev
a front that answers the probe `403` but would pass `initialize`: relying on a legacy
fallback there is a deliberate non-goal — fix the auth wall (or pass credentials),
because auth status never selects an era. A `5xx` on the probe is a server failure,
also never era evidence: `connect()` rejects with `SdkHttpError(EraNegotiationFailed)`. Probe timeouts are **transport-aware**: on **stdio** a server that does not
also never era evidence: `connect()` rejects with `SdkHttpError(EraNegotiationFailed)`. A `2xx` probe answer that carries no usable reply (an empty or non-JSON body, a `204`, a missing or unexpected content type) is not era evidence either: `connect()` rejects with `SdkError(EraNegotiationFailed)`. Probe timeouts are **transport-aware**: on **stdio** a server that does not
answer within `timeoutMs` is treated as legacy and the client falls back to `initialize`
(some legacy servers never respond to unknown pre-`initialize`
requests at all); on **HTTP** a probe timeout rejects with `SdkError(RequestTimeout)` —
Expand Down Expand Up @@ -349,11 +349,20 @@ mismatch is rejected as an entry/routing error (`-32022 Unsupported protocol ver
for requests; drop + `onerror` for notifications).

Methods deleted by a protocol revision are **physically absent** from that era's
registry: an inbound `tasks/get` on a 2026-era connection gets `-32601` even if a
handler is registered, and sending an era-mismatched spec method (e.g. `server/discover`
toward a 2025-era peer, or any `tasks/*` method toward a 2026-era peer) throws
registry: an inbound `ping` or `tasks/result` on a 2026-era connection gets `-32601`
even if a handler is registered, and sending an era-mismatched spec method (e.g.
`server/discover` toward a 2025-era peer, or `ping` toward a 2026-era peer) throws
`SdkError(MethodNotSupportedByProtocolVersion)` before anything reaches the transport.

The one exception is the Tasks extension (SEP-2663), which keeps two names the
2025-11-25 revision had in core: `tasks/get` and `tasks/cancel`. A handler registered
for them with an explicit schema (`setRequestHandler('tasks/get', { params, result },
handler)`) is served, and a request sent with an explicit result schema
(`request({ method: 'tasks/get', params }, resultSchema)`) is sent, on every era. With
no handler, or with a method-keyed handler, they still get `-32601`. If one factory
serves both eras and such a handler is meant for 2025-era clients only, register it only
when `ctx.era === 'legacy'`; otherwise it answers 2026-07-28 requests too.

If you were on a v2 alpha and consumed wire schemas directly:

| v2-alpha pattern | Mechanical fix |
Expand Down Expand Up @@ -700,9 +709,13 @@ methods at compile time. `ResultTypeMap['tools/call']` is plain `CallToolResult`
maps still carry the `tasks/*` entries and the `CreateTaskResult` unions; narrow with
the `isCallToolResult` guard if you are pinned to one of those alphas. `2.0.0-alpha.4`
and later include the exclusion.) Where
task interop is genuinely required, use the explicit-schema custom-method form
(`request({ method: 'tasks/get', params }, GetTaskResultSchema)`). Inbound `tasks/*`
requests → `-32601`.
task interop is genuinely required, use the explicit-schema custom-method form on both
sides: `request({ method: 'tasks/get', params }, resultSchema)` to send and
`setRequestHandler('tasks/get', { params, result }, handler)` to serve, with the schemas
of the revision you are talking to (`GetTaskResultSchema` is the 2025-11-25 shape). On a
2026-era connection only `tasks/get` and `tasks/cancel`, the two names the Tasks
extension keeps, are served and sent this way; `tasks/result` and `tasks/list` stay
refused there. With no handler registered, inbound `tasks/*` requests get `-32601`.

The experimental tasks **interception** layer is removed entirely — see
[upgrade-to-v2.md › Experimental tasks interception removed](./upgrade-to-v2.md#experimental-tasks-interception-removed).
Expand Down
Loading
Loading