From 65507be3a732bb06cbec0e363ca971780fd311b1 Mon Sep 17 00:00:00 2001 From: yannj-fr <4557670+yannj-fr@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:25:38 +0100 Subject: [PATCH] fix(mcp-apps-renderer): address review on the React-pilot PR Bridge-free /activity (review blocker): the `./activity` entry pulled ext-apps' LATEST_PROTOCOL_VERSION through constants.ts, statically dragging the App SDK + zod/v4 (~+55 kB gzip) into react-core's eager chunk. Move MCP_APPS_PROTOCOL_VERSION to the bridge side (session.ts), sourced from the /app-bridge subpath the session already imports - single source of truth, no hand-maintained literal, zero extra cost. dist/activity.mjs and dist/constants.mjs now carry no @modelcontextprotocol edge. Keep react-core's zod peer at >=3.25 (review blocker): the floor moved down to the package, it did not go away; ext-apps + the MCP SDK both hard-require it, so >=3.0.0 is an ERESOLVE install failure once an MCP App renders. Drop the web component from this PR: nothing consumes it here (the React adapter builds its own iframe) and it carried two open defects. It lands with the Vue/Angular adapters, its first real consumers. Removes its files, exports, tsdown entry, and the now-unused lit dependency. Also: add content.serverId to the bind effect deps; delete drained thread entries from the request queue's maps (bounded growth); register the package in static_compat.yml (+ a compat-check script) and static_bundle_size.yml; publint repository.url -> git+https. Manifest regenerated. --- .github/workflows/static_bundle_size.yml | 2 +- .github/workflows/static_compat.yml | 4 +- packages/mcp-apps-renderer/package.json | 26 +-- .../src/__tests__/sandbox.test.ts | 14 +- packages/mcp-apps-renderer/src/constants.ts | 8 - .../mcp-apps-renderer/src/request-queue.ts | 11 +- packages/mcp-apps-renderer/src/session.ts | 11 + .../web-components/__tests__/mcp-app.test.ts | 157 ------------- .../src/web-components/define.ts | 18 -- .../src/web-components/index.ts | 9 - .../src/web-components/mcp-app.ts | 214 ------------------ packages/mcp-apps-renderer/tsdown.config.ts | 12 - packages/react-core/package.json | 2 +- .../v2/components/MCPAppsActivityRenderer.tsx | 14 +- pnpm-lock.yaml | 5 +- scripts/release/public-api/manifest.v1.json | 42 +--- 16 files changed, 52 insertions(+), 497 deletions(-) delete mode 100644 packages/mcp-apps-renderer/src/web-components/__tests__/mcp-app.test.ts delete mode 100644 packages/mcp-apps-renderer/src/web-components/define.ts delete mode 100644 packages/mcp-apps-renderer/src/web-components/index.ts delete mode 100644 packages/mcp-apps-renderer/src/web-components/mcp-app.ts diff --git a/.github/workflows/static_bundle_size.yml b/.github/workflows/static_bundle_size.yml index 9e95ba63c0..9a3a45c928 100644 --- a/.github/workflows/static_bundle_size.yml +++ b/.github/workflows/static_bundle_size.yml @@ -66,7 +66,7 @@ jobs: # branch) so compressed-size-action can build both sides for comparison. # The `pattern` below restricts measurement to the 10 in-scope packages. build-script: build - pattern: "packages/{core,shared,react-core,react-ui,react-textarea,runtime-client-gql,web-inspector,voice,a2ui-renderer,react-native}/dist/**/*.{mjs,js,cjs}" + pattern: "packages/{core,shared,react-core,react-ui,react-textarea,runtime-client-gql,web-inspector,voice,a2ui-renderer,react-native,mcp-apps-renderer}/dist/**/*.{mjs,js,cjs}" # Measures what an app importing { CopilotChat } from # @copilotkit/react-core/v2 bundles, by driving esbuild over a synthetic entry diff --git a/.github/workflows/static_compat.yml b/.github/workflows/static_compat.yml index 4cfc27cd60..558e4ba86d 100644 --- a/.github/workflows/static_compat.yml +++ b/.github/workflows/static_compat.yml @@ -49,9 +49,9 @@ jobs: - name: Build packages run: > npx nx run-many -t build - --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer + --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer,@copilotkit/mcp-apps-renderer - name: Run compat-check run: > npx nx run-many -t compat-check - --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer + --projects=@copilotkit/core,@copilotkit/shared,@copilotkit/react-core,@copilotkit/react-ui,@copilotkit/react-textarea,@copilotkit/runtime-client-gql,@copilotkit/web-inspector,@copilotkit/voice,@copilotkit/a2ui-renderer,@copilotkit/mcp-apps-renderer diff --git a/packages/mcp-apps-renderer/package.json b/packages/mcp-apps-renderer/package.json index 4f9d42b6bd..969069d9c5 100644 --- a/packages/mcp-apps-renderer/package.json +++ b/packages/mcp-apps-renderer/package.json @@ -16,7 +16,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/CopilotKit/CopilotKit.git", + "url": "git+https://github.com/CopilotKit/CopilotKit.git", "directory": "packages/mcp-apps-renderer" }, "files": [ @@ -47,26 +47,6 @@ "default": "./dist/activity.cjs" } }, - "./web-components": { - "import": { - "types": "./dist/web-components/index.d.mts", - "default": "./dist/web-components/index.mjs" - }, - "require": { - "types": "./dist/web-components/index.d.cts", - "default": "./dist/web-components/index.cjs" - } - }, - "./web-components/define": { - "import": { - "types": "./dist/web-components/define.d.mts", - "default": "./dist/web-components/define.mjs" - }, - "require": { - "types": "./dist/web-components/define.d.cts", - "default": "./dist/web-components/define.cjs" - } - }, "./package.json": "./package.json" }, "publishConfig": { @@ -74,6 +54,7 @@ }, "scripts": { "build": "tsdown", + "compat-check": "es-check es2022 --module 'dist/**/!(*.umd).{mjs,cjs,js}' && es-check es2018 'dist/**/*.umd.js'", "check-types": "tsc --noEmit -p tsconfig.json", "test": "vitest run", "test:watch": "vitest", @@ -83,8 +64,7 @@ "dependencies": { "@copilotkit/shared": "workspace:*", "@modelcontextprotocol/ext-apps": "^1.7.5", - "@modelcontextprotocol/sdk": "^1.29.0", - "lit": "^3.3.2" + "@modelcontextprotocol/sdk": "^1.29.0" }, "peerDependencies": { "@ag-ui/client": "0.0.59", diff --git a/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts b/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts index 27a8948add..51fb0f1a5a 100644 --- a/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts +++ b/packages/mcp-apps-renderer/src/__tests__/sandbox.test.ts @@ -1,10 +1,13 @@ import { describe, expect, it } from "vitest"; +import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/ext-apps"; import { buildSandboxHTML } from "../sandbox"; import { - MCP_APPS_PROTOCOL_VERSION, MCP_OPEN_LINK_BLOCKED_SCHEMES, MCPAppsActivityType, } from "../constants"; +// MCP_APPS_PROTOCOL_VERSION lives on the bridge side (session) so the bridge-free +// `./constants` / `./activity` entry never pulls the ext-apps bundle. +import { MCP_APPS_PROTOCOL_VERSION } from "../session"; import { MCPAppsActivityContentSchema } from "../content-schema"; describe("buildSandboxHTML", () => { @@ -28,7 +31,14 @@ describe("constants", () => { expect(MCPAppsActivityType).toBe("mcp-apps"); }); - it("advertises the ext-apps protocol version (2026-01-26)", () => { + it("sources the protocol version from ext-apps (no hand-maintained literal)", () => { + // MCP_APPS_PROTOCOL_VERSION is a re-export of the bridge's own + // LATEST_PROTOCOL_VERSION, so it can never drift from the spec version. + expect(MCP_APPS_PROTOCOL_VERSION).toBe(LATEST_PROTOCOL_VERSION); + }); + + it("negotiates the expected current protocol version (2026-01-26 canary)", () => { + // Canary: if ext-apps bumps the version, this fails so we review the change. expect(MCP_APPS_PROTOCOL_VERSION).toBe("2026-01-26"); }); diff --git a/packages/mcp-apps-renderer/src/constants.ts b/packages/mcp-apps-renderer/src/constants.ts index 75cd1c1ca0..88c912381e 100644 --- a/packages/mcp-apps-renderer/src/constants.ts +++ b/packages/mcp-apps-renderer/src/constants.ts @@ -1,16 +1,8 @@ -import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/ext-apps"; - /** * Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType. */ export const MCPAppsActivityType = "mcp-apps"; -/** - * Single source of truth for the MCP Apps protocol version negotiated by the host - * (the ext-apps latest). Frontends must not hardcode their own version. - */ -export const MCP_APPS_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION; - /** * URL schemes a widget may NOT open via ui/open-link. The ext-apps schema * validates `url` as a plain string only (noopener/noreferrer does not restrict diff --git a/packages/mcp-apps-renderer/src/request-queue.ts b/packages/mcp-apps-renderer/src/request-queue.ts index 729199dc68..c64047bf8a 100644 --- a/packages/mcp-apps-renderer/src/request-queue.ts +++ b/packages/mcp-apps-renderer/src/request-queue.ts @@ -76,7 +76,16 @@ export class MCPAppsRequestQueue { queue.shift(); } } finally { - this.processing.set(threadId, false); + // Drop the drained thread entries from both maps. `mcpAppsRequestQueue` is + // shared for the page lifetime, so retaining an entry per thread id would + // grow unbounded as threads come and go. + const queue = this.queues.get(threadId); + if (!queue || queue.length === 0) { + this.queues.delete(threadId); + this.processing.delete(threadId); + } else { + this.processing.set(threadId, false); + } } } diff --git a/packages/mcp-apps-renderer/src/session.ts b/packages/mcp-apps-renderer/src/session.ts index 444afc853d..cb5d67690e 100644 --- a/packages/mcp-apps-renderer/src/session.ts +++ b/packages/mcp-apps-renderer/src/session.ts @@ -1,5 +1,6 @@ import { AppBridge, + LATEST_PROTOCOL_VERSION, PostMessageTransport, } from "@modelcontextprotocol/ext-apps/app-bridge"; import type { AbstractAgent } from "@ag-ui/client"; @@ -13,6 +14,16 @@ import type { ɵMcpFollowUpHost } from "./follow-up"; import { MCP_OPEN_LINK_BLOCKED_SCHEMES } from "./constants"; import type { MCPAppsActivityContent } from "./content-schema"; +/** + * The MCP Apps protocol version this host negotiates. Sourced directly from the + * ext-apps bridge (single source of truth, no hand-maintained literal). It lives + * here (a bridge-side module) rather than in the bridge-free `./constants` / + * `./activity` entry so the lightweight activity-registration surface stays free + * of the ext-apps bundle; consumers that need the version import it from the + * package root, which already loads the bridge. + */ +export const MCP_APPS_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION; + /** * Permissive `ui/message` schema. ext-apps restricts the request to * `role: "user"` with no `followUp`, but CopilotKit intentionally extends diff --git a/packages/mcp-apps-renderer/src/web-components/__tests__/mcp-app.test.ts b/packages/mcp-apps-renderer/src/web-components/__tests__/mcp-app.test.ts deleted file mode 100644 index 260de05561..0000000000 --- a/packages/mcp-apps-renderer/src/web-components/__tests__/mcp-app.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; -import type { AbstractAgent } from "@ag-ui/client"; -import { defineMcpAppWebComponents } from "../define"; -import { COPILOTKIT_MCP_APP_TAG } from "../index"; -import type { CopilotKitMcpApp } from "../mcp-app"; -import type { MCPAppsActivityContent } from "../../content-schema"; - -// Minimal agent mock: only what bindMcpApp + the request queue touch. -function makeAgent() { - const runAgentCalls: Array = []; - const agent = { - agentId: "test-agent", - threadId: "thread-1", - isRunning: false, - runAgentCalls, - addMessage() {}, - subscribe() { - return { unsubscribe() {} }; - }, - async runAgent(input?: any) { - runAgentCalls.push(input); - const req = input?.forwardedProps?.__proxiedMCPRequest; - if (req?.method === "resources/read") { - return { - result: { - contents: [ - { - uri: req.params?.uri, - mimeType: "text/html", - text: "Widget", - }, - ], - }, - newMessages: [], - }; - } - return { result: {}, newMessages: [] }; - }, - }; - return agent as unknown as AbstractAgent & { - runAgentCalls: typeof runAgentCalls; - }; -} - -function makeContent( - over?: Partial, -): MCPAppsActivityContent { - return { - resourceUri: "ui://test/app", - serverHash: "hash-123", - result: { content: [], isError: false }, - toolInput: {}, - ...over, - } as MCPAppsActivityContent; -} - -const tick = (ms = 30) => new Promise((r) => setTimeout(r, ms)); - -/** Dispatch a JSON-RPC message from the iframe (source = its contentWindow). */ -function fromIframe(iframe: HTMLIFrameElement, data: unknown) { - window.dispatchEvent( - new MessageEvent("message", { - data, - source: iframe.contentWindow, - origin: "", - }), - ); -} - -let elements: CopilotKitMcpApp[] = []; - -async function mountApp(opts?: { content?: MCPAppsActivityContent }) { - const agent = makeAgent(); - const el = document.createElement(COPILOTKIT_MCP_APP_TAG) as CopilotKitMcpApp; - el.agent = agent; - el.host = { runAgent: async () => ({ result: undefined, newMessages: [] }) }; - el.content = opts?.content ?? makeContent(); - document.body.appendChild(el); - elements.push(el); - await el.updateComplete; - await tick(60); // let fetchResource + bridge.connect settle - const iframe = el.querySelector("iframe") as HTMLIFrameElement; - return { el, agent, iframe }; -} - -/** Simulate the sandbox proxy-ready + initialized handshake. */ -async function handshake(iframe: HTMLIFrameElement) { - fromIframe(iframe, { - jsonrpc: "2.0", - method: "ui/notifications/sandbox-proxy-ready", - }); - await tick(20); - fromIframe(iframe, { - jsonrpc: "2.0", - method: "ui/notifications/initialized", - }); - await tick(20); -} - -beforeAll(() => { - defineMcpAppWebComponents(); -}); - -afterEach(() => { - elements.forEach((el) => el.remove()); - elements = []; - vi.restoreAllMocks(); -}); - -describe("", () => { - it("registers the custom element", () => { - expect(customElements.get(COPILOTKIT_MCP_APP_TAG)).toBeDefined(); - }); - - it("creates the sandbox iframe once and binds through the agent", async () => { - const { agent, iframe } = await mountApp(); - expect(iframe).toBeTruthy(); - expect(iframe.getAttribute("data-testid")).toBe("mcp-app-iframe"); - const readCall = agent.runAgentCalls.find( - (c) => - c?.forwardedProps?.__proxiedMCPRequest?.method === "resources/read", - ); - expect(readCall).toBeDefined(); - }); - - it("does NOT remount the iframe when content changes", async () => { - const { el, iframe } = await mountApp(); - expect(iframe).toBeTruthy(); - - el.content = makeContent({ toolInput: { a: 1 } }); - await el.updateComplete; - await tick(10); - - const iframeAfter = el.querySelector("iframe"); - expect(iframeAfter).toBe(iframe); // same node, never recreated - }); - - it("emits copilotkit-mcp-initialized when the widget initializes", async () => { - const { el, iframe } = await mountApp(); - const onInit = vi.fn(); - el.addEventListener("copilotkit-mcp-initialized", onInit); - await handshake(iframe); - expect(onInit).toHaveBeenCalled(); - }); - - it("tears down the session on disconnect without throwing", async () => { - const { el, iframe } = await mountApp(); - await handshake(iframe); - expect(() => el.remove()).not.toThrow(); - // Post-teardown messages are ignored (no throw). - fromIframe(iframe, { - jsonrpc: "2.0", - method: "ui/notifications/initialized", - }); - await tick(10); - }); -}); diff --git a/packages/mcp-apps-renderer/src/web-components/define.ts b/packages/mcp-apps-renderer/src/web-components/define.ts deleted file mode 100644 index 9ecba105f8..0000000000 --- a/packages/mcp-apps-renderer/src/web-components/define.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Side-effecting entry: registers `` with the custom -// element registry. Import for the effect; call `defineMcpAppWebComponents()` -// explicitly if you prefer an idempotent, guarded registration. -import { CopilotKitMcpApp } from "./mcp-app"; -import { COPILOTKIT_MCP_APP_TAG } from "./index"; - -export function defineMcpAppWebComponents(): void { - if ( - typeof customElements !== "undefined" && - !customElements.get(COPILOTKIT_MCP_APP_TAG) - ) { - customElements.define(COPILOTKIT_MCP_APP_TAG, CopilotKitMcpApp); - } -} - -defineMcpAppWebComponents(); - -export { CopilotKitMcpApp, COPILOTKIT_MCP_APP_TAG }; diff --git a/packages/mcp-apps-renderer/src/web-components/index.ts b/packages/mcp-apps-renderer/src/web-components/index.ts deleted file mode 100644 index beee836767..0000000000 --- a/packages/mcp-apps-renderer/src/web-components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Web Component surface for MCP Apps. -// -// `` owns the sandbox iframe + the ext-apps bridge (via -// bindMcpApp) and renders it without remounting the iframe across re-renders or -// property updates. Import from here for the class/tag; import `./define` for -// the side-effecting `customElements.define` registration. -export { CopilotKitMcpApp } from "./mcp-app"; - -export const COPILOTKIT_MCP_APP_TAG = "copilotkit-mcp-app" as const; diff --git a/packages/mcp-apps-renderer/src/web-components/mcp-app.ts b/packages/mcp-apps-renderer/src/web-components/mcp-app.ts deleted file mode 100644 index a707a9cf67..0000000000 --- a/packages/mcp-apps-renderer/src/web-components/mcp-app.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { html, LitElement, nothing } from "lit"; -import type { AbstractAgent } from "@ag-ui/client"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { bindMcpApp } from "../session"; -import type { FetchedResource, McpAppSession } from "../session"; -import type { ɵMcpFollowUpHost } from "../follow-up"; -import type { MCPAppsActivityContent } from "../content-schema"; - -/** - * `` - framework-agnostic host element for a single MCP App. - * - * The element OWNS the sandbox iframe: it creates the iframe exactly once (in a - * static slot, imperatively, NOT through the reactive template) so re-renders, - * property updates, and reactive state changes never remount it. All protocol - * logic lives in `bindMcpApp`; this element only mounts the iframe, sizes it - * from the widget's size notifications, forwards tool input/result on content - * changes, and surfaces lifecycle as DOM events: - * - * - `copilotkit-mcp-initialized` the widget finished initializing - * - `copilotkit-mcp-size-changed` detail: { width?, height? } - * - `copilotkit-mcp-error` detail: { error: Error } - * - * Consumers (React/Vue/Angular thin adapters, or plain HTML) set the `agent`, - * `host`, and `content` properties (all objects, so `attribute: false`); the - * session binds as soon as `agent` + `content` + `host` are all present. - */ -export class CopilotKitMcpApp extends LitElement { - static properties = { - agent: { attribute: false }, - host: { attribute: false }, - content: { attribute: false }, - }; - - /** The AG-UI agent used to proxy resource/tool requests. */ - agent?: AbstractAgent; - /** CopilotKit host, for ui/message follow-up runs (issue #5819). */ - host?: ɵMcpFollowUpHost; - /** The activity content (resourceUri, serverHash, tool input/result). */ - content?: MCPAppsActivityContent; - - private iframe: HTMLIFrameElement | null = null; - private session: McpAppSession | null = null; - private started = false; - private lastToolInput?: string; - private lastToolResult?: string; - private setupError: Error | null = null; - - /** Light DOM: the iframe lives in the page, easy to size, no shadow needed. */ - protected createRenderRoot() { - return this; - } - - connectedCallback(): void { - super.connectedCallback(); - // If the element was moved (disconnect -> reconnect) the iframe still exists - // but the session was torn down; rebind against the current props. - if (this.iframe && !this.session) { - this.maybeStart(); - } - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - this.session?.teardown(); - this.session = null; - this.started = false; - } - - protected firstUpdated(): void { - // Create the iframe ONCE, imperatively, into the static slot. It is never - // part of the reactive template, so Lit never reconciles/remounts it. - const slot = this.querySelector("[data-mcp-slot]"); - if (!slot) return; - const iframe = document.createElement("iframe"); - iframe.style.width = "100%"; - iframe.style.border = "none"; - slot.appendChild(iframe); - this.iframe = iframe; - this.maybeStart(); - } - - protected updated(changed: Map): void { - if (!this.started) { - // Deps may have arrived after firstUpdated (frameworks often set - // properties over several ticks). - if ( - changed.has("agent") || - changed.has("host") || - changed.has("content") - ) { - this.maybeStart(); - } - return; - } - if (changed.has("content")) { - this.pushContent(); - } - } - - private maybeStart(): void { - if (this.started || !this.iframe) return; - const { agent, host, content } = this; - if (!agent || !host || !content) return; - - this.started = true; - this.setupError = null; - this.session = bindMcpApp({ - iframe: this.iframe, - getContent: () => this.content as MCPAppsActivityContent, - getAgent: () => this.agent, - host, - hooks: { - onSizeChanged: (size) => this.onSizeChanged(size), - onInitialized: () => this.onInitialized(), - onResource: (resource) => this.onResource(resource), - onError: (error) => this.onSetupError(error), - }, - }); - // Push any tool input/result already present (buffered until the widget is - // ready by the session itself). - this.pushContent(); - } - - /** Forward tool input/result to the widget when they change (Effects 3/4). */ - private pushContent(): void { - if (!this.session || !this.content) return; - const { toolInput, result } = this.content; - - if (toolInput !== undefined) { - const key = safeStringify(toolInput); - if (key !== this.lastToolInput) { - this.lastToolInput = key; - this.session.sendToolInput(toolInput as Record); - } - } - if (result !== undefined) { - const key = safeStringify(result); - if (key !== this.lastToolResult) { - this.lastToolResult = key; - this.session.sendToolResult(result as CallToolResult); - } - } - } - - private onSizeChanged(size: { width?: number; height?: number }): void { - if (this.iframe && typeof size.height === "number") { - this.iframe.style.height = `${size.height}px`; - } - this.dispatchEvent( - new CustomEvent("copilotkit-mcp-size-changed", { - detail: size, - bubbles: true, - composed: true, - }), - ); - } - - private onInitialized(): void { - this.dispatchEvent( - new CustomEvent("copilotkit-mcp-initialized", { - bubbles: true, - composed: true, - }), - ); - } - - private onResource(resource: FetchedResource): void { - // Reflect the widget's border preference so consumers can style the host. - if (resource._meta?.ui?.prefersBorder) { - this.setAttribute("prefers-border", ""); - } else { - this.removeAttribute("prefers-border"); - } - } - - private onSetupError(error: Error): void { - this.setupError = error; - this.requestUpdate(); - this.dispatchEvent( - new CustomEvent("copilotkit-mcp-error", { - detail: { error }, - bubbles: true, - composed: true, - }), - ); - } - - render() { - return html` - ${ - this.setupError - ? html`
- ${this.setupError.message} -
` - : nothing - } -
- `; - } -} - -function safeStringify(value: unknown): string { - try { - return JSON.stringify(value) ?? ""; - } catch { - return ""; - } -} - -declare global { - interface HTMLElementTagNameMap { - "copilotkit-mcp-app": CopilotKitMcpApp; - } -} diff --git a/packages/mcp-apps-renderer/tsdown.config.ts b/packages/mcp-apps-renderer/tsdown.config.ts index e24339d8aa..e0441d51c9 100644 --- a/packages/mcp-apps-renderer/tsdown.config.ts +++ b/packages/mcp-apps-renderer/tsdown.config.ts @@ -6,7 +6,6 @@ const externalPkgs = [ "@ag-ui/client", "@copilotkit/shared", "zod", - "lit", ]; const isExternal = (id: string) => @@ -24,15 +23,4 @@ export default defineConfig([ external: isExternal, exports: false, }, - { - entry: ["src/web-components/index.ts", "src/web-components/define.ts"], - format: ["esm", "cjs"], - dts: true, - sourcemap: true, - target: "es2022", - outDir: "dist/web-components", - unbundle: true, - external: isExternal, - exports: false, - }, ]); diff --git a/packages/react-core/package.json b/packages/react-core/package.json index c58c0d57c7..19be5e09cc 100644 --- a/packages/react-core/package.json +++ b/packages/react-core/package.json @@ -137,6 +137,6 @@ "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc", - "zod": ">=3.0.0" + "zod": ">=3.25" } } diff --git a/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx b/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx index 9de7411c8a..f603344974 100644 --- a/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx +++ b/packages/react-core/src/v2/components/MCPAppsActivityRenderer.tsx @@ -74,9 +74,9 @@ export const MCPAppsActivityRenderer: React.FC = agentRef.current = agent; // Effect 1: create the sandbox iframe and bind the MCP session. Re-binds - // only when the widget identity (resourceUri/serverHash) or the agent/host - // changes - NOT when tool input/result stream in (those are pushed by the - // effects below without recreating the iframe). + // only when the widget identity (resourceUri/serverHash/serverId) or the + // agent/host changes - NOT when tool input/result stream in (those are + // pushed by the effects below without recreating the iframe). useEffect(() => { const container = containerRef.current; if (!container) { @@ -178,7 +178,13 @@ export const MCPAppsActivityRenderer: React.FC = iframeRef.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [agent, copilotkit, content.resourceUri, content.serverHash]); + }, [ + agent, + copilotkit, + content.resourceUri, + content.serverHash, + content.serverId, + ]); // Effect 2: size the iframe when the widget reports a new content size. useEffect(() => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bac86f9597..4f8283ab18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2679,9 +2679,6 @@ importers: '@modelcontextprotocol/sdk': specifier: '>=1.26.0' version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) - lit: - specifier: ^3.3.2 - version: 3.3.2 devDependencies: '@ag-ui/client': specifier: 0.0.59 @@ -2691,7 +2688,7 @@ importers: version: 26.1.0 tsdown: specifier: ^0.20.3 - version: 0.20.3(@arethetypeswrong/core@0.18.2)(oxc-resolver@11.24.2)(publint@0.3.17)(synckit@0.11.12)(typescript@5.9.2) + version: 0.20.3(@arethetypeswrong/core@0.18.2)(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(oxc-resolver@11.24.2)(publint@0.3.17)(synckit@0.11.12)(typescript@5.9.2) typescript: specifier: 5.9.2 version: 5.9.2 diff --git a/scripts/release/public-api/manifest.v1.json b/scripts/release/public-api/manifest.v1.json index ee92605090..79f99e4492 100644 --- a/scripts/release/public-api/manifest.v1.json +++ b/scripts/release/public-api/manifest.v1.json @@ -1072,46 +1072,6 @@ "path": "packages/mcp-apps-renderer/package.json", "selector": "exports[\"./package.json\"]" } - }, - { - "importPath": "@copilotkit/mcp-apps-renderer/web-components", - "exportKey": "./web-components", - "kind": "code", - "conditions": { - "import": { - "types": "./dist/web-components/index.d.mts", - "default": "./dist/web-components/index.mjs" - }, - "require": { - "types": "./dist/web-components/index.d.cts", - "default": "./dist/web-components/index.cjs" - } - }, - "provenance": { - "kind": "package-json", - "path": "packages/mcp-apps-renderer/package.json", - "selector": "exports[\"./web-components\"]" - } - }, - { - "importPath": "@copilotkit/mcp-apps-renderer/web-components/define", - "exportKey": "./web-components/define", - "kind": "code", - "conditions": { - "import": { - "types": "./dist/web-components/define.d.mts", - "default": "./dist/web-components/define.mjs" - }, - "require": { - "types": "./dist/web-components/define.d.cts", - "default": "./dist/web-components/define.cjs" - } - }, - "provenance": { - "kind": "package-json", - "path": "packages/mcp-apps-renderer/package.json", - "selector": "exports[\"./web-components/define\"]" - } } ], "compatibility": { @@ -1233,7 +1193,7 @@ "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc", - "zod": ">=3.0.0" + "zod": ">=3.25" }, "provenance": [ {