Summary
The published type declarations use extensionless relative import specifiers (e.g. import { ProtocolWithEvents } from "./events"; in dist/src/app-bridge.d.ts). Under TypeScript's Node16/NodeNext module resolution, relative specifiers must carry an explicit extension, so these imports do not resolve. As a result, any type that transitively depends on one loses its members. Most visibly, the recommended bridge.addEventListener("sandboxready", …) API (which AppBridge/App inherit from ProtocolWithEvents via ./events) is invisible to consumers, producing TS2339.
This is types-only — runtime JavaScript is unaffected (the .js bundles are produced by Bun.build, which inlines relative modules, so no extensionless relative import survives at runtime).
Environment
@modelcontextprotocol/ext-apps 1.7.4 (current latest); the same specifiers are present on main.
- TypeScript with
"module" / "moduleResolution" = NodeNext (or Node16).
Reproduction
tsconfig.json:
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["index.ts"]
}
index.ts:
import { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge";
declare const bridge: AppBridge;
bridge.addEventListener("sandboxready", () => {}); // recommended API
bridge.onsandboxready = () => {}; // declared directly on AppBridge
Run tsc --noEmit.
Actual
index.ts:6:8 - error TS2339: Property 'addEventListener' does not exist on type 'AppBridge'.
onsandboxready (declared directly on AppBridge) compiles fine — only members inherited through the unresolved ./events import disappear. skipLibCheck suppresses the error inside the .d.ts itself, but not the TS2339 it causes in consumer code.
Expected
addEventListener / removeEventListener and the other AppBridgeEventMap events resolve for NodeNext/Node16 consumers, matching the documentation (the onsandboxready / onsizechange / … setters are @deprecated in favor of addEventListener(...)).
Root cause
tsconfig.json uses "moduleResolution": "bundler", which permits extensionless relative specifiers, and the src files write relative imports without extensions (e.g. src/app-bridge.ts → import { ProtocolWithEvents } from "./events";). tsc (emitDeclarationOnly) copies specifiers verbatim into the emitted .d.ts, so the extensionless form ships. Because the project's own type-check runs under bundler resolution, the failure is never observed in-repo; it only surfaces for downstream Node16/NodeNext consumers. External SDK imports already use explicit .js (e.g. @modelcontextprotocol/sdk/shared/protocol.js) — only the relative imports are inconsistent. Affected export subpaths include ., ./app-bridge, ./react, and ./server.
Suggested fix
Add explicit .js extensions to relative imports across src (e.g. from "./events.js", from "../app.js"). This is standard ESM/NodeNext hygiene, matches the existing SDK-import style, and is compatible with both the bundler-mode type-check and the Bun.build bundling (both resolve ./x.js → ./x.ts). Happy to open a PR.
Summary
The published type declarations use extensionless relative import specifiers (e.g.
import { ProtocolWithEvents } from "./events";indist/src/app-bridge.d.ts). Under TypeScript'sNode16/NodeNextmodule resolution, relative specifiers must carry an explicit extension, so these imports do not resolve. As a result, any type that transitively depends on one loses its members. Most visibly, the recommendedbridge.addEventListener("sandboxready", …)API (whichAppBridge/Appinherit fromProtocolWithEventsvia./events) is invisible to consumers, producingTS2339.This is types-only — runtime JavaScript is unaffected (the
.jsbundles are produced byBun.build, which inlines relative modules, so no extensionless relative import survives at runtime).Environment
@modelcontextprotocol/ext-apps1.7.4 (currentlatest); the same specifiers are present onmain."module"/"moduleResolution"=NodeNext(orNode16).Reproduction
tsconfig.json:{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noEmit": true, "skipLibCheck": true }, "include": ["index.ts"] }index.ts:Run
tsc --noEmit.Actual
onsandboxready(declared directly onAppBridge) compiles fine — only members inherited through the unresolved./eventsimport disappear.skipLibChecksuppresses the error inside the.d.tsitself, but not theTS2339it causes in consumer code.Expected
addEventListener/removeEventListenerand the otherAppBridgeEventMapevents resolve forNodeNext/Node16consumers, matching the documentation (theonsandboxready/onsizechange/ … setters are@deprecatedin favor ofaddEventListener(...)).Root cause
tsconfig.jsonuses"moduleResolution": "bundler", which permits extensionless relative specifiers, and thesrcfiles write relative imports without extensions (e.g.src/app-bridge.ts→import { ProtocolWithEvents } from "./events";).tsc(emitDeclarationOnly) copies specifiers verbatim into the emitted.d.ts, so the extensionless form ships. Because the project's own type-check runs underbundlerresolution, the failure is never observed in-repo; it only surfaces for downstreamNode16/NodeNextconsumers. External SDK imports already use explicit.js(e.g.@modelcontextprotocol/sdk/shared/protocol.js) — only the relative imports are inconsistent. Affected export subpaths include.,./app-bridge,./react, and./server.Suggested fix
Add explicit
.jsextensions to relative imports acrosssrc(e.g.from "./events.js",from "../app.js"). This is standard ESM/NodeNext hygiene, matches the existing SDK-import style, and is compatible with both thebundler-mode type-check and theBun.buildbundling (both resolve./x.js→./x.ts). Happy to open a PR.