Summary
The four v2 SDK packages — @modelcontextprotocol/client, core, server, server-legacy — are declared both in the repo-root package.json and again in clients/web, clients/cli, and (two of them) clients/tui. Each declaration installs its own copy into that client's node_modules. They should be declared once, at the root, and resolved from there by every client.
Why it matters
v2 is deliberately not an npm workspace, so each client has its own node_modules — but Node resolution walks up, and the root install is on every client's chain. The root manifest is already the source of truth for what ships: per the README, "Runtime dependencies are declared on the root package.json; client builds bundle @inspector/core and externalize npm packages resolved from the root install." The per-client copies are therefore duplicates of a decision made at the root, and duplicates can drift.
That drift is not hypothetical here. The same pins in five places have to be bumped in lockstep or a client silently builds against a different SDK copy than the one the tarball will resolve — and a second copy of @modelcontextprotocol/client/core is exactly the failure the vitest config already carries a dedupe + server.deps.inline workaround for (a vi.mock that misses because the mock and the import resolved to different copies).
yaml in vitest.shared.mts is the existing precedent: it is aliased to the repo-root node_modules while every other pinned package points at the client's own.
Scope
Remove the four packages from clients/web, clients/cli, and clients/tui package.json, leaving the root declaration as the single source. clients/launcher declares none of them today and stays as-is.
@modelcontextprotocol/ext-apps rides along
ext-apps is declared twice (root + clients/web) and is the only reason the v1 SDK is installed at all: it declares @modelcontextprotocol/sdk: ^1.29.0 as a peer, which npm auto-installs. Nothing in this repo imports @modelcontextprotocol/sdk — it appears in no package.json of ours, only as "peer": true in the lock files — so there is nothing to "remove", but there are currently two copies at two versions: 1.29.0 at the root and 1.30.0 under clients/web. Consolidating ext-apps to the root collapses that to one copy at one version, which is the only lever we have over a transitive peer.
Acceptance
- The four packages (plus
ext-apps) appear in exactly one package.json: the root.
npm install at the root still produces a working tree for every client; npm run ci passes.
- No second copy of the v1
@modelcontextprotocol/sdk in any client node_modules after a clean install.
Summary
The four v2 SDK packages —
@modelcontextprotocol/client,core,server,server-legacy— are declared both in the repo-rootpackage.jsonand again inclients/web,clients/cli, and (two of them)clients/tui. Each declaration installs its own copy into that client'snode_modules. They should be declared once, at the root, and resolved from there by every client.Why it matters
v2 is deliberately not an npm workspace, so each client has its own
node_modules— but Node resolution walks up, and the root install is on every client's chain. The root manifest is already the source of truth for what ships: per the README, "Runtime dependencies are declared on the rootpackage.json; client builds bundle@inspector/coreand externalize npm packages resolved from the root install." The per-client copies are therefore duplicates of a decision made at the root, and duplicates can drift.That drift is not hypothetical here. The same pins in five places have to be bumped in lockstep or a client silently builds against a different SDK copy than the one the tarball will resolve — and a second copy of
@modelcontextprotocol/client/coreis exactly the failure the vitest config already carries adedupe+server.deps.inlineworkaround for (avi.mockthat misses because the mock and the import resolved to different copies).yamlinvitest.shared.mtsis the existing precedent: it is aliased to the repo-rootnode_moduleswhile every other pinned package points at the client's own.Scope
Remove the four packages from
clients/web,clients/cli, andclients/tuipackage.json, leaving the root declaration as the single source.clients/launcherdeclares none of them today and stays as-is.@modelcontextprotocol/ext-appsrides alongext-appsis declared twice (root +clients/web) and is the only reason the v1 SDK is installed at all: it declares@modelcontextprotocol/sdk: ^1.29.0as a peer, which npm auto-installs. Nothing in this repo imports@modelcontextprotocol/sdk— it appears in nopackage.jsonof ours, only as"peer": truein the lock files — so there is nothing to "remove", but there are currently two copies at two versions:1.29.0at the root and1.30.0underclients/web. Consolidatingext-appsto the root collapses that to one copy at one version, which is the only lever we have over a transitive peer.Acceptance
ext-apps) appear in exactly onepackage.json: the root.npm installat the root still produces a working tree for every client;npm run cipasses.@modelcontextprotocol/sdkin any clientnode_modulesafter a clean install.