Skip to content

fix(core): add | undefined to Transport optional callbacks for exactOptionalPropertyTypes compatibility#1661

Closed
rechedev9 wants to merge 3 commits intomodelcontextprotocol:mainfrom
rechedev9:fix/exact-optional-property-types-1314
Closed

fix(core): add | undefined to Transport optional callbacks for exactOptionalPropertyTypes compatibility#1661
rechedev9 wants to merge 3 commits intomodelcontextprotocol:mainfrom
rechedev9:fix/exact-optional-property-types-1314

Conversation

@rechedev9
Copy link

Summary

Fixes TS2420 errors when implementing the Transport interface in projects with exactOptionalPropertyTypes: true enabled.

The five optional callback/method properties on Transport previously lacked explicit | undefined:

// Before
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: <T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void;
setProtocolVersion?: (version: string) => void;
setSupportedProtocolVersions?: (versions: string[]) => void;

With exactOptionalPropertyTypes: true, TypeScript treats T | undefined and T as distinct in optional property contexts. A concrete class that declares onclose?: (() => void) | undefined is not assignable to an interface that declares onclose?: () => void under this flag, producing TS2420.

The fix adds explicit | undefined to all five optional members:

// After
onclose?: (() => void) | undefined;
onerror?: ((error: Error) => void) | undefined;
onmessage?: (<T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void) | undefined;
setProtocolVersion?: ((version: string) => void) | undefined;
setSupportedProtocolVersions?: ((versions: string[]) => void) | undefined;

This is backwards-compatible: projects not using exactOptionalPropertyTypes are unaffected.

Breaking Changes

None.

Test plan

  • Added packages/core/test/shared/transport.types.test.ts — compile-time type test verifying a class with explicit | undefined callbacks implements Transport without errors
  • pnpm typecheck:all passes clean across all packages
  • All 441 core tests pass

Fixes #1314

…xactOptionalPropertyTypes compatibility

Resolves TS2420 errors when users implement the Transport interface in
projects with `exactOptionalPropertyTypes: true` enabled. Optional
callback properties (onclose, onerror, onmessage, setProtocolVersion,
setSupportedProtocolVersions) now include explicit `| undefined` in
their types, satisfying the stricter assignability rules.

Fixes modelcontextprotocol#1314
@rechedev9 rechedev9 requested a review from a team as a code owner March 11, 2026 18:19
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 11, 2026

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 5275ece

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 5275ece

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

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/core Patch

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

@felixweinberger
Copy link
Contributor

@claude review

Comment on lines 118 to 120

/**
* The session ID generated for this connection.
Copy link

Choose a reason for hiding this comment

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

🟡 The sessionId property at line 122 is missing | undefined, making the fix incomplete. All five callback properties got | undefined added, but sessionId?: string was left unchanged. The test class itself declares sessionId?: string | undefined (line 19), which would fail TS2420 against the current interface under exactOptionalPropertyTypes: true.

Extended reasoning...

What the bug is

This PR adds | undefined to the five optional callback/method properties on the Transport interface to fix exactOptionalPropertyTypes compatibility. However, the sessionId property at line 122 was not updated:

sessionId?: string;  // should be: sessionId?: string | undefined;

Why this matters

Under exactOptionalPropertyTypes: true, TypeScript treats T | undefined and T as distinct types in optional property contexts. A class that declares sessionId?: string | undefined is not assignable to an interface that declares sessionId?: string. This produces the same TS2420 error that this PR is trying to fix.

Proof via the test file

The PR's own test class at transport.types.test.ts:19 declares:

class ExplicitUndefinedTransport implements Transport {
    sessionId?: string | undefined;  // line 19
    ...
}

This class compiles today only because the project does not have exactOptionalPropertyTypes enabled. Under that flag, the test would fail with TS2420 on sessionId specifically, since the interface still has sessionId?: string without | undefined.

Step-by-step example

  1. User enables exactOptionalPropertyTypes: true in their tsconfig.json
  2. User writes a class implementing Transport with sessionId?: string | undefined (a natural pattern, and the exact pattern shown in the test)
  3. TypeScript sees that the interface declares sessionId?: string (no | undefined)
  4. Under exactOptionalPropertyTypes, string | undefined is not assignable to string in optional property position
  5. TS2420 error: "Class incorrectly implements interface Transport"

Fix

Change line 122 of packages/core/src/shared/transport.ts from:

sessionId?: string;

to:

sessionId?: string | undefined;

This is consistent with the treatment applied to all other optional properties in this PR and matches the pattern already used in the test class.

Comment on lines +15 to +36
// A concrete class that uses the explicit `| undefined` union form for optional callbacks.
// With the old Transport interface (no `| undefined` on callbacks), this class would produce
// TS2420 under `exactOptionalPropertyTypes: true`.
class ExplicitUndefinedTransport implements Transport {
sessionId?: string | undefined;
onclose?: (() => void) | undefined;
onerror?: ((error: Error) => void) | undefined;
onmessage?: (<T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void) | undefined;
setProtocolVersion?: ((version: string) => void) | undefined;
setSupportedProtocolVersions?: ((versions: string[]) => void) | undefined;

async start(): Promise<void> {}
async close(): Promise<void> {}
async send(_message: JSONRPCMessage): Promise<void> {}
}

test('Transport allows explicit | undefined on optional callback properties', () => {
const transport: Transport = new ExplicitUndefinedTransport();
// The mere fact this file compiles is the assertion.
// We also verify runtime assignability here.
expect(transport).toBeDefined();
});
Copy link

Choose a reason for hiding this comment

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

🟡 This type test does not actually verify exactOptionalPropertyTypes compatibility because the project tsconfig does not enable that flag (it is not part of strict mode). Without exactOptionalPropertyTypes: true in the compilation, TypeScript treats prop?: T and prop?: T | undefined identically, so this test passes with or without the PR changes. Consider adding a dedicated tsconfig with exactOptionalPropertyTypes: true for this test file, or using @ts-expect-error annotations to assert the old signatures would fail.

Extended reasoning...

The issue

The test file transport.types.test.ts is intended to verify that the updated Transport interface works correctly under exactOptionalPropertyTypes: true. The comment at lines 16-17 explicitly states: "With the old Transport interface (no | undefined on callbacks), this class would produce TS2420 under exactOptionalPropertyTypes: true." However, the test is not actually compiled with that flag enabled.

Why it does not work

The project tsconfig chain is: packages/core/tsconfig.json@modelcontextprotocol/tsconfig (at common/tsconfig/tsconfig.json). The base config sets "strict": true but does not set "exactOptionalPropertyTypes": true. This is a common misconception — exactOptionalPropertyTypes is a separate opt-in flag that is not included in strict mode.

Without exactOptionalPropertyTypes: true, TypeScript treats prop?: T and prop?: T | undefined as equivalent for assignability purposes. This means the ExplicitUndefinedTransport class would successfully implement the old Transport interface (without | undefined) just fine.

Step-by-step proof

  1. Consider the old interface: onclose?: () => void;
  2. The test class declares: onclose?: (() => void) | undefined;
  3. Without exactOptionalPropertyTypes, TypeScript internally expands onclose?: () => void to onclose?: (() => void) | undefined anyway.
  4. So the class property matches the interface property — no TS2420 error, regardless of the PR changes.
  5. If someone reverted the | undefined additions in transport.ts, this test would still pass.

Impact

The test cannot catch regressions. If a future change accidentally removes the | undefined from the Transport interface properties, this test will continue to pass, giving false confidence that the exactOptionalPropertyTypes fix is still in place.

How to fix

Either:

  • Create a separate tsconfig.exactOptional.json that extends the base config and adds "exactOptionalPropertyTypes": true, then configure this test to use it.
  • Use @ts-expect-error annotations with a negative test case that asserts the old signatures would fail.
  • Add a triple-slash directive or use tsd / dtslint for more robust type-level testing.

Note: The actual code fix in transport.ts is correct and valuable for downstream consumers who enable exactOptionalPropertyTypes. This is purely a test quality issue.

@felixweinberger
Copy link
Contributor

Thanks for the contribution! Closing in favor of #1766.

@rechedev9
Copy link
Author

Thank you Felix!

@rechedev9 rechedev9 deleted the fix/exact-optional-property-types-1314 branch March 26, 2026 16:29
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.

error TS2420: Class 'StreamableHTTPServerTransport' incorrectly implements interface 'Transport'.

2 participants