From 25800da676fa8cd986faad3f3786d88919b6380a Mon Sep 17 00:00:00 2001 From: rechedev9 Date: Thu, 26 Mar 2026 17:18:47 +0100 Subject: [PATCH 1/2] test(core): enforce transport exact optional compatibility --- ...transport-exact-optional-property-types.md | 2 +- packages/core/package.json | 2 +- packages/core/src/shared/transport.ts | 10 +++-- .../core/test/shared/transport.types.test.ts | 37 +++++++++++++++++++ packages/core/tsconfig.exact-optional.json | 7 ++++ 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/core/test/shared/transport.types.test.ts create mode 100644 packages/core/tsconfig.exact-optional.json diff --git a/.changeset/fix-transport-exact-optional-property-types.md b/.changeset/fix-transport-exact-optional-property-types.md index c3187db8a..01a4ad0c9 100644 --- a/.changeset/fix-transport-exact-optional-property-types.md +++ b/.changeset/fix-transport-exact-optional-property-types.md @@ -4,6 +4,6 @@ Add explicit `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`). -This fixes TS2420 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where the emitted `.d.ts` for implementing classes included `| undefined` but the interface did not. +This fixes TS2420 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where the emitted `.d.ts` for implementing classes included `| undefined` but the interface did not. The package typecheck now also compiles a dedicated Transport compatibility test with `exactOptionalPropertyTypes: true`, so this stays enforced. Workaround for older SDK versions: enable `skipLibCheck: true` in your tsconfig. diff --git a/packages/core/package.json b/packages/core/package.json index 6f0c703a5..f96505e1b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ } }, "scripts": { - "typecheck": "tsgo -p tsconfig.json --noEmit", + "typecheck": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.exact-optional.json --noEmit", "lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .", "lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .", "check": "pnpm run typecheck && pnpm run lint", diff --git a/packages/core/src/shared/transport.ts b/packages/core/src/shared/transport.ts index 6a78dae80..04fa0c572 100644 --- a/packages/core/src/shared/transport.ts +++ b/packages/core/src/shared/transport.ts @@ -37,10 +37,14 @@ export function createFetchWithInit(baseFetch: FetchLike = fetch, baseInit?: Req return async (url: string | URL, init?: RequestInit): Promise => { const mergedInit: RequestInit = { ...baseInit, - ...init, - // Headers need special handling - merge instead of replace - headers: init?.headers ? { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) } : baseInit.headers + ...init }; + + // Headers need special handling - merge instead of replace + if (init?.headers) { + mergedInit.headers = { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) }; + } + return baseFetch(url, mergedInit); }; } diff --git a/packages/core/test/shared/transport.types.test.ts b/packages/core/test/shared/transport.types.test.ts new file mode 100644 index 000000000..010f45ea1 --- /dev/null +++ b/packages/core/test/shared/transport.types.test.ts @@ -0,0 +1,37 @@ +/** + * Compile-time type checks for the Transport interface. + * + * Verifies that a class declaring optional Transport properties as `T | undefined` + * (the pattern required by `exactOptionalPropertyTypes: true`) is assignable to + * Transport without TS2420 errors when compiled with the dedicated + * exact-optional typecheck config. + * + * See: https://github.com/modelcontextprotocol/typescript-sdk/issues/1314 + */ +import { test } from 'vitest'; + +import type { Transport } from '../../src/shared/transport.js'; +import type { JSONRPCMessage, MessageExtraInfo } from '../../src/types/index.js'; + +// A concrete class that uses the explicit `| undefined` union form for optional Transport members. +// With the old Transport interface (no `| undefined` on these members), this class would produce +// TS2420 under `exactOptionalPropertyTypes: true` when compiled by tsconfig.exact-optional.json. +class ExplicitUndefinedTransport implements Transport { + sessionId?: string | undefined; + onclose?: (() => void) | undefined; + onerror?: ((error: Error) => void) | undefined; + onmessage?: ((message: T, extra?: MessageExtraInfo) => void) | undefined; + setProtocolVersion?: ((version: string) => void) | undefined; + setSupportedProtocolVersions?: ((versions: string[]) => void) | undefined; + + async start(): Promise {} + async close(): Promise {} + async send(_message: JSONRPCMessage): Promise {} +} + +test('Transport allows explicit | undefined on optional members', () => { + const transport: Transport = new ExplicitUndefinedTransport(); + // The mere fact this file compiles is the assertion. + // We also verify runtime assignability here. + expect(transport).toBeDefined(); +}); diff --git a/packages/core/tsconfig.exact-optional.json b/packages/core/tsconfig.exact-optional.json new file mode 100644 index 000000000..722ec271b --- /dev/null +++ b/packages/core/tsconfig.exact-optional.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/shared/transport.ts", "src/types/index.ts", "test/shared/transport.types.test.ts"], + "compilerOptions": { + "exactOptionalPropertyTypes": true + } +} From b6cb43614b8039b3f89d3b5c388c02be5ea31397 Mon Sep 17 00:00:00 2001 From: rechedev9 Date: Thu, 26 Mar 2026 17:46:12 +0100 Subject: [PATCH 2/2] ci(publish): skip preview publish on forks --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a180396b6..c3b6e20cd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -38,6 +38,7 @@ jobs: run: pnpm run build:all - name: Publish preview packages + if: github.repository == 'modelcontextprotocol/typescript-sdk' run: pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/server' './packages/client' './packages/middleware/express' './packages/middleware/hono' './packages/middleware/node'