modelcontextprotocol / modelcontextprotocol/typescript-sdk

normalizeHeaders references the bare global `HeadersInit`, breaking Node-only consumers with skipLibCheck: false

Open Beginner friendly
#2,568 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P2 ready for work v1
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Summary

shared/transport.d.ts references the bare global HeadersInit, which is a DOM lib type. Node-only consumers compiling with "skipLibCheck": false and no "DOM" in lib fail to build.

File/line: dist/esm/shared/transport.d.ts:7

export declare function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string>;
Reproduction

@modelcontextprotocol/sdk@1.30.0 + @types/node@22.20.1, with "lib": ["ES2023"], "types": ["node"], "skipLibCheck": false:

node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts(7,51):
  error TS2304: Cannot find name 'HeadersInit'.

Affects every published 1.x from 1.23.0-beta.0 through 1.30.0 (current latest).

Why @types/node cannot fix this

@types/node@22's fetch-globals shim (node_modules/@types/node/web-globals/fetch.d.ts) binds Headers, Request, RequestInit, Response and ResponseInit globally from undici-types — but deliberately not HeadersInit.

Per the review thread on DefinitelyTyped PR #74414 (@Renegade334): HeadersInit is a web-IDL typedef, which maps to a TypeScript type alias, and type aliases cannot be declaration-merged — unlike the dictionary/interface-shaped Headers/Request/RequestInit/Response, which can. So @types/node structurally cannot bind this global the way it binds the others.

That leaves the fix on the SDK side: don't reference the bare global.

Suggested fix

Change the signature to avoid the DOM-only global:

export declare function normalizeHeaders(headers: RequestInit['headers'] | undefined): Record<string, string>;

This is same-behaviour and depends only on a global @types/node does bind. It's also exactly what the v2 rewrite already does (#1279).

Blast radius is small: src/shared/transport.ts and its compiled output — 2 files.

Why a 1.x backport matters

The v2 rewrite already fixes this, but v2 is outside the peer range that @anthropic-ai/claude-agent-sdk pins (^1.29.0), so consumers coming through the Agent SDK can't upgrade to it today. Without a 1.x backport, the only options are a local ambient shim, adding "DOM" to lib (which drags every browser global into a Node service), or skipLibCheck: true (which stops type-checking all declaration files).

Happy to open a PR for the one-line change if that's useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Inspect normalizeHeaders in src/shared/transport.ts and its compiled declaration at dist/esm/shared/transport.d.ts. Verify the Node-only reproduction with skipLibCheck false and no DOM lib, then confirm the published declaration no longer requires the unresolved global HeadersInit.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend-api-design
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.