Skip to content

SEP-2792: Reference implementation for per-request language negotiation#2158

Draft
SamMorrowDrums wants to merge 2 commits into
modelcontextprotocol:mainfrom
SamMorrowDrums:sammorrowdrums/reimagined-enigma
Draft

SEP-2792: Reference implementation for per-request language negotiation#2158
SamMorrowDrums wants to merge 2 commits into
modelcontextprotocol:mainfrom
SamMorrowDrums:sammorrowdrums/reimagined-enigma

Conversation

@SamMorrowDrums
Copy link
Copy Markdown

@SamMorrowDrums SamMorrowDrums commented May 26, 2026

SEP-2792: Reference Implementation

This PR implements the reference implementation for SEP-2792: Internationalization via Per-Request Language Negotiation.

Note: This is a reference implementation intended to demonstrate the SEP's feasibility. It is not intended to merge until SEP-2792 reaches Accepted status.

What shipped in the SDK

Core i18n helpers (packages/core/src/shared/i18n.ts):

  • Constants: ACCEPT_LANGUAGE_META / CONTENT_LANGUAGE_META (using io.modelcontextprotocol/ vendor prefix)
  • Getter/setter helpers: getAcceptLanguage, setAcceptLanguage, getContentLanguage, setContentLanguage
  • negotiateLanguage(acceptLanguage, available, defaultLocale?) — thin wrapper around @formatjs/intl-localematcher for RFC 4647 matching

Client Streamable HTTP transport (packages/client/src/client/streamableHttp.ts):

  • Mirrors _meta[acceptLanguage]Accept-Language HTTP header on outbound requests
  • Throws on mismatch if header is manually set to a conflicting value (SEP-2243 strict-mismatch pattern)

Server Streamable HTTP transport (packages/server/src/server/streamableHttp.ts):

  • Validates Accept-Language header vs _meta on inbound POST (returns HTTP 400 on mismatch)
  • Copies header → _meta when only header is present (transparent to handler code)
  • Mirrors _meta[contentLanguage]Content-Language response header

Example server & client

  • examples/server/src/i18nExample.ts: A get_greeting tool localized in en/fr/de, supporting both stdio and HTTP
  • examples/client/src/i18nClient.ts: Exercises three scenarios — explicit English, French-Canadian with quality-value fallback, and Japanese (forces fallback to English)

Sample client output (stdio transport):

=== SEP-2792 i18n Client Demo ===

--- Accept-Language: "en" ---
  tools/list → title: "Get Greeting", description: "Returns a greeting in the negotiated language"
              contentLanguage: "en"
  tools/call → text: "Hello, World! Welcome."
              contentLanguage: "en"

--- Accept-Language: "fr-CA,fr;q=0.9,en;q=0.5" ---
  tools/list → title: "Obtenir un salut", description: "Retourne un salut dans la langue négociée"
              contentLanguage: "fr"
  tools/call → text: "Bonjour, World ! Bienvenue."
              contentLanguage: "fr"

--- Accept-Language: "ja" ---
  tools/list → title: "Get Greeting", description: "Returns a greeting in the negotiated language"
              contentLanguage: "en"
  tools/call → text: "Hello, World! Welcome."
              contentLanguage: "en"

AI Disclosure

This PR was authored with assistance from GitHub Copilot (claude-opus-4.6).


Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…tiation

Implements internationalization via per-request language negotiation as
specified in SEP-2792. Changes include:

SDK changes:
- Add i18n helper module (packages/core/src/shared/i18n.ts) with:
  - ACCEPT_LANGUAGE_META / CONTENT_LANGUAGE_META constants
  - getAcceptLanguage / setAcceptLanguage helpers for request _meta
  - getContentLanguage / setContentLanguage helpers for response _meta
  - negotiateLanguage() using @formatjs/intl-localematcher (RFC 4647)
- Client Streamable HTTP transport: mirrors _meta acceptLanguage to
  Accept-Language header; throws on header/body mismatch
- Server Streamable HTTP transport: validates Accept-Language header vs
  _meta (400 on mismatch), copies header→_meta when only header present,
  mirrors Content-Language header from response _meta on JSON responses

Examples:
- examples/server/src/i18nExample.ts: server with get_greeting tool
  supporting en/fr/de via stdio and HTTP transports
- examples/client/src/i18nClient.ts: client demonstrating three language
  scenarios (exact, fallback chain, no-match fallback)

Tests:
- Unit tests for all helpers and negotiateLanguage (quality values, subtag
  matching, fallback behavior)
- HTTP integration tests: header mirroring, Content-Language on response,
  400 on mismatch, agreement pass-through
- stdio integration test: mid-session language switching on same connection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

⚠️ No Changeset found

Latest commit: 5a5f548

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 26, 2026

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/codemod

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 5a5f548

SamMorrowDrums added a commit to SamMorrowDrums/modelcontextprotocol that referenced this pull request May 27, 2026
Adds a transport-agnostic, fully opt-in i18n mechanism for MCP using
_meta['io.modelcontextprotocol/acceptLanguage'] on requests and
_meta['io.modelcontextprotocol/contentLanguage'] on responses, mirrored
into the standard HTTP Accept-Language / Content-Language headers on the
Streamable HTTP transport with a strict-mismatch rule consistent with
SEP-2243.

Per-request scope (no handshake-bound state) aligns with SEP-2575 and
supports mid-conversation language switching. Reuses BCP 47, RFC 4647
language-range matching, and existing ecosystem libraries verbatim,
no bespoke matcher or schema.

Supersedes modelcontextprotocol#2355. Proposes subsuming the locale aspect of SEP-1809.

Reference implementation:
modelcontextprotocol/typescript-sdk#2158 (en/fr/de server + client,
stdio and Streamable HTTP, unit and integration tests including
mid-session language switch on stdio).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Use -32001 HeaderMismatch error code (SEP-2243) for language
   header/body mismatch on both client and server sides.
2. Add error-response localization: getErrorContentLanguage/
   setErrorContentLanguage helpers for error.data._meta, mirror
   Content-Language header from error responses, demonstrate in
   example server (empty name triggers localized error).
3. Remove batch handling from client Accept-Language extraction
   (MCP no longer permits JSON-RPC batches over Streamable HTTP).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
Copy link
Copy Markdown
Author

Update: Review feedback addressed (commit 5a5f548)

Following @pja-ant's review on the SEP (modelcontextprotocol/modelcontextprotocol#2792), three changes applied per SEP commit b5667477:

1. -32001 HeaderMismatch error code

  • Defined HEADER_MISMATCH_ERROR_CODE = -32001 constant in packages/core/src/shared/i18n.ts with JSDoc pointing at SEP-2243
  • Server: mismatch response now uses -32001 (was -32000)
  • Client: mismatch error message references HeaderMismatch code (still uses SdkErrorCode.SendFailed for the throw since SdkErrorCode has no -32001 variant; message is clear)
  • Tests assert on -32001

2. Error-response localization

  • Added getErrorContentLanguage(errorData) and setErrorContentLanguage(data, lang) helpers for error.data._meta
  • Server transport _extractContentLanguage() now checks both JSONRPCResultResponse and JSONRPCErrorResponse for Content-Language mirroring
  • Example server: empty name input now throws a localized ProtocolError with error.data._meta[contentLanguage]
  • Example client: demonstrates catching and reading the localized error
  • New unit tests (6) and integration test (1) for the error path

3. No batching

  • Client _extractAcceptLanguage() simplified: no longer unions across batch items. Takes first message only (array param kept for type compatibility with existing _send signature, but no batch-specific logic).

Test counts

  • Core: 582 passed (+10 new)
  • Server: 71 passed (+1 new)
  • Client: 364 passed (unchanged)
  • Build: ✅
  • Lint: ✅ (all modified packages)

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