WSL job copies the checkout onto ext4 before building (9P was the cost: 605 s to 348 s); Playwright installs only the headless shell; every tsconfig lists its @types explicitly, a verified no-op on 5.9 and the prerequisite for TypeScript 7.
The Windows WSL job is the critical path of every CI run (589 to 752 s).
Its checkout sits on the Windows drive, which WSL reaches over 9P, and
writing node_modules there is what makes npm ci take about three minutes
where the Linux job takes 23 s. The step now copies the tree onto the
distro's ext4 filesystem before installing and building, as Microsoft's
WSL guidance recommends for I/O-heavy work.
The e2e job downloaded the full Chromium build (about 165 MB) on every
run, but a headless run with no channel launches chromium-headless-shell
and never opens it. `--only-shell` skips that download.
Every tsconfig now lists the @types packages it uses (node, bun,
vite/client, dom-speech-recognition) instead of relying on TypeScript
auto-including everything under node_modules/@types. On TypeScript 5.9
this is a no-op: identical diagnostics across all 48 projects and
byte-identical emitted .d.ts files. It is a prerequisite for TypeScript 7,
which drops the auto-inclusion; with these lists the Go compiler passes
all 48 projects, so that upgrade becomes a version bump once TypeDoc
supports it.
Two cache-based approaches were evaluated and rejected: an npm cache for
the WSL job (npm ci is slow there because of 9P, not downloads, and a
prefix-restored cache grows without bound) and a Playwright browser cache
(Playwright's docs advise against it; the download is under 10 s).
Guide fact-checked claim by claim against the built package: three wording fixes, seven coverage gaps (including the double-registration guard now covering the base Protocol's progress/cancelled handlers), and the legacy setRequestHandler overload's @deprecated moved onto a documented call signature so editors report it.
Guide: name @modelcontextprotocol/express in the server-author row, say
the two SDKs share no types rather than "do not interoperate" (they speak
the same wire protocol), note zod 4.0/4.1 lack ~standard.jsonSchema, list
the full 1.x-shaped extra, cover tool callbacks in the handler-context
bullet, document that the double-registration guard now covers the base
Protocol's notifications/progress and notifications/cancelled handlers,
mention ResourceNotFoundError, add the Express row to the import map, and
add a "What stays the same" section.
The @deprecated tag on the legacy setRequestHandler / setNotificationHandler
overload sat on the parameter inside an intersection type, which TypeScript
does not report. The overload now comes from LegacyRequestHandlerSetter /
LegacyNotificationHandlerSetter, named types with a documented call
signature, so editors flag the 1.x call and not the 2.x forms.
README links to the quickstart use the lowercase slug typedoc emits.
Runs the published 1.7.5 App and AppBridge against the 2.x build over an in-memory transport in both pairings, via an npm alias devDependency and sdk 1.30. Complements wire-compat.test.ts: this one runs the real 1.x code and is the only test that catches a coordinated 2.x wire rename.
setRequestHandler(Schema, handler) and setNotificationHandler(Schema, handler) work on App and AppBridge as deprecated overloads forwarding to the 2.x method-keyed form; AppRequest, AppNotification and AppResult return as deprecated type aliases. Both removed in 3.0.
Install @modelcontextprotocol/ext-apps@1.7.5 under the npm alias ext-apps-v1
(plus @modelcontextprotocol/sdk@1.30.0, its peer) as devDependencies and run
the 2.x App against the 1.x AppBridge and vice versa over an in-memory
duplex, with each bridge fronting a real Client+McpServer of its own SDK
generation. Covers initialize, notifications, view-to-host requests,
tool/resource proxying, error codes, AbortSignal cancellation, teardown and
close.
They appear in App's public setRequestHandler / setNotificationHandler
signatures, so TypeDoc's link validation needs them reachable from the
entry point.
Consumers on 1.x wrote setRequestHandler(Schema, handler) and
setNotificationHandler(Schema, handler) with a whole-message Zod schema.
SDK 2.0 keys handlers by method name, so App and AppBridge now accept
that form as a third, deprecated overload: a small helper detects the
schema, extracts the method literal, forwards to the 2.x
(method, { params }, handler) form, and hands the legacy handler the
reassembled { method, params } message plus a 1.x-shaped extra
(signal, requestId, sessionId, _meta). A one-time console warning
points at the new form. The double-registration guard applies to both
forms.
The AppRequest, AppNotification and AppResult unions that 1.x fed to
the SDK's Protocol generics come back as deprecated type aliases, since
they leaked through the root entry and cost nothing to keep.
Both go away in 3.0.
Follow-up to #720 and #705: regenerated lockfile, ^2.0.0 peers with client required, types from @modelcontextprotocol/client, restored double-registration guard, deprecated raw-shape registerAppTool overload, migration guide, wire-compat tests, MCPB build from the local package.
- pdf-server build-mcpb.mjs packs the ext-apps build from this checkout
into the staging dir instead of asking the registry for ^2.0.0, which
is not published yet; CI's "Build MCPB bundle" step failed on ETARGET.
- Claim notifications/cancelled and notifications/progress in the
MethodRegistry right after super() so a direct setNotificationHandler
cannot silently replace the base Protocol's own handlers.
- Migration guide and add-app-to-server skill: custom methods use the
three-argument setRequestHandler("m", { params }, handler) form (the
two-argument form is only for spec methods); zod schemas live in
@modelcontextprotocol/core, not client/server; it is
toolInfo.tool.outputSchema that loosened in schema.json, and the
recursive JSON-value def is nested under three definitions; core is a
required peer that client already depends on.
- app-bridge.ts: fix "forthe"/"forresult" typos in @see links, update
three JSDoc examples to the method-string setNotificationHandler form,
move the UntypedHandlerSetter type below the import block (same in
app.ts).
- wire-compat.test.ts: say the fixtures are 1.x-shaped, not captures.
Add explicit .js extensions to the relative imports in app.ts,
app-bridge.ts and their tests so the emitted declarations resolve under
NodeNext (#704). Replace the zod 3 JSON Schema fallback test, whose
code path was removed, with the remaining non-Standard-JSON-Schema
rejection case as its own test.
The base SDK Protocol silently replaces an existing request or
notification handler. v1's ProtocolWithEvents threw instead, so a stray
setRequestHandler / setNotificationHandler could not disconnect on*
handlers or addEventListener listeners. Restore that guard on both App
and AppBridge with a small MethodRegistry, keep replace semantics for the
on* setters via a protected replaceRequestHandler, and release methods
on removeRequestHandler / removeNotificationHandler.
Also:
- give oncreatesamplingmessage a getter, a replace warning and the same
clearable shape as the other request setters
- let App register handlers for custom (non-spec) methods; the default
branch of assertRequestHandlerCapability threw a misleading
"No handler for method X registered" error
- move the RESOURCE_URI_META_KEY / RESOURCE_MIME_TYPE docs to
src/constants.ts where the constants now live
- fix stale "inherited from Server" wording and point @see links at
@modelcontextprotocol/client, which is what hosts install
Replay JSON-RPC messages recorded from ext-apps 1.7.x (sdk 1.x key
order and ids) through the 2.x App and AppBridge over a raw in-memory
transport and assert exactly what 2.x emits: the ui/initialize
handshake, params-less ui/notifications/initialized, tools/call in both
directions, and the -32002 to -32602 re-encoding on the host side. Also
check that AppBridge never sends an MCP initialize toward the View and
that the outer-client proxy works with a default (non-legacy) 2.x
Client/Server pair.
Install instructions now list the split base MCP SDK packages at ^2.0.0
by role (core comes in transitively, server only for server authors),
require Node 20+, and state that the wire protocol is unchanged so 2.x
Views and 1.x hosts interoperate. The agent skills lose the exact
prerelease wording and add-app-to-server gains a v1 to v2 import
mapping. docs/migrate-to-2.md lists the breaking changes, the peer
matrix and the host-side error deltas.
The zod peer range is ^4.2.0, whose schemas implement
~standard.jsonSchema, so the lazy zod/v4 toJSONSchema fallback for
zod 3.25 is dead code. Re-export StandardSchemaWithJSON from the client
package instead of keeping a structurally identical local copy.
Consumers using moduleResolution NodeNext need explicit extensions in
the emitted declaration files. Covers every source file except app.ts,
app-bridge.ts and events.ts (and their tests), which are being reworked
separately.
The app-only consumer now also exercises the ./react entry. The
server-only consumer no longer asserts that client is absent from
node_modules (npm auto-installs required peers) but still checks that
the server entry does not bundle it. Surface npm and esbuild errors
instead of swallowing them.
SDK v2 registerTool takes Standard JSON Schema objects (z.object(...))
and keeps a deprecated overload for the v1 raw-shape form. Mirror that
here so existing servers passing { field: z.string() } keep compiling;
the call is forwarded to the SDK, which auto-wraps the shape. Tests
exercise both forms end to end through a real McpServer and Client.
spec.types.ts previously inferred CallToolResult, ContentBlock and
friends from core's Zod schemas via a local core-types module so that
the type surface stayed free of a client package edge. Now that client
is a required peer, take the types from it directly and delete the
indirection. Generated schemas are unchanged.
The client, core and server packages become ^2.0.0 peers and client is
now a required peer (App and AppBridge extend its Protocol class);
server stays optional for View-only consumers. Examples move to
zod ^4.2.0 to match the library peer range.
The root package is not yet published at 2.0.0, so the lockfile now
links the examples' @modelcontextprotocol/ext-apps dependency to the
repository root instead of a registry copy. This drops the stale 1.x
tarball (and its sdk 1.x peer) from the lockfile and makes
scripts/link-self.mjs a no-op.
npm pack runs the prepare script for directory specs even with
--ignore-scripts and its output can pollute stdout, so locate the packed
tarball on disk instead of parsing --json output. Run npm with a minimal
environment so caller npm config cannot leak into the synthetic
consumers, and require exact versions for the consumers' dependencies.
Add a CI job that runs the check on Node 22.
The emitted .d.ts copy relative specifiers verbatim, and the extensionless form
(e.g. `from "./events"`) doesn't resolve under Node16/NodeNext, hiding inherited
members such as `addEventListener` (TS2339). Appending `.js` matches the existing
SDK-import style and works with both bundler-mode type-checking and Bun.build.
Types-only; runtime is unaffected.
Fixes#704
App and AppBridge extend the public Protocol from @modelcontextprotocol/client; the iframe wire protocol (ui/initialize only) is unchanged. Peers move to the split SDK 2.0 packages with client and server as optional role peers. Fixups (lockfile, peer ranges, restored handler guards, docs, wire-compat tests) follow in a separate PR.
Co-authored-by: Andrew Khadder <andrew@manufact.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Closes#702
Non-breaking transitive updates via `npm audit fix`, plus a `sharp` major
bump. Takes `npm audit` from 15 findings (2 critical, 7 high, 4 moderate,
2 low) to 3 moderate.
Critical / high:
- shell-quote 1.8.3 -> 1.9.0 (GHSA-w7jw-789q-3m8p, GHSA-395f-4hp3-45gv)
- @babel/core 7.28.6 -> 7.29.7 (GHSA-4x5r-pxfx-6jf8)
- sharp ^0.34.5 -> ^0.35.3 (GHSA-f88m-g3jw-g9cj; libvips CVE-2026-33327,
CVE-2026-33328, CVE-2026-35590, CVE-2026-35591)
- brace-expansion 1.1.16 / 2.1.2 (GHSA-3jxr-9vmj-r5cp)
- fast-uri 3.1.4 (GHSA-v2hh-gcrm-f6hx, GHSA-4c8g-83qw-93j6)
- hono 4.12.31 (8 advisories, incl. GHSA-88fw-hqm2-52qc, GHSA-hvrm-45r6-mjfj)
- linkify-it 5.0.2 (GHSA-22p9-wv53-3rq4, GHSA-v245-v573-v5vm)
Plus markdown-it 14.3.0, body-parser 2.3.0, type-is 2.1.0, undici 7.28.0
and vite 6.4.3.
sharp is a devDependency used only by tests/e2e/generate-grid-screenshots,
which is testIgnore'd unless GENERATE_SCREENSHOTS=1. The only breaking
change in 0.35 is the Node floor moving to >=20.9.0, and CI runs Node 22;
the API used (png / resize / toFile) is unchanged.
The 3 remaining findings are all the same advisory (GHSA-frvp-7c67-39w9,
path traversal in @hono/node-server's serve-static on Windows), reported
once each for @hono/node-server, @modelcontextprotocol/sdk and
@modelcontextprotocol/ext-apps. The fix is in @hono/node-server 2.0.5 but
sdk 1.29.0 pins ^1.19.9, so the range cannot reach it; that needs an
upstream bump. It is not reachable here either way — the SDK imports only
getRequestListener and serve from that package, never serve-static.
The Protocol-based migration needs the Protocol class exported from the
package roots, which lands in modelcontextprotocol/typescript-sdk#2501.
Pin the preview builds so the branch is installable; swap to the next
published beta once that PR is released.
Replaces the Client/Server subclassing from the v2 migration with direct
Protocol extension, now that the SDK exports Protocol from the client
package root. This restores the v1 wire contract on the iframe channel:
ui/initialize is the first and only handshake, so deployed hosts keep
working unchanged.
Deleted with the double handshake: the inner MCP initialize round-trip
and its pinned protocol version, the contentless inner capabilities and
their retro-registration plumbing, the oninitialized defineProperty
shadow, and the getCapabilities/getHostCapabilities split (v1
getCapabilities restored). Tests pin the wire order and assert no MCP
initialize is sent in either direction.
Note: package.json overrides point the SDK packages at locally built
tarballs carrying the Protocol export; replace with published versions
before any upstream PR.
The consent page shipped without a viewport meta tag, so mobile browsers
laid it out at a desktop width. Its Approve/Deny buttons were 37px tall
and nested a <button> inside an <a>, which is invalid HTML — interactive
content is not permitted inside a link.
- Add a viewport meta tag to the consent page
- Replace the <a><button> nesting with anchors styled as buttons
(role="button" plus a Space-key handler to keep button keyboard
semantics)
- Lay the actions out as a wrapping flex row with equal 56px tap targets
- Add touch-action: manipulation so a tap resolves to a click immediately
rather than waiting on the double-tap-to-zoom gesture
- Suppress long-press text selection and the iOS callout menu on the
buttons, scoped so the card's client/redirect details stay copyable
- Give buttons an :active style since removing the native tap highlight
otherwise leaves no press feedback
- Apply the same 56px minimum and touch handling to the in-app view's
buttons (the fullscreen toggle was ~33x33)