Skip to content

feat(server): createRequestStateCodec; pin conformance to main@d70d7ad; toNodeHandler onerror + ctx.log spec-align#2332

Open
felixweinberger wants to merge 3 commits into
fweinberger/on-tonodehandlerfrom
fweinberger/on-codec-conformance
Open

feat(server): createRequestStateCodec; pin conformance to main@d70d7ad; toNodeHandler onerror + ctx.log spec-align#2332
felixweinberger wants to merge 3 commits into
fweinberger/on-tonodehandlerfrom
fweinberger/on-codec-conformance

Conversation

@felixweinberger

Copy link
Copy Markdown
Contributor

Adds createRequestStateCodec (opt-in HMAC sealing for the multi-round-trip requestState), pins the conformance referee to a local build of main @ d70d7ad, and folds in two follow-up fix rounds for earlier PRs in this stack. Stacked on fweinberger/on-tonodehandler.

Motivation and Context

createRequestStateCodec

The multi-round-trip requestState hook expects callers to seal opaque state across the client round-trip; today every consumer hand-rolls node:crypto HMAC. New createRequestStateCodec({ key, ttlSeconds?, bind? }) in @modelcontextprotocol/server returns { mint, verify }:

  • WebCrypto-based (subtle.importKey/sign/verify), runtime-neutral, constant-time MAC check
  • wire format "v1." b64url({"p":payload,"exp":unix,"b":bind?}) "." b64url(mac); TTL default 600 s; key ≥32 bytes else RangeError
  • bind?: (ctx) => string for principal/method binding — stored as a domain-separated keyed tag (b64url(HMAC(key, "mcp.requestState.bind:" + bind(ctx))[:16])), so the signed-not-encrypted body never carries a client-readable principal identifier
  • fail-closed; thrown reasons are fixed opaque codes (malformed/mac/expired/bind) — never decoded payload or binding values
  • ServerOptions.requestState.verify return widened void|Promise<void>unknown|Promise<unknown> so codec.verify is directly assignable (seam already awaited-and-discarded)

examples/mrtr/server.ts and test/conformance/src/everythingServer.ts switch from hand-rolled HMAC to the helper; the input-required-result-tampered-state conformance scenario is now its proof. JSDoc and migration.md §requestState carry the signed-not-encrypted note (payload p is client-readable; use AEAD for confidentiality; the bind tag is keyed so it is not).

Conformance referee pin

@modelcontextprotocol/conformance repointed from 0.2.0-alpha.4 to a vendored tarball built from origin/main @ d70d7ad (file:./vendor/...tgz, version 0.2.0-main.d70d7ad). README documents the rebuild recipe + tarball sha256. What main brings vs alpha.4: modelcontextprotocol/conformance#337 (gates the SEP-2350 scope-union WARNING), #347 (client-scenario mocks serve server/discover natively), #348 (re-syncs draft spec types), #353 (renumbers draft error codes per modelcontextprotocol/modelcontextprotocol#2907), #262 (SEP-2663 tasks-lifecycle scenario). expected-failures*.yaml reconciled so all gate legs exit 0; the four #353-fallout entries are annotated as renumber-pending (the SDK error codes are flipped in fweinberger/on-renumber, later in this stack).

Follow-up fixes folded in

  • modern-era ctx.mcpReq.log: absent _meta['io.modelcontextprotocol/logLevel'] on a 2026-era request now suppresses (spec: absent → MUST NOT send notifications/message); legacy-era branch unchanged. The e2e scenario body sets the _meta key explicitly.
  • toNodeHandler(handler, opts?): new opts.onerror(error) is called before the adapter-level 500 fallback when request-conversion / handler.fetch throws; wrapped in try { … } catch {} so a throwing user callback never escapes past the 500/−32603 fallback. New ToNodeHandlerOptions exported from @modelcontextprotocol/node.
  • { fetch, close, notify }{ fetch, close, notify, bus } aligned in .changeset/create-mcp-handler.md, McpHttpHandler JSDoc, and migration-SKILL.md.

How Has This Been Tested?

  • 13 unit tests in packages/server/test/server/requestStateCodec.test.ts (round-trip, body/MAC tamper, cross-key, malformed×4, expired, bind ok/mismatch-opaque/mint-requires-ctx, key-too-short, hook-assignability, decoded-body-never-carries-raw-bind)
  • node suite +2 (onerror called; throwing onerror swallowed)
  • conformance server:draft 77p/0f at the codec commit (all 14 input-required-result-* scenarios pass incl. tampered-state); after the pin reconcile all gate legs (client:all, client:2026, server, server:draft, server:2026) exit 0
  • Full gates at tip: typecheck, lint, server (327), node (88), e2e (2535p/205xf), integration (348)

Breaking Changes

None. createRequestStateCodec is opt-in; ServerOptions.requestState.verify widening is backward-compatible.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

server:all remains red on the pre-existing json-schema-2020-12 / server-sse-polling pair plus the new SEP-2663 tasks-* pending-only scenarios from conformance#262 — not a gate leg, untouched. Reference shape: signed mode only; AEAD/HKDF deferred.

@felixweinberger felixweinberger requested a review from a team as a code owner June 19, 2026 14:18
@changeset-bot

changeset-bot Bot commented Jun 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 75506c6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@modelcontextprotocol/server Minor
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major
@modelcontextprotocol/node Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2332

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2332

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2332

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2332

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2332

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2332

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2332

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2332

commit: 75506c6

Comment thread packages/server/src/server/requestStateCodec.ts
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from f2a3ec6 to f0848e2 Compare June 19, 2026 14:37
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from f9069fa to c8779f8 Compare June 19, 2026 14:37
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from f0848e2 to f4e4654 Compare June 19, 2026 14:50
Comment on lines +322 to 340
// request method on that revision). The spec at 2026-07-28
// says an absent key means the server MUST NOT send
// `notifications/message` for the request — so an absent key
// suppresses, it does not mean "send everything". On
// 2025-era connections the session-scoped level set via
// `logging/setLevel` applies exactly as before (an absent
// session level there continues to mean no filter).
let threshold: LoggingLevel | undefined;
if (this._servedModernEra()) {
threshold = ctx.mcpReq.envelope?.[LOG_LEVEL_META_KEY] as LoggingLevel | undefined;
if (threshold === undefined) {
return Promise.resolve();
}
} else {
threshold = this._loggingLevels.get(undefined);
}
if (threshold !== undefined && this.LOG_LEVEL_SEVERITY.get(level)! < this.LOG_LEVEL_SEVERITY.get(threshold)!) {
return Promise.resolve();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Two follow-ups on the rewritten ctx.mcpReq.log() level filter: (1) the legacy branch reads the threshold via this._loggingLevels.get(undefined), but logging/setLevel stores the level under the transport session id, so on a sessionful 2025-era connection handler-emitted logs ignore the client-set level (this lookup pre-dates the PR, but these exact lines are rewritten and re-documented here — consult ctx.sessionId with a fallback to the undefined key); (2) the new modern-era behavior — an absent _meta['io.modelcontextprotocol/logLevel'] key now suppresses the notification entirely — is documented in the changeset and e2e test, but docs/server.md's Logging section still tells authors to call ctx.mcpReq.log() inside any handler with no mention that on 2026-07-28 requests delivery requires the per-request logLevel envelope key, so the documented pattern silently does nothing for the common case.

Extended reasoning...

Part 1 — legacy branch ignores the session-scoped logging/setLevel level (pre-existing).

The logging/setLevel handler (_registerLoggingHandler, server.ts ~290) stores the client's chosen level keyed by the transport session id:

const transportSessionId = ctx.sessionId || (ctx.http?.req?.headers.get('mcp-session-id') as string) || undefined;
this._loggingLevels.set(transportSessionId, parseResult.data);

So on a sessionful Streamable HTTP connection the level lives under the session-id key. The legacy branch of the rewritten filter in buildContext (server.ts:336) reads only this._loggingLevels.get(undefined) — it never consults ctx.sessionId — so it finds nothing and applies no filter.

Step-by-step: (1) a 2025-era client on a sessionful Streamable HTTP transport sends logging/setLevel with level: 'warning'; the level is stored under the session id (e.g. 'abc-123'). (2) A tool handler calls ctx.mcpReq.log('debug', ...). (3) _servedModernEra() is false, so threshold = this._loggingLevels.get(undefined)undefined. (4) The severity comparison is skipped (threshold !== undefined is false) and the debug notification is sent — even though the client asked for warning and above. By contrast, Server.sendLoggingMessage(params, sessionId) honors the per-session level via isMessageIgnored(level, sessionId), and the changeset/migration prose says ctx.mcpReq.log "respects the client's log level filter", so the behavior contradicts both the sibling API and the docs.

This lookup-by-undefined is byte-identical to the pre-PR ternary, so the PR did not introduce it — but the PR rewrites these exact lines into the new if/else and re-documents the legacy semantics in the comment ("the session-scoped level set via logging/setLevel applies exactly as before"), so this is the natural place to fix it. ctx.sessionId is in scope in buildContext; the fix is this._loggingLevels.get(ctx.sessionId) ?? this._loggingLevels.get(undefined) (or just reuse isMessageIgnored(level, ctx.sessionId)).

Part 2 — docs/server.md Logging prose now contradicts the modern-era behavior (introduced by this PR).

The new branch (server.ts:330-334) makes an absent _meta['io.modelcontextprotocol/logLevel'] key on a 2026-07-28-served request return early and emit nothing, where previously an absent key meant "no filter, send". The change is recorded in the changeset, and the e2e scenario (handler-context.test.ts) had to be updated to set the _meta key explicitly to keep the log delivered — which is itself evidence the default path now suppresses.

However, docs/server.md's "Logging" section (lines 345-376) — untouched by this PR — still tells server authors to declare the logging capability and "call ctx.mcpReq.log(level, data) inside any handler", with examples that just call ctx.mcpReq.log('info', ...). There is no mention of the per-request io.modelcontextprotocol/logLevel envelope key anywhere in docs/server.md or docs/client.md. After this change, the documented pattern silently does nothing for any 2026-era request whose client did not set that key — the common case, since nothing in the docs tells clients to set it. Per the repo checklist ("Bugfix or behavior change: check whether docs/**/*.md describes the old behavior and needs updating"), the Logging section should add a sentence noting that on 2026-07-28 requests delivery requires the client to set the per-request io.modelcontextprotocol/logLevel _meta key.

Impact and fix. Part 1: real correctness gap on the legacy sessionful path, but pre-existing — fix or note, not blocking. Part 2: documentation-only; one or two sentences in docs/server.md's Logging section close the gap. Neither blocks the PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part (1) is now addressed: the legacy branch of the ctx.mcpReq.log() filter in buildContext (packages/server/src/server/server.ts:342) reads this._loggingLevels.get(ctx.sessionId) ?? this._loggingLevels.get(undefined), so a sessionful 2025-era connection's logging/setLevel threshold is honored.

Part (2) is still open: docs/server.md's Logging section (lines 345-376) is unchanged — it still tells authors to declare the logging capability and call ctx.mcpReq.log(level, data) inside any handler, with no mention that on 2026-07-28-served requests delivery now requires the client to set the per-request io.modelcontextprotocol/logLevel _meta envelope key (an absent key suppresses the notification entirely under the new behavior in this PR). One or two sentences in that section would close the gap. Non-blocking.

Comment thread docs/migration-SKILL.md
Comment on lines 9 to 10
# NOTE: the SDK's modern-path rejection codes are aligned with what this
# referee asserts: header/body mismatches answer -32001 (HeaderMismatch) and a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The header NOTE in test/conformance/expected-failures.yaml (lines 9-13) still claims the SDK's modern-path rejection codes "are aligned with what this referee asserts" (-32001 / -32602), but the conformance#353 entries this same PR adds further down in this file (and in expected-failures.2026-07-28.yaml) state the new pin asserts -32020/-32021/-32022 while the SDK still emits -32001/-32003/-32004 — which is exactly why the new expected-failure entries exist. Rewrite or drop the NOTE (e.g. point at the #353 sections / the fweinberger/on-renumber follow-up) so the file's header doesn't contradict its own entries.

Extended reasoning...

What the inconsistency is. The PR repoints the conformance referee from the published 0.2.0-alpha.4 to a vendored build of main @ d70d7ad and rewrites the adjacent "Baseline established against..." paragraph (lines 4-7) of test/conformance/expected-failures.yaml to name the new pin. However, the NOTE immediately below it (lines 9-13) is carried forward unchanged: it still states that "the SDK's modern-path rejection codes are aligned with what this referee asserts: header/body mismatches answer -32001 (HeaderMismatch) and a missing _meta envelope (or missing protocolVersion key) answers -32602", and that mismatched cells would only be re-derived "if a future published conformance release changes those assignments".

Why it is now false at this pin. "This referee" now refers to the vendored 0.2.0-main.d70d7ad build, which carries the conformance#353 error-code renumber (spec#2907). The very entries this PR adds further down in the same file say so explicitly: under the new # --- conformance#353 error-code renumber (spec#2907) --- sections, the comments state the referee at this pin asserts -32020/-32021/-32022 for header/body mismatch, missing-required-client-capability, and unsupported-protocol-version, while the SDK "still emits -32001/-32003/-32004". The same wording appears in the new entries added to expected-failures.2026-07-28.yaml. The four #353-fallout expected failures (request-metadata, http-custom-header-server-validation, http-header-validation, server-stateless) exist precisely because the SDK's codes are no longer aligned with the referee — the future-tense escape hatch in the NOTE ("if a future ... release changes those assignments") has already happened at this pin, just via a vendored main build rather than a published release.

Step-by-step walk-through of the contradiction as the file stands after this PR:

  1. Lines 4-7 (rewritten by this PR): baseline is established against 0.2.0-main.d70d7ad.
  2. Lines 9-10 (untouched): "the SDK's modern-path rejection codes are aligned with what this referee asserts: header/body mismatches answer -32001 (HeaderMismatch)...".
  3. The new server-section entry added by this PR for http-custom-header-server-validation: "SDK still emits −32001/−32003/−32004; the referee at this pin asserts −32020/−32021/−32022 for header/body mismatch, ..." — i.e. the referee does not assert -32001 for header/body mismatch, directly contradicting step 2.
  4. A reader (or the next person bumping the pin) following the header NOTE would conclude no error-code reconciliation is pending, when the file's own Notification on Capabilities #353 sections — and the fweinberger/on-renumber branch later in this stack, per the PR description — say the opposite.

Why nothing catches it. The expected-failures evaluator only checks scenario names against actual results (and stale entries); comment prose in the YAML is never validated, so the contradiction passes every gate silently.

Impact. Documentation/comment accuracy only — no runtime or CI behaviour is affected. The cost is reader confusion in the file that is supposed to be the authoritative record of why each conformance cell is expected to fail, which matters most when the next pin bump or the renumber follow-up reconciles this file.

Fix. Rewrite (or drop) the NOTE so it reflects the d70d7ad pin — e.g. note that at this pin the referee asserts the conformance#353-renumbered codes (-32020/-32021/-32022), the SDK still emits -32001/-32003/-32004, the affected cells are tracked in the #353 sections below (and in expected-failures.2026-07-28.yaml), and the SDK-side flip lands in fweinberger/on-renumber. The -32602 missing-envelope statement can be kept if it still holds, but the headline alignment claim should go.

Comment thread packages/middleware/node/src/toNodeHandler.ts
Comment thread packages/server/src/server/requestStateCodec.ts
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from c8779f8 to d6e2be7 Compare June 22, 2026 16:54
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from f4e4654 to 50ac3ce Compare June 22, 2026 16:54
Comment thread test/conformance/expected-failures.yaml Outdated
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from d6e2be7 to 7327fb9 Compare June 22, 2026 17:53
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from 50ac3ce to 47f361b Compare June 22, 2026 17:53
Comment on lines +137 to +153
it('binds the version prefix into the MAC (a transplanted body.mac under a different prefix fails)', async () => {
const codec = createRequestStateCodec({ key: KEY });
const wire = await codec.mint({ ok: true });
// The MAC covers `"v1." + body`. The verifier hard-gates the prefix,
// so to assert the MAC binding we recompute it manually with `"v2."`
// over the same body and confirm the codec rejects that pair.
const body = wire.slice('v1.'.length, wire.lastIndexOf('.'));
const cryptoKey = await crypto.subtle.importKey('raw', KEY, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
const v2Mac = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(`v2.${body}`)));
const b64url = (b: Uint8Array) =>
btoa(String.fromCodePoint(...b))
.replaceAll('+', '-')
.replaceAll('/', '_')
.replace(/=+$/, '');
// Same body, MAC computed for a v2 prefix, presented under v1 — MAC fails.
await expect(codec.verify(`v1.${body}.${b64url(v2Mac)}`, fakeCtx('tools/call'))).rejects.toThrow('mac');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The 'binds the version prefix into the MAC' test asserts rejection of a MAC computed over 'v2.' + body presented under the v1. prefix — but any mismatching MAC satisfies that assertion (the sibling 'rejects a tampered MAC' test already covers it), so a hypothetical regression where mint/verify drop the prefix and MAC only the bare body would still pass this test. The discriminating case is the opposite construction: compute the MAC over the bare body (no prefix) and present it under v1. — the prefix-binding implementation rejects it, while a body-only-MAC regression would accept it. The shipped codec does bind the prefix correctly, so this is a test-effectiveness nit only.

Extended reasoning...

What the issue is. The new test 'binds the version prefix into the MAC (a transplanted body.mac under a different prefix fails)' (packages/server/test/server/requestStateCodec.test.ts:137-153) is intended to lock in the security property the PR/commit message highlights ('prefix bound into MAC'): mint MACs PREFIX + body, so a body.mac pair cannot be transplanted across codec versions sharing a key. As written, however, the test cannot distinguish a prefix-binding implementation from a regressed body-only-MAC implementation, so it provides no regression protection for that property.

The code path. The test mints a wire value, extracts the body, manually computes v2Mac = HMAC(key, 'v2.' + body), and asserts that codec.verify(\v1.${body}.${b64url(v2Mac)}`, ...)rejects with'mac'`.

Step-by-step proof that the assertion is non-discriminating:

  1. Correct (current) implementation: verify recomputes HMAC(key, 'v1.' + body) (requestStateCodec.ts, subtle.verify('HMAC', ..., utf8.encode(PREFIX + body))). That differs from the presented HMAC(key, 'v2.' + body)subtle.verify returns false → throws 'mac'. Test passes. ✓
  2. Hypothetical regression where both mint and verify drop the prefix and MAC only the bare body: verify recomputes HMAC(key, body). That also differs from the presented HMAC(key, 'v2.' + body) → still false → still throws 'mac'. Test still passes, even though the domain-separation property it is named after is gone.

In other words the assertion is satisfied by any wrong MAC — which the sibling 'rejects a tampered MAC' test already exercises — so this test is functionally redundant with it.

Why nothing else catches it. No other test presents a MAC computed without the prefix, and the prefix-binding behavior is otherwise only documented in comments; a future refactor that accidentally moved the MAC input to the bare body would sail through the suite green.

Impact. Test-effectiveness only — the shipped codec is correct (both mint and verify use utf8.encode(PREFIX + body)), so there is no runtime or security bug. The cost is false confidence: the test named after the prefix-binding guarantee does not actually guard it.

How to fix. Make the manual MAC computation the genuinely discriminating case — sign the bare body (no prefix) and present it under v1. (or add this case alongside the existing one):

const bareMac = new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(body)));
await expect(codec.verify(`v1.${body}.${b64url(bareMac)}`, fakeCtx('tools/call'))).rejects.toThrow('mac');

The prefix-binding implementation rejects this ('mac'); a body-only-MAC regression would accept it (the MAC matches, exp is valid, no bind tag) and the test would fail — exactly the regression the test exists to catch.

Comment thread packages/server/src/server/requestStateCodec.ts
…he multi-round-trip requestState

`createRequestStateCodec({ key, ttlSeconds?, bind? })` returns `{ mint, verify }`:
`mint` HMAC-SHA256-seals a JSON-serializable payload (with TTL, default 600 s,
and optional context binding) into the opaque `requestState` wire string;
`verify` is the function consumers drop directly into
`ServerOptions.requestState.verify` (and call again in the handler to read the
payload back). WebCrypto-based and runtime-neutral; verification is fail-closed
and constant-time (`subtle.verify`). Thrown reasons are fixed opaque codes
(`malformed`/`mac`/`expired`/`bind`) — never decoded payload or binding values
— so the seam's `onerror` relay cannot leak principal identifiers.

The `ServerOptions.requestState.verify` return type is widened to
`unknown | Promise<unknown>` (the seam already awaited-and-discarded) so the
codec's payload-returning `verify` is directly assignable.

The hand-rolled HMAC in `examples/server/src/multiRoundTrip.ts` and the
conformance fixture switch to the helper (the
`input-required-result-tampered-state` scenario is now its conformance-side
proof).
…d7ad

Vendors a tarball built from modelcontextprotocol/conformance@main
(d70d7ad, post-0.2.0-alpha.4) and points the package.json devDependency
at it via a file: spec, so #347 (client-scenario mocks serve
server/discover) is available without waiting for the next published
release. README documents the pin and how to revert to a published
version.

Baseline read against d6008bcd7 (no fixture changes in this commit):

client:all  345p / 14f / 1w
  - stale baseline: auth/scope-step-up now passes (#337 gates the
    SEP-2350 scope-union WARNING to 2026-07-28+)
  - new unexpected: request-metadata (2f) — mock now rejects with
    -32020/-32022 (renumbered per spec#2907 in #353); SDK still emits
    and recognises -32001/-32004
  - sep-2322-client-request-state, http-custom-headers,
    http-invalid-tool-headers all pass with the withLocalDiscoverResponse
    shim still in place; #347 makes those mocks serve server/discover
    natively, so the shim is now redundant (removal is a separate change)

server:draft  68p / 4f
  - server-stateless 23p/1f, http-custom-header-server-validation 6p/3f,
    http-header-validation flagged — all #353 error-code renumbering
    (referee now asserts -32020/-32021/-32022; SDK emits -32001/-32004)
  - new on main: SEP-2663 tasks lifecycle scenario (#262), pending-list
    only — not in the draft suite
  - spec-types/draft.ts re-synced (#348)
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from 7327fb9 to bb7a7ca Compare June 22, 2026 20:12
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from 47f361b to 096183a Compare June 22, 2026 20:12
…ey snapshot, prefix bound into MAC, constant-time bind compare, fail-closed on unconfigured bind; align {fetch,close,notify,bus} doc-shape; reconcile conformance expected-failures to local main pin
@felixweinberger felixweinberger force-pushed the fweinberger/on-codec-conformance branch from 096183a to 75506c6 Compare June 22, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant