Skip to content

Deno: TS2307 on all .js subpath imports — './*' exports map types to '*.js.d.ts' #2701

Description

@johnhenry

Summary

Under Deno, every .js-suffixed subpath import of the SDK fails type resolution with TS2307, because the ./* export pattern maps types to ./dist/esm/*.d.ts. For the specifier @modelcontextprotocol/sdk/server/mcp.js, * captures server/mcp.js, so the types path resolves to dist/esm/server/mcp.js.d.ts — which does not exist. The real declaration file is dist/esm/server/mcp.d.ts.

tsc resolves this fine (it strips the .js before matching), so the problem is invisible to Node-only users. Deno applies the substitution literally and fails.

This affects the import style used throughout the SDK's own README and examples, so it hits essentially any MCP server that tries to type-check under Deno or publish to JSR.

Reproduction

Clean directory, no tsconfig:

mkdir repro && cd repro
echo '{"name":"repro","type":"module","dependencies":{"@modelcontextprotocol/sdk":"1.30.0"}}' > package.json
npm install
echo '{"nodeModulesDir":"manual"}' > deno.json
printf 'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";\nexport const s: McpServer | null = null;\n' > mod.ts

deno check mod.ts

Result:

Check mod.ts
TS2307 [ERROR]: Cannot find module '@modelcontextprotocol/sdk/server/mcp.js'.
    at file:///.../repro/mod.ts:1:27
error: Type checking failed.

The identical file under tsc 5.9.3 exits 0:

tsc --noEmit --module nodenext --moduleResolution nodenext --strict mod.ts   # exit 0

Why it's worse than one error

Once McpServer is unresolved, the zod-inferred parameter types on every server.tool(...) callback collapse to any. In a real server this cascades: a file of mine with 3 SDK imports produced 1 × TS2307 plus 19 × TS7031 ("Binding element 'sessionId' implicitly has an 'any' type"), all downstream of the single resolution failure. The error output makes it look like a codebase problem rather than a one-line packaging one.

Versions

  • @modelcontextprotocol/sdk 1.30.0
  • Deno 2.9.0 (stable, x86_64-unknown-linux-gnu)
  • TypeScript 5.9.3
  • Node 24

Suggested fix

Add a ./*.js pattern alongside the existing ./*. Node's exports resolution prefers the more specific pattern, so ordering doesn't matter and existing consumers are unaffected:

"./*.js": {
  "types": "./dist/esm/*.d.ts",
  "import": "./dist/esm/*.js",
  "require": "./dist/cjs/*.js"
},
"./*": {
  "types": "./dist/esm/*.d.ts",
  "import": "./dist/esm/*",
  "require": "./dist/cjs/*"
}

I verified this against 1.30.0 by patching node_modules:

  • deno check mod.ts → exit 0
  • node --input-type=module -e 'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; ...' → still resolves, typeof McpServer === "function"

So it fixes Deno without regressing Node.

Note on prior art

#1269 ("Non-Node environment support (Bun, Deno, React Native)") was closed as completed in March, but this type-resolution path appears not to be covered by it — runtime import works under Deno, only type-checking fails. Happy to open a PR if the suggested shape looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.x

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions