Skip to content

Commit 25800da

Browse files
committed
test(core): enforce transport exact optional compatibility
1 parent 1497412 commit 25800da

5 files changed

Lines changed: 53 additions & 5 deletions

File tree

.changeset/fix-transport-exact-optional-property-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
Add explicit `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`).
66

7-
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.
7+
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.
88

99
Workaround for older SDK versions: enable `skipLibCheck: true` in your tsconfig.

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
}
3636
},
3737
"scripts": {
38-
"typecheck": "tsgo -p tsconfig.json --noEmit",
38+
"typecheck": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.exact-optional.json --noEmit",
3939
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
4040
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
4141
"check": "pnpm run typecheck && pnpm run lint",

packages/core/src/shared/transport.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ export function createFetchWithInit(baseFetch: FetchLike = fetch, baseInit?: Req
3737
return async (url: string | URL, init?: RequestInit): Promise<Response> => {
3838
const mergedInit: RequestInit = {
3939
...baseInit,
40-
...init,
41-
// Headers need special handling - merge instead of replace
42-
headers: init?.headers ? { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) } : baseInit.headers
40+
...init
4341
};
42+
43+
// Headers need special handling - merge instead of replace
44+
if (init?.headers) {
45+
mergedInit.headers = { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) };
46+
}
47+
4448
return baseFetch(url, mergedInit);
4549
};
4650
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Compile-time type checks for the Transport interface.
3+
*
4+
* Verifies that a class declaring optional Transport properties as `T | undefined`
5+
* (the pattern required by `exactOptionalPropertyTypes: true`) is assignable to
6+
* Transport without TS2420 errors when compiled with the dedicated
7+
* exact-optional typecheck config.
8+
*
9+
* See: https://github.com/modelcontextprotocol/typescript-sdk/issues/1314
10+
*/
11+
import { test } from 'vitest';
12+
13+
import type { Transport } from '../../src/shared/transport.js';
14+
import type { JSONRPCMessage, MessageExtraInfo } from '../../src/types/index.js';
15+
16+
// A concrete class that uses the explicit `| undefined` union form for optional Transport members.
17+
// With the old Transport interface (no `| undefined` on these members), this class would produce
18+
// TS2420 under `exactOptionalPropertyTypes: true` when compiled by tsconfig.exact-optional.json.
19+
class ExplicitUndefinedTransport implements Transport {
20+
sessionId?: string | undefined;
21+
onclose?: (() => void) | undefined;
22+
onerror?: ((error: Error) => void) | undefined;
23+
onmessage?: (<T extends JSONRPCMessage>(message: T, extra?: MessageExtraInfo) => void) | undefined;
24+
setProtocolVersion?: ((version: string) => void) | undefined;
25+
setSupportedProtocolVersions?: ((versions: string[]) => void) | undefined;
26+
27+
async start(): Promise<void> {}
28+
async close(): Promise<void> {}
29+
async send(_message: JSONRPCMessage): Promise<void> {}
30+
}
31+
32+
test('Transport allows explicit | undefined on optional members', () => {
33+
const transport: Transport = new ExplicitUndefinedTransport();
34+
// The mere fact this file compiles is the assertion.
35+
// We also verify runtime assignability here.
36+
expect(transport).toBeDefined();
37+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"include": ["src/shared/transport.ts", "src/types/index.ts", "test/shared/transport.types.test.ts"],
4+
"compilerOptions": {
5+
"exactOptionalPropertyTypes": true
6+
}
7+
}

0 commit comments

Comments
 (0)