') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Migrate to SDK v2 with official Protocol and isolated role peers by tonxxd · Pull Request #720 · modelcontextprotocol/ext-apps · GitHub
Skip to content

Migrate to SDK v2 with official Protocol and isolated role peers - #720

Draft
tonxxd wants to merge 13 commits into
modelcontextprotocol:mainfrom
tonxxd:fix/official-protocol-role-peers
Draft

Migrate to SDK v2 with official Protocol and isolated role peers#720
tonxxd wants to merge 13 commits into
modelcontextprotocol:mainfrom
tonxxd:fix/official-protocol-role-peers

Conversation

@tonxxd

@tonxxdtonxxd commented Jul 20, 2026

Copy link
Copy Markdown

Summary

This PR brings @khandrew1's SDK v2 migration from #710 upstream together with Felix's public-Protocol port and the role-isolated optional peers alternative.

Problem

The migration to SDK v2 makes both role packages peers of one ext-apps package. A server entry point also imported constants through app.ts, creating a server → app → client edge.

flowchart LR
Consumer["server-only consumer"] --> ServerEntry["ext-apps/server"]
ServerEntry --> App["app.js"]
App --> Client["@modelcontextprotocol/client"]
ServerEntry --> Server["@modelcontextprotocol/server"]
Loading

Proposed architecture

This alternative reuses the official public Protocol base while isolating each entry point to its matching optional role peer.

flowchart TB
subgraph ClientSide["App / host entry points"]
AppEntry[". / react"] -. "optional peer" .-> Client["@modelcontextprotocol/client"]
BridgeEntry["app-bridge"] -. "optional peer" .-> Client
end
subgraph ServerSide["Server entry point"]
ServerEntry["server"] -. "optional peer" .-> Server["@modelcontextprotocol/server"]
end
AppEntry --> Core["@modelcontextprotocol/core"]
BridgeEntry --> Core
ServerEntry --> Core
Loading
ConsumerInstallsDoes not install or bundle
App / hostext-apps + client + coreserver
MCP serverext-apps + server + coreclient

Implementation

  • Preserves @khandrew1's v2 migration.
  • Ports App and AppBridge to the official Protocol<BaseContext>.
  • Preserves the Apps-only ui/initialize handshake used by v1 hosts.
  • Moves resource constants and shared core wire types into neutral modules.
  • Marks client and server peers optional.
  • Adds packed app/host-only and server-only compile/install/bundle assertions.
  • Clears dist before builds so stale role edges cannot enter published packages.

SDK package status

The public Protocol export from typescript-sdk#2501 is published in @modelcontextprotocol/client@2.0.0-beta.5.

This branch now uses the complete registry-published beta.5 package set (client, core, server, express, and node). The temporary pkg.pr.new specifications and MCP overrides have been removed, and the lockfile contains only npmjs.org HTTP(S) package resolutions.

Tradeoff

This keeps request lifecycle, cancellation, validation, and transport behavior in the official SDK. Consumers still install the one role package used by their entry point. The core-only alternative in #719 avoids both role packages but owns more protocol code in ext-apps.

Verification

  • npm run build
  • npm run build:all
  • npm test: 392 passed, 1 skipped
  • npm run test:dependency-isolation
  • TypeDoc validation with warnings treated as errors
  • Prettier, version consistency, and git diff --check
  • Registry-only lockfile invariant used by CI
  • Functional integration-server E2E: 5 passed

khandrew1and others added 10 commits July 14, 2026 13:35
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 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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates @modelcontextprotocol/ext-apps to the split MCP SDK v2 packages while switching the Apps iframe channel to the official public Protocol base (preserving the v1 ui/initialize-first wire contract) and enforcing role-isolated optional peers for client/server dependencies.

Changes:

  • Ports App/AppBridge and transports/types to v2 split packages (@modelcontextprotocol/{client,core,server}) and updates related tests/snippets.
  • Moves shared wire types/constants into role-neutral modules and updates server helpers accordingly.
  • Adds dependency-isolation verification and improves TypeDoc linking to base SDK docs.

Reviewed changes

Copilot reviewed 122 out of 125 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
typedoc.config.mjsAdds base-SDK external symbol link mapping + plugin.
src/types.tsRemoves protocol-wide union types; keeps re-exports.
src/spec.types.tsSwitches shared type imports to role-neutral core-types.
src/server/index.tsRemoves server→app edge; updates schemas/types to v2.
src/server/index.test.tsUpdates server type import to v2 package.
src/server/index.examples.tsUpdates examples to v2 server imports + zod objects.
src/react/useApp.tsxUpdates Implementation type import to v2 client package.
src/message-transport.tsMoves JSONRPC schema to core; transport types to client.
src/message-transport.test.tsUpdates JSONRPCMessage type import to v2 client package.
src/generated/schema.tsUpdates MCP schema imports from sdk to core.
src/generated/schema.jsonRegenerated JSON schema reflecting new schema typing.
src/events.tsRefactors to protocol-independent EventDispatcher with warnings.
src/events.test.tsAdds unit coverage for EventDispatcher behavior.
src/core-types.tsNew: role-neutral shared types inferred from core schemas.
src/constants.tsNew: role-neutral resource metadata constants.
src/app.tsMigrates App to Protocol base; updates handlers/capabilities/events.
src/app.test.tsNew tests pin iframe handshake order + v2 Protocol behavior.
src/app.examples.tsUpdates examples to split v2 client/server types.
src/app-bridge.test.tsUpdates to v2 packages; adds handshake/validation/proxy tests.
src/app-bridge.examples.tsUpdates examples to v2 packages + new context signal shape.
scripts/typedoc-plugin-base-sdk-links.mjsNew TypeDoc resolver for base-SDK local refs.
scripts/generate-schemas.tsUpdates schema generator commentary/import rewrite to core.
scripts/check-dependency-isolation.mjsNew packed install/compile/bundle role-isolation check.
README.mdUpdates install guidance to split v2 packages.
plugins/mcp-apps/skills/create-mcp-app/SKILL.mdUpdates dependency guidance for v2 split packages.
plugins/mcp-apps/skills/convert-web-app/SKILL.mdUpdates migration instructions/snippets for v2 APIs.
plugins/mcp-apps/skills/add-app-to-server/SKILL.mdUpdates setup + code samples for v2 APIs.
package.jsonSwitches to split v2 peer deps + adds isolation test script.
package-lock.jsonUpdates lockfile for split v2 packages + examples.
examples/wiki-explorer-server/src/mcp-app.tsUpdates CallToolResult typing import to v2 client package.
examples/wiki-explorer-server/server.tsUpdates server types/imports to v2 server package.
examples/wiki-explorer-server/package.jsonAdds split v2 dependencies.
examples/wiki-explorer-server/main.tsUpdates transports to v2 express/node/server packages.
examples/video-resource-server/src/mcp-app.tsUpdates CallToolResult typing import to v2 client package.
examples/video-resource-server/server.tsUpdates server types/imports + input schema to z.object.
examples/video-resource-server/package.jsonAdds split v2 dependencies.
examples/video-resource-server/main.tsUpdates transports to v2 express/node/server packages.
examples/transcript-server/server.tsUpdates server imports + uses z.object({}) inputSchema.
examples/transcript-server/package.jsonAdds split v2 dependencies.
examples/transcript-server/main.tsUpdates transports to v2 express/node/server packages.
examples/threejs-server/src/mcp-app-wrapper.tsxUpdates CallToolResult typing import to v2 client package.
examples/threejs-server/server.tsUpdates server imports + uses z.object({}) inputSchema.
examples/threejs-server/package.jsonAdds split v2 dependencies.
examples/threejs-server/main.tsUpdates transports to v2 express/node/server packages.
examples/system-monitor-server/server.tsUpdates server imports + outputSchema uses full Zod schema.
examples/system-monitor-server/package.jsonAdds split v2 dependencies.
examples/system-monitor-server/main.tsUpdates transports to v2 express/node/server packages.
examples/sheet-music-server/server.tsUpdates server imports to v2 server package.
examples/sheet-music-server/package.jsonAdds split v2 dependencies.
examples/sheet-music-server/main.tsUpdates transports to v2 express/node/server packages.
examples/shadertoy-server/server.tsUpdates server imports to v2 server package.
examples/shadertoy-server/package.jsonAdds split v2 dependencies.
examples/shadertoy-server/main.tsUpdates transports to v2 express/node/server packages.
examples/scenario-modeler-server/server.tsUpdates imports + uses full Zod schemas (not .shape).
examples/scenario-modeler-server/package.jsonAdds split v2 dependencies.
examples/scenario-modeler-server/main.tsUpdates transports to v2 express/node/server packages.
examples/say-server/package.jsonRemoves sdk v1 dependency from python example package.json.
examples/quickstart/server.tsUpdates server import + uses z.object({}) inputSchema.
examples/quickstart/package.jsonAdds split v2 dependencies.
examples/quickstart/main.tsUpdates transports to v2 express/node/server packages.
examples/qr-server/package.jsonRemoves sdk v1 dependency from python example package.json.
examples/pdf-server/src/mcp-app.tsUpdates CallToolResult/ContentBlock imports to v2 client package.
examples/pdf-server/server.tsUpdates server imports + requestId/signal usage to v2 context.
examples/pdf-server/server.test.tsUpdates in-memory client imports to v2 client package.
examples/pdf-server/package.jsonAdds split v2 dependencies.
examples/pdf-server/main.tsUpdates transports to v2 express/node/server packages.
examples/map-server/src/mcp-app.tsUpdates ContentBlock typing import to v2 client package.
examples/map-server/server.tsUpdates server imports + uses z.object for schemas.
examples/map-server/package.jsonAdds split v2 dependencies.
examples/map-server/main.tsUpdates transports to v2 express/node/server packages.
examples/lazy-auth-server/server.tsUpdates to v2 server/node + z.object({}) schemas + context id.
examples/lazy-auth-server/package.jsonAdds split v2 dependencies.
examples/integration-server/src/mcp-app.tsxUpdates CallToolResult typing import to v2 client package.
examples/integration-server/server.tsUpdates server imports + uses z.object({}) inputSchema.
examples/integration-server/package.jsonAdds split v2 dependencies.
examples/integration-server/main.tsUpdates transports to v2 express/node/server packages.
examples/debug-server/server.tsUpdates server imports to v2 server package.
examples/debug-server/package.jsonAdds split v2 dependencies.
examples/debug-server/main.tsUpdates transports to v2 express/node/server packages.
examples/customer-segmentation-server/server.tsUpdates imports + uses full Zod schemas (not .shape).
examples/customer-segmentation-server/package.jsonAdds split v2 dependencies.
examples/customer-segmentation-server/main.tsUpdates transports to v2 express/node/server packages.
examples/cohort-heatmap-server/server.tsUpdates imports + uses full Zod schemas (not .shape).
examples/cohort-heatmap-server/package.jsonAdds split v2 dependencies.
examples/cohort-heatmap-server/main.tsUpdates transports to v2 express/node/server packages.
examples/budget-allocator-server/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/budget-allocator-server/package.jsonAdds split v2 dependencies.
examples/budget-allocator-server/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-vue/src/App.vueUpdates CallToolResult typing import to v2 client package.
examples/basic-server-vue/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-vue/package.jsonAdds split v2 dependencies.
examples/basic-server-vue/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-vanillajs/src/mcp-app.tsUpdates CallToolResult typing import to v2 client package.
examples/basic-server-vanillajs/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-vanillajs/package.jsonAdds split v2 dependencies.
examples/basic-server-vanillajs/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-svelte/src/App.svelteUpdates CallToolResult typing import to v2 client package.
examples/basic-server-svelte/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-svelte/package.jsonAdds split v2 dependencies.
examples/basic-server-svelte/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-solid/src/mcp-app.tsxUpdates CallToolResult typing import to v2 client package.
examples/basic-server-solid/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-solid/package.jsonAdds split v2 dependencies.
examples/basic-server-solid/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-react/src/mcp-app.tsxUpdates CallToolResult typing import to v2 client package.
examples/basic-server-react/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-react/package.jsonAdds split v2 dependencies.
examples/basic-server-react/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-server-preact/src/mcp-app.tsxUpdates CallToolResult typing import to v2 client package.
examples/basic-server-preact/server.tsUpdates imports + uses z.object({}) inputSchema.
examples/basic-server-preact/package.jsonAdds split v2 dependencies.
examples/basic-server-preact/main.tsUpdates transports to v2 express/node/server packages.
examples/basic-host/src/index.tsxUpdates Tool type import to v2 client package.
examples/basic-host/src/implementation.tsUpdates client + transports imports to v2 client package.
examples/basic-host/package.jsonUpdates base SDK dependency to v2 client package.
docs/testing-mcp-apps.mdAdds guidance about build/snippet type-checking.
docs/quickstart.mdUpdates Node requirement + dependencies/snippets to v2.
docs/patterns.tsxUpdates imports and example code to v2 APIs.
docs/patterns.mdUpdates patterns snippets for v2 schema + request usage.
docs/overview.mdUpdates architecture/lifecycle explanation for v2 approach.
docs/migrate_from_openai_apps.mdUpdates McpServer import + zod object schema examples.
docs/authorization.mdUpdates schema example to z.object(...).
build.bun.tsClears dist before build; updates externals to split peers.
AGENTS.mdUpdates architecture docs for Protocol-based iframe channel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/app.ts Outdated
Comment threadsrc/app.ts Outdated
Comment threaddocs/overview.md Outdated
Comment threadREADME.md
Comment on lines +125 to +129
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@2.0.0-beta.4 \
@modelcontextprotocol/server@2.0.0-beta.4 \
@modelcontextprotocol/core@2.0.0-beta.4 \
zod@^4.2.0
Sign up for freeto 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.

4 participants

@tonxxd@khandrew1@felixweinberger