From bb115c1f9b26242bc694e6a791e32b207067338d Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Thu, 4 Dec 2025 14:11:49 +0100 Subject: [PATCH 01/11] experimental MCP apps extension support --- apps/react/demo/package.json | 4 +- .../app/api/copilotkit/[[...slug]]/route.ts | 7 +- .../src/lib/mcp-apps-extension-middleware.ts | 543 ++++++++++++++++++ packages/agent/package.json | 2 +- packages/angular/package.json | 4 +- packages/core/package.json | 2 +- packages/react/package.json | 4 +- packages/runtime/package.json | 6 +- packages/shared/package.json | 2 +- packages/sqlite-runner/package.json | 2 +- packages/web-inspector/package.json | 2 +- pnpm-lock.yaml | 343 +++++------ 12 files changed, 720 insertions(+), 201 deletions(-) create mode 100644 apps/react/demo/src/lib/mcp-apps-extension-middleware.ts diff --git a/apps/react/demo/package.json b/apps/react/demo/package.json index 90d82a2..df8e24a 100644 --- a/apps/react/demo/package.json +++ b/apps/react/demo/package.json @@ -9,17 +9,19 @@ "lint": "next lint" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "@copilotkitnext/agent": "workspace:*", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/react": "workspace:*", "@copilotkitnext/runtime": "workspace:*", "@copilotkitnext/shared": "workspace:*", + "@modelcontextprotocol/sdk": "^1.18.2", "hono": "^4.6.13", "next": "15.4.4", "openai": "^5.9.0", "react": "19.1.0", "react-dom": "19.1.0", + "rxjs": "^7.8.1", "zod": "^3.25.75" }, "devDependencies": { diff --git a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts index 38bb58d..ea82346 100644 --- a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts +++ b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts @@ -1,6 +1,7 @@ import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; import { handle } from "hono/vercel"; import { BasicAgent } from "@copilotkitnext/agent"; +import { MCPAppsExtensionMiddleware } from "@/lib/mcp-apps-extension-middleware"; const determineModel = () => { if (process.env.OPENAI_API_KEY?.trim()) { @@ -19,7 +20,11 @@ const agent = new BasicAgent({ model: determineModel(), prompt: "You are a helpful AI assistant.", temperature: 0.7, -}); +}).use(new MCPAppsExtensionMiddleware({ + mcpServers: [ + // Example: { type: "sse", url: "http://localhost:3001/sse" } + ], +})); const honoRuntime = new CopilotRuntime({ agents: { diff --git a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts new file mode 100644 index 0000000..2392216 --- /dev/null +++ b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts @@ -0,0 +1,543 @@ +import { Middleware, RunAgentInput, AbstractAgent, BaseEvent, Tool, EventType, Message, ToolCall, ToolCallResultEvent, ActivitySnapshotEvent, RunStartedEvent, RunFinishedEvent } from "@ag-ui/client"; +import { Observable, from, switchMap } from "rxjs"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { randomUUID } from "crypto"; + +/** + * Proxied MCP request structure from the frontend iframe + */ +export interface ProxiedMCPRequest { + /** The MCP server URL to connect to */ + serverUrl: string; + /** The MCP server transport type */ + serverType: "http" | "sse"; + /** The JSON-RPC method to call */ + method: string; + /** The JSON-RPC params */ + params?: Record; +} + +/** + * Extract EventWithState type from Middleware.runNextWithState return type + */ +type ExtractObservableType = T extends Observable ? U : never; +type RunNextWithStateReturn = ReturnType; +export type EventWithState = ExtractObservableType; + +/** + * UI Tool with its source server config and resource URI + */ +interface UIToolInfo { + tool: Tool; + serverConfig: MCPClientConfig; + resourceUri: string; +} + +/** + * MCP Client configuration for HTTP transport + */ +export interface MCPClientConfigHTTP { + type: "http"; + url: string; +} + +/** + * MCP Client configuration for SSE transport + */ +export interface MCPClientConfigSSE { + type: "sse"; + url: string; + headers?: Record; +} + +/** + * MCP Client configuration + */ +export type MCPClientConfig = MCPClientConfigHTTP | MCPClientConfigSSE; + +/** + * Configuration for MCPAppsExtensionMiddleware + */ +export interface MCPAppsExtensionMiddlewareConfig { + /** + * List of MCP server configurations + */ + mcpServers?: MCPClientConfig[]; +} + +/** + * Check if a tool has a UI resource attached (per SEP-1865) + */ +function hasUIResource(tool: { _meta?: Record }): boolean { + return typeof tool._meta?.["ui/resourceUri"] === "string"; +} + +/** + * Extended tool type that includes MCP Apps metadata + */ +export interface MCPAppTool extends Tool { + /** UI resource URI from SEP-1865 */ + uiResourceUri?: string; +} + +/** + * Convert MCP tool to AG-UI tool format, preserving UI resource info + */ +function convertMCPToolToAGUITool(mcpTool: { + name: string; + description?: string; + inputSchema?: Record; + _meta?: Record; +}): Tool { + const tool: Tool = { + name: mcpTool.name, + description: mcpTool.description || "", + parameters: mcpTool.inputSchema || { type: "object", properties: {} }, + }; + + // Store UI resource URI in the description for now + // TODO: Once AG-UI Tool type supports _meta, use that instead + const uiResourceUri = mcpTool._meta?.["ui/resourceUri"]; + if (typeof uiResourceUri === "string") { + tool.description = `${tool.description}\n[UI Resource: ${uiResourceUri}]`; + } + + return tool; +} + +/** + * MCP Apps Extension middleware - fetches UI-enabled tools from MCP servers. + */ +export class MCPAppsExtensionMiddleware extends Middleware { + private config: MCPAppsExtensionMiddlewareConfig; + /** Map of tool name -> server config for UI tools */ + private uiToolsMap: Map = new Map(); + + constructor(config: MCPAppsExtensionMiddlewareConfig = {}) { + super(); + this.config = config; + } + + run(input: RunAgentInput, next: AbstractAgent): Observable { + // Check for proxied MCP request mode + const proxiedRequest = input.forwardedProps?.__proxiedMCPRequest as ProxiedMCPRequest | undefined; + if (proxiedRequest) { + return this.handleProxiedMCPRequest(input.runId, proxiedRequest); + } + + // If no MCP servers configured, pass through using runNextWithState + if (!this.config.mcpServers?.length) { + return this.processStream(this.runNextWithState(input, next), new Map()); + } + + // Fetch UI tools from MCP servers and inject them + return from(this.fetchUITools()).pipe( + switchMap((uiToolInfos) => { + // Build map of tool name -> UIToolInfo + const uiToolsMap = new Map(); + for (const info of uiToolInfos) { + uiToolsMap.set(info.tool.name, info); + } + + // Merge UI tools with existing input tools + const enhancedInput: RunAgentInput = { + ...input, + tools: [...input.tools, ...uiToolInfos.map((info) => info.tool)], + }; + // Use runNextWithState to get state with each event + return this.processStream(this.runNextWithState(enhancedInput, next), uiToolsMap); + }) + ); + } + + /** + * Handle a proxied MCP request from the frontend iframe. + * This bypasses the normal agent flow and directly executes the MCP request. + */ + private handleProxiedMCPRequest(runId: string, request: ProxiedMCPRequest): Observable { + return new Observable((subscriber) => { + // Emit RunStarted + const runStartedEvent: RunStartedEvent = { + type: EventType.RUN_STARTED, + runId, + threadId: runId, + }; + subscriber.next(runStartedEvent); + + // Execute the MCP request + this.executeMCPRequest(request) + .then((result) => { + // Emit RunFinished with the MCP result + const runFinishedEvent: RunFinishedEvent = { + type: EventType.RUN_FINISHED, + runId, + threadId: runId, + result, + }; + subscriber.next(runFinishedEvent); + subscriber.complete(); + }) + .catch((error) => { + // Emit RunFinished with error + const runFinishedEvent: RunFinishedEvent = { + type: EventType.RUN_FINISHED, + runId, + threadId: runId, + result: { error: String(error) }, + }; + subscriber.next(runFinishedEvent); + subscriber.complete(); + }); + }); + } + + /** + * Execute a generic MCP request (tools/call, resources/read, etc.) + */ + private async executeMCPRequest(request: ProxiedMCPRequest): Promise { + let transport; + + if (request.serverType === "sse") { + transport = new SSEClientTransport(new URL(request.serverUrl)); + } else { + transport = new StreamableHTTPClientTransport(new URL(request.serverUrl)); + } + + const client = new Client( + { name: "mcp-apps-middleware", version: "1.0.0" }, + { + capabilities: { + extensions: { + "io.modelcontextprotocol/ui": { + mimeTypes: ["text/html+mcp"], + }, + }, + }, + } + ); + + try { + await client.connect(transport); + + // Per SEP-1865: Forward any method that doesn't start with "ui/" + // Methods starting with "ui/" are handled by the host, not the MCP server + switch (request.method) { + case "tools/call": + return await client.callTool(request.params as { name: string; arguments?: Record }); + case "resources/read": + return await client.readResource(request.params as { uri: string }); + case "notifications/message": + // notifications/message is a one-way notification (no response expected) + await client.notification({ method: "notifications/message", params: request.params }); + return { success: true }; + case "ping": + return await client.ping(); + default: + throw new Error(`MCP method not allowed for UI proxy: ${request.method}`); + } + } finally { + await client.close(); + } + } + + /** + * Process the event stream, holding back RunFinished events until either: + * a) Another event comes -> flush the held RunFinished immediately + * b) Stream ends -> do special processing, then flush RunFinished and complete + */ + private processStream( + source: Observable, + uiToolsMap: Map + ): Observable { + return new Observable((subscriber) => { + let heldRunFinished: EventWithState | null = null; + let isProcessing = false; + + const subscription = source.subscribe({ + next: (eventWithState) => { + const event = eventWithState.event; + + // If we have a held RunFinished and a new event comes, flush it first + if (heldRunFinished) { + subscriber.next(heldRunFinished.event); + heldRunFinished = null; + } + + // If this is a RunFinished event, hold it back + if (event.type === EventType.RUN_FINISHED) { + heldRunFinished = eventWithState; + } else { + subscriber.next(event); + } + }, + error: (err) => { + // On error, flush any held event and propagate error + if (heldRunFinished) { + subscriber.next(heldRunFinished.event); + heldRunFinished = null; + } + subscriber.error(err); + }, + complete: async () => { + // Stream ended - do special processing if we have a held RunFinished + if (heldRunFinished && !isProcessing) { + isProcessing = true; + + try { + // Find tool calls that don't have a corresponding result message + const pendingToolCalls = this.findPendingToolCalls(heldRunFinished.messages); + + // Filter for UI tool calls (tools we injected from MCP servers) + const pendingUIToolCalls = pendingToolCalls.filter((tc) => + uiToolsMap.has(tc.function.name) + ); + + // Execute pending UI tool calls and emit results + for (const toolCall of pendingUIToolCalls) { + const toolInfo = uiToolsMap.get(toolCall.function.name)!; + try { + const args = JSON.parse(toolCall.function.arguments || "{}"); + const mcpResult = await this.executeToolCall(toolInfo.serverConfig, toolCall.function.name, args); + + // Fetch the UI resource + const resource = await this.readResource(toolInfo.serverConfig, toolInfo.resourceUri); + + // Emit tool result event + const resultEvent: ToolCallResultEvent = { + type: EventType.TOOL_CALL_RESULT, + messageId: randomUUID(), + toolCallId: toolCall.id, + content: this.extractTextContent(mcpResult), + }; + subscriber.next(resultEvent); + + // Emit activity snapshot with full MCP result and resource + const activityEvent: ActivitySnapshotEvent = { + type: EventType.ACTIVITY_SNAPSHOT, + messageId: randomUUID(), + activityType: "MCP-APPS-EXTENSION", + content: { result: mcpResult, resource }, + replace: true, + }; + subscriber.next(activityEvent); + } catch (error) { + console.error(`Failed to execute UI tool call ${toolCall.function.name}:`, error); + // Emit error result + const errorResult: ToolCallResultEvent = { + type: EventType.TOOL_CALL_RESULT, + messageId: randomUUID(), + toolCallId: toolCall.id, + content: JSON.stringify({ error: String(error) }), + }; + subscriber.next(errorResult); + } + } + + subscriber.next(heldRunFinished.event); + } finally { + heldRunFinished = null; + isProcessing = false; + } + } + subscriber.complete(); + }, + }); + + return () => subscription.unsubscribe(); + }); + } + + /** + * Execute a tool call on the MCP server and return the raw result + */ + private async executeToolCall( + serverConfig: MCPClientConfig, + toolName: string, + args: Record + ): Promise { + let transport; + + if (serverConfig.type === "sse") { + transport = new SSEClientTransport(new URL(serverConfig.url)); + } else { + transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); + } + + const client = new Client( + { name: "mcp-apps-middleware", version: "1.0.0" }, + { + capabilities: { + extensions: { + "io.modelcontextprotocol/ui": { + mimeTypes: ["text/html+mcp"], + }, + }, + }, + } + ); + + try { + await client.connect(transport); + + const result = await client.callTool({ + name: toolName, + arguments: args, + }); + + return result; + } finally { + await client.close(); + } + } + + /** + * Read a UI resource from the MCP server + */ + private async readResource( + serverConfig: MCPClientConfig, + resourceUri: string + ): Promise { + let transport; + + if (serverConfig.type === "sse") { + transport = new SSEClientTransport(new URL(serverConfig.url)); + } else { + transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); + } + + const client = new Client( + { name: "mcp-apps-middleware", version: "1.0.0" }, + { + capabilities: { + extensions: { + "io.modelcontextprotocol/ui": { + mimeTypes: ["text/html+mcp"], + }, + }, + }, + } + ); + + try { + await client.connect(transport); + + const result = await client.readResource({ + uri: resourceUri, + }); + + // Return the first content item (the UI resource) + return result.contents[0]; + } finally { + await client.close(); + } + } + + /** + * Extract text content from MCP result, fallback to JSON stringified content + */ + private extractTextContent(mcpResult: unknown): string { + const result = mcpResult as { content?: unknown }; + if (Array.isArray(result.content)) { + const textContent = result.content + .filter((c): c is { type: "text"; text: string } => + c && typeof c === "object" && c.type === "text" && typeof c.text === "string" + ) + .map((c) => c.text) + .join("\n"); + return textContent || JSON.stringify(result.content); + } + return JSON.stringify(result.content); + } + + /** + * Find tool calls that don't have a corresponding result (role: "tool") message + */ + private findPendingToolCalls(messages: Message[]): ToolCall[] { + // Collect all tool calls from assistant messages + const allToolCalls: ToolCall[] = []; + for (const message of messages) { + if (message.role === "assistant" && "toolCalls" in message && message.toolCalls) { + allToolCalls.push(...message.toolCalls); + } + } + + // Collect all tool call IDs that have results + const resolvedToolCallIds = new Set(); + for (const message of messages) { + if (message.role === "tool" && "toolCallId" in message) { + resolvedToolCallIds.add(message.toolCallId); + } + } + + // Return tool calls that don't have results + return allToolCalls.filter((tc) => !resolvedToolCallIds.has(tc.id)); + } + + /** + * Connect to all configured MCP servers and fetch tools with UI resources + */ + private async fetchUITools(): Promise { + const allUITools: UIToolInfo[] = []; + + for (const serverConfig of this.config.mcpServers || []) { + try { + const tools = await this.fetchToolsFromServer(serverConfig); + allUITools.push(...tools); + } catch (error) { + console.error(`Failed to fetch tools from MCP server ${serverConfig.url}:`, error); + } + } + + return allUITools; + } + + /** + * Connect to a single MCP server and fetch its UI-enabled tools + */ + private async fetchToolsFromServer(serverConfig: MCPClientConfig): Promise { + let transport; + + if (serverConfig.type === "sse") { + transport = new SSEClientTransport(new URL(serverConfig.url)); + } else { + transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); + } + + const client = new Client( + { name: "mcp-apps-middleware", version: "1.0.0" }, + { + capabilities: { + // Advertise MCP Apps UI support per SEP-1865 + extensions: { + "io.modelcontextprotocol/ui": { + mimeTypes: ["text/html+mcp"], + }, + }, + }, + } + ); + + try { + await client.connect(transport); + + // Fetch tools from the server + const response = await client.listTools(); + + // Filter for tools with UI resources and convert to AG-UI format with server config + const uiTools = response.tools + .filter(hasUIResource) + .map((mcpTool) => ({ + tool: convertMCPToolToAGUITool(mcpTool), + serverConfig, + resourceUri: mcpTool._meta!["ui/resourceUri"] as string, + })); + + return uiTools; + } finally { + // Always close the connection + await client.close(); + } + } +} diff --git a/packages/agent/package.json b/packages/agent/package.json index 950f41f..21894a0 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -36,7 +36,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "@ai-sdk/anthropic": "^2.0.22", "@ai-sdk/google": "^2.0.17", "@ai-sdk/mcp": "^0.0.8", diff --git a/packages/angular/package.json b/packages/angular/package.json index ef27e9f..d81f42b 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -31,8 +31,8 @@ "test:watch": "vitest --watch" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", - "@ag-ui/core": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/core": "0.0.42-alpha.3", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "clsx": "^2.1.1", diff --git a/packages/core/package.json b/packages/core/package.json index 58b2ba7..1755eac 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,7 +36,7 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "@copilotkitnext/shared": "workspace:*", "rxjs": "7.8.1", "zod": "^3.25.75", diff --git a/packages/react/package.json b/packages/react/package.json index a161eba..ac255ad 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -53,8 +53,8 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", - "@ag-ui/core": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/core": "0.0.42-alpha.3", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "@copilotkitnext/web-inspector": "workspace:*", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index ccb7412..340776b 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -44,9 +44,9 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", - "@ag-ui/core": "0.0.42-alpha.1", - "@ag-ui/encoder": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/core": "0.0.42-alpha.3", + "@ag-ui/encoder": "0.0.42-alpha.3", "@copilotkitnext/shared": "workspace:*", "cors": "^2.8.5", "express": "^4.21.2", diff --git a/packages/shared/package.json b/packages/shared/package.json index 1b8d999..9931398 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -31,7 +31,7 @@ "typescript": "5.8.2" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "uuid": "^11.1.0", "partial-json": "^0.1.7" }, diff --git a/packages/sqlite-runner/package.json b/packages/sqlite-runner/package.json index 61ae8e0..1660407 100644 --- a/packages/sqlite-runner/package.json +++ b/packages/sqlite-runner/package.json @@ -37,7 +37,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "@copilotkitnext/runtime": "workspace:*", "rxjs": "7.8.1" }, diff --git a/packages/web-inspector/package.json b/packages/web-inspector/package.json index 67db54c..02024d9 100644 --- a/packages/web-inspector/package.json +++ b/packages/web-inspector/package.json @@ -25,7 +25,7 @@ "clean": "rm -rf dist src/styles/generated.css" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.1", + "@ag-ui/client": "0.0.42-alpha.3", "@copilotkitnext/core": "workspace:*", "lit": "^3.2.0", "lucide": "^0.525.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebee60e..264790c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 8.6.14(@types/react@19.1.0)(storybook@8.6.14(prettier@3.6.0)) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.5 - version: 1.0.6(webpack@5.100.0(@swc/core@1.12.11)) + version: 1.0.6(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) '@storybook/react-webpack5': specifier: ^8 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) prettier: specifier: ^3.6.0 version: 3.6.0 @@ -74,7 +74,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^18.2.0 - version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) + version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 version: 18.2.20(chokidar@3.6.0) @@ -175,7 +175,7 @@ importers: version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 - version: 18.2.20 + version: 18.2.20(chokidar@3.6.0) '@angular/compiler-cli': specifier: ^18.2.0 version: 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) @@ -269,8 +269,8 @@ importers: apps/react/demo: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/agent': specifier: workspace:* version: link:../../../packages/agent @@ -286,6 +286,9 @@ importers: '@copilotkitnext/shared': specifier: workspace:* version: link:../../../packages/shared + '@modelcontextprotocol/sdk': + specifier: ^1.18.2 + version: 1.18.2 hono: specifier: ^4.6.13 version: 4.8.10 @@ -301,6 +304,9 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + rxjs: + specifier: ^7.8.1 + version: 7.8.1 zod: specifier: ^3.25.75 version: 3.25.75 @@ -346,7 +352,7 @@ importers: version: 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/nextjs': specifier: ^8 - version: 8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) + version: 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) '@storybook/react': specifier: ^8 version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) @@ -400,8 +406,8 @@ importers: packages/agent: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@ai-sdk/anthropic': specifier: ^2.0.22 version: 2.0.23(zod@3.25.75) @@ -455,11 +461,11 @@ importers: packages/angular: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@ag-ui/core': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -597,8 +603,8 @@ importers: packages/core: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared @@ -710,11 +716,11 @@ importers: packages/react: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@ag-ui/core': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -840,14 +846,14 @@ importers: packages/runtime: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@ag-ui/core': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@ag-ui/encoder': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared @@ -901,8 +907,8 @@ importers: packages/shared: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 partial-json: specifier: ^0.1.7 version: 0.1.7 @@ -932,8 +938,8 @@ importers: packages/sqlite-runner: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/runtime': specifier: workspace:* version: link:../runtime @@ -974,8 +980,8 @@ importers: packages/web-inspector: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.1 - version: 0.0.42-alpha.1 + specifier: 0.0.42-alpha.3 + version: 0.0.42-alpha.3 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -1025,18 +1031,27 @@ packages: '@ag-ui/client@0.0.42-alpha.1': resolution: {integrity: sha512-vnaCclWRpVtsuidUF1X08XGRc1NDp0z15p50DWPXSgD2fXnsXC7DCI3IZEN5B9ExhY0CqlhdngM4qYtl3Fr2Uw==} + '@ag-ui/client@0.0.42-alpha.3': + resolution: {integrity: sha512-VXJlCJ0XAYqBB5+RaKXs5lHrwVqm5pXEjwdPhBSjwyOWD04bTkIxPrjtzdZFyT/6mrJnnqcd4zywHSEsCtvOnQ==} + '@ag-ui/core@0.0.36': resolution: {integrity: sha512-uYUrzw6uxuw4qVQ61mdSeiG0mFh2n/VAWmWsWzwETDuhqJZT7rFmd07IajcFWcyItMr1wjqxFDdlklucAyEYNA==} '@ag-ui/core@0.0.42-alpha.1': resolution: {integrity: sha512-jnGJFax6YG6pjqDcIC1RQajjFke8tiLaNF4GmRTUp5vz0/MN+8FsZF/Df6K81pzuPu+FueUmr62TprDO5qo8rw==} + '@ag-ui/core@0.0.42-alpha.3': + resolution: {integrity: sha512-n6/ExW3yct8N+vJYS3YrSah9BKUWi8cEugwJobGr8mOXVVEK8+pHKYLf2UAWsvz6L+mTYoYyn5LD4faOr51DBQ==} + '@ag-ui/encoder@0.0.36': resolution: {integrity: sha512-p8UNh6a77G/oe/4EZmwkTeYCN/5SnqSY2Cz8f8psZpk4LKzzrPkRNykrUAIBsi1wMp50/VQiM27oTRaade/Qkw==} '@ag-ui/encoder@0.0.42-alpha.1': resolution: {integrity: sha512-GwTh81t3fDoN7BN7VOsxPtcAvcPhHjn1aMkYUIm1cQ+s5sU9+JTlhgNemFE/N6T5ssddnfX+6ntOWdB8JWtXkA==} + '@ag-ui/encoder@0.0.42-alpha.3': + resolution: {integrity: sha512-slpJOFlWFNTt3iXV0WiGQjqJBL2SFVuj0m7ddDEEht9SXdA3xlx6x3e3D/on3sVn06nFUTTR5MMlAD8ff1nv0Q==} + '@ag-ui/langgraph@0.0.11': resolution: {integrity: sha512-3xUkaOelnpQ5tbsbuoOTin71tTgWEN0GDZBjGs/7xAwly2Dn4fahbBAoscXullO/pH9kTGGgbuJ0rWDUgo6fKQ==} @@ -1046,6 +1061,9 @@ packages: '@ag-ui/proto@0.0.42-alpha.1': resolution: {integrity: sha512-rOPWdDv40tu9I5qonxj3diGYsN8aX9qIDV4Ef5GQN6UwbzDAx3MQI7AmwgcoPuyRQFMtGiepyDpZZN9RETGDfw==} + '@ag-ui/proto@0.0.42-alpha.3': + resolution: {integrity: sha512-CKK+SB4FbrZt/lE54Dh/a5lplyFkVwf1YJykazHSb6iKZT65nFARt1Z9tWfR56zNLKX0gJ8/Yq30/fuChcFpZw==} + '@ai-sdk/anthropic@2.0.23': resolution: {integrity: sha512-ZEBiiv1UhjGjBwUU63pFhLK5LCSlNDb1idY9K1oZHm5/Fda1cuTojf32tOp0opH0RPbPAN/F8fyyNjbU33n9Kw==} engines: {node: '>=18'} @@ -12592,6 +12610,19 @@ snapshots: uuid: 11.1.0 zod: 3.25.75 + '@ag-ui/client@0.0.42-alpha.3': + dependencies: + '@ag-ui/core': 0.0.42-alpha.3 + '@ag-ui/encoder': 0.0.42-alpha.3 + '@ag-ui/proto': 0.0.42-alpha.3 + '@types/uuid': 10.0.0 + compare-versions: 6.1.1 + fast-json-patch: 3.1.1 + rxjs: 7.8.1 + untruncate-json: 0.0.1 + uuid: 11.1.0 + zod: 3.25.75 + '@ag-ui/core@0.0.36': dependencies: rxjs: 7.8.1 @@ -12602,6 +12633,11 @@ snapshots: rxjs: 7.8.1 zod: 3.25.75 + '@ag-ui/core@0.0.42-alpha.3': + dependencies: + rxjs: 7.8.1 + zod: 3.25.75 + '@ag-ui/encoder@0.0.36': dependencies: '@ag-ui/core': 0.0.36 @@ -12612,6 +12648,11 @@ snapshots: '@ag-ui/core': 0.0.42-alpha.1 '@ag-ui/proto': 0.0.42-alpha.1 + '@ag-ui/encoder@0.0.42-alpha.3': + dependencies: + '@ag-ui/core': 0.0.42-alpha.3 + '@ag-ui/proto': 0.0.42-alpha.3 + '@ag-ui/langgraph@0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ag-ui/client': 0.0.36 @@ -12639,6 +12680,12 @@ snapshots: '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1 + '@ag-ui/proto@0.0.42-alpha.3': + dependencies: + '@ag-ui/core': 0.0.42-alpha.3 + '@bufbuild/protobuf': 2.6.0 + '@protobuf-ts/protoc': 2.11.1 + '@ai-sdk/anthropic@2.0.23(zod@3.25.75)': dependencies: '@ai-sdk/provider': 2.0.0 @@ -12728,6 +12775,7 @@ snapshots: rxjs: 7.8.1 transitivePeerDependencies: - chokidar + optional: true '@angular-devkit/architect@0.1902.15(chokidar@4.0.3)': dependencies: @@ -12736,7 +12784,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) @@ -12795,11 +12843,11 @@ snapshots: tslib: 2.6.3 typescript: 5.4.5 watchpack: 2.4.1 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) @@ -12912,9 +12960,9 @@ snapshots: '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 @@ -12999,7 +13047,7 @@ snapshots: dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) rxjs: 7.8.1 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) transitivePeerDependencies: - chokidar @@ -13016,7 +13064,7 @@ snapshots: '@angular-devkit/build-webpack@0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': dependencies: - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) rxjs: 7.8.1 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) @@ -13044,6 +13092,7 @@ snapshots: source-map: 0.7.4 optionalDependencies: chokidar: 4.0.3 + optional: true '@angular-devkit/core@19.2.15(chokidar@4.0.3)': dependencies: @@ -13056,16 +13105,6 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics@18.2.20': - dependencies: - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - jsonc-parser: 3.3.1 - magic-string: 0.30.11 - ora: 5.4.1 - rxjs: 7.8.1 - transitivePeerDependencies: - - chokidar - '@angular-devkit/schematics@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/core': 18.2.20(chokidar@3.6.0) @@ -13215,7 +13254,7 @@ snapshots: '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 @@ -13264,30 +13303,6 @@ snapshots: optionalDependencies: parse5: 7.3.0 - '@angular/cli@18.2.20': - dependencies: - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular-devkit/schematics': 18.2.20 - '@inquirer/prompts': 5.3.8 - '@listr2/prompt-adapter-inquirer': 2.0.15(@inquirer/prompts@5.3.8) - '@schematics/angular': 18.2.20 - '@yarnpkg/lockfile': 1.1.0 - ini: 4.1.3 - jsonc-parser: 3.3.1 - listr2: 8.2.4 - npm-package-arg: 11.0.3 - npm-pick-manifest: 9.1.0 - pacote: 18.0.6 - resolve: 1.22.8 - semver: 7.6.3 - symbol-observable: 4.0.0 - yargs: 17.7.2 - transitivePeerDependencies: - - bluebird - - chokidar - - supports-color - '@angular/cli@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) @@ -16532,7 +16547,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.44.0 @@ -16542,10 +16557,10 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.3.2 source-map: 0.7.4 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: type-fest: 4.41.0 - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)) + webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) webpack-hot-middleware: 2.26.1 '@polka/url@1.0.0-next.29': {} @@ -17049,14 +17064,6 @@ snapshots: '@rushstack/eslint-patch@1.12.0': {} - '@schematics/angular@18.2.20': - dependencies: - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular-devkit/schematics': 18.2.20 - jsonc-parser: 3.3.1 - transitivePeerDependencies: - - chokidar - '@schematics/angular@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/core': 18.2.20(chokidar@3.6.0) @@ -17425,10 +17432,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.6.14(prettier@3.6.0) - '@storybook/addon-webpack5-compiler-swc@1.0.6(webpack@5.100.0(@swc/core@1.12.11))': + '@storybook/addon-webpack5-compiler-swc@1.0.6(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': dependencies: '@swc/core': 1.12.11 - swc-loader: 0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) + swc-loader: 0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) transitivePeerDependencies: - '@swc/helpers' - webpack @@ -17469,7 +17476,7 @@ snapshots: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: '@angular/animations': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) - '@angular/cli': 18.2.20 + '@angular/cli': 18.2.20(chokidar@3.6.0) zone.js: 0.14.10 transitivePeerDependencies: - '@rspack/core' @@ -17523,7 +17530,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 @@ -17531,23 +17538,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.100.0(@swc/core@1.12.11) - webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17611,7 +17618,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/nextjs@8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': + '@storybook/nextjs@8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) @@ -17626,30 +17633,30 @@ snapshots: '@babel/preset-react': 7.27.1(@babel/core@7.28.0) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) '@babel/runtime': 7.27.6 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 - babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)) - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) + babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) find-up: 5.0.0 image-size: 1.2.1 loader-utils: 3.3.1 next: 15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11)) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) pnp-webpack-plugin: 1.7.0(typescript@5.8.2) postcss: 8.5.6 - postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)) + sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) styled-jsx: 5.1.7(@babel/core@7.28.0)(react@18.3.1) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -17657,7 +17664,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -17676,11 +17683,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) '@types/semver': 7.7.0 find-up: 5.0.0 magic-string: 0.30.17 @@ -17691,7 +17698,7 @@ snapshots: semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) tsconfig-paths: 4.2.0 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: typescript: 5.8.2 transitivePeerDependencies: @@ -17706,7 +17713,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': dependencies: debug: 4.4.1(supports-color@5.5.0) endent: 2.1.0 @@ -17716,7 +17723,7 @@ snapshots: react-docgen-typescript: 2.4.0(typescript@5.8.2) tslib: 2.8.1 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) transitivePeerDependencies: - supports-color @@ -17726,10 +17733,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -18787,7 +18794,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: @@ -19217,12 +19224,12 @@ snapshots: schema-utils: 4.3.2 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)): + babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@babel/core': 7.28.0 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.10): dependencies: @@ -19928,19 +19935,6 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - css-loader@6.11.0(webpack@5.100.0(@swc/core@1.12.11)): - dependencies: - icss-utils: 5.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) - postcss-modules-scope: 3.2.1(postcss@8.5.6) - postcss-modules-values: 4.0.0(postcss@8.5.6) - postcss-value-parser: 4.2.0 - semver: 7.7.2 - optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) - css-loader@7.1.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) @@ -21376,7 +21370,7 @@ snapshots: typescript: 5.4.5 webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -21391,7 +21385,7 @@ snapshots: semver: 7.7.2 tapable: 2.2.2 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) form-data-encoder@1.7.2: {} @@ -21926,7 +21920,7 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)): + html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21934,17 +21928,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) - - html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): - dependencies: - '@types/html-minifier-terser': 6.1.0 - html-minifier-terser: 6.1.0 - lodash: 4.17.21 - pretty-error: 4.0.0 - tapable: 2.2.2 - optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) optional: true htmlparser2@6.1.0: @@ -23911,7 +23895,7 @@ snapshots: transitivePeerDependencies: - supports-color - node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -23938,7 +23922,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) node-releases@2.0.19: {} @@ -24532,14 +24516,14 @@ snapshots: transitivePeerDependencies: - typescript - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.2) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) transitivePeerDependencies: - typescript @@ -25385,12 +25369,12 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)): + sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.90.0 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) sass-loader@16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: @@ -26057,10 +26041,6 @@ snapshots: dependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - style-loader@3.3.4(webpack@5.100.0(@swc/core@1.12.11)): - dependencies: - webpack: 5.100.0(@swc/core@1.12.11) - style-loader@4.0.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) @@ -26154,11 +26134,11 @@ snapshots: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swc-loader@0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): + swc-loader@0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@swc/core': 1.12.11 '@swc/counter': 0.1.3 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) symbol-observable@4.0.0: {} @@ -26278,16 +26258,17 @@ snapshots: '@swc/core': 1.12.11 esbuild: 0.25.6 - terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): + terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: '@jridgewell/trace-mapping': 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: '@swc/core': 1.12.11 + esbuild: 0.25.6 terser@5.31.6: dependencies: @@ -27194,17 +27175,7 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - webpack-dev-middleware@6.1.3(webpack@5.100.0(@swc/core@1.12.11)): - dependencies: - colorette: 2.0.20 - memfs: 3.5.3 - mime-types: 2.1.35 - range-parser: 1.2.1 - schema-utils: 4.3.2 - optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) - - webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)): + webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: colorette: 2.0.20 memfs: 4.36.3 @@ -27213,7 +27184,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optional: true webpack-dev-middleware@7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): @@ -27227,7 +27198,7 @@ snapshots: optionalDependencies: webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)): + webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -27255,10 +27226,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)) + webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) ws: 8.18.3 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11) + webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) transitivePeerDependencies: - bufferutil - debug @@ -27325,16 +27296,16 @@ snapshots: optionalDependencies: html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)) webpack-virtual-modules@0.6.2: {} - webpack@5.100.0(@swc/core@1.12.11): + webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -27358,7 +27329,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -27366,19 +27337,17 @@ snapshots: - esbuild - uglify-js - webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6): + webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0): dependencies: - '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 - acorn-import-phases: 1.0.3(acorn@8.15.0) + acorn-import-attributes: 1.9.5(acorn@8.15.0) browserslist: 4.25.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.2 + enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -27388,9 +27357,9 @@ snapshots: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.3.2 + schema-utils: 3.3.0 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -27398,7 +27367,7 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0): + webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6): dependencies: '@types/estree': 1.0.8 '@webassemblyjs/ast': 1.14.1 @@ -27420,7 +27389,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: From bf0bdf044c2ab730c847b158eae546bd2ebbc54a Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Thu, 4 Dec 2025 14:40:27 +0100 Subject: [PATCH 02/11] add mcp apps extension middleware --- apps/react/demo/package.json | 8 +- .../mcp-apps-extension-middleware.test.ts | 2144 +++++++++++++++++ .../demo/src/lib/__tests__/test-utils.ts | 403 ++++ apps/react/demo/vitest.config.mjs | 14 + pnpm-lock.yaml | 435 +++- 5 files changed, 2890 insertions(+), 114 deletions(-) create mode 100644 apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts create mode 100644 apps/react/demo/src/lib/__tests__/test-utils.ts create mode 100644 apps/react/demo/vitest.config.mjs diff --git a/apps/react/demo/package.json b/apps/react/demo/package.json index df8e24a..d474764 100644 --- a/apps/react/demo/package.json +++ b/apps/react/demo/package.json @@ -6,7 +6,10 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "test": "vitest", + "test:watch": "vitest --watch", + "test:coverage": "vitest --coverage" }, "dependencies": { "@ag-ui/client": "0.0.42-alpha.3", @@ -34,6 +37,7 @@ "eslint": "^9", "eslint-config-next": "15.4.4", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^3.2.3" } } diff --git a/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts b/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts new file mode 100644 index 0000000..e34078c --- /dev/null +++ b/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts @@ -0,0 +1,2144 @@ +import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest"; +import { Observable } from "rxjs"; +import { EventType, BaseEvent, RunAgentInput } from "@ag-ui/client"; +import { + MCPAppsExtensionMiddleware, + MCPClientConfig, + ProxiedMCPRequest, +} from "../mcp-apps-extension-middleware"; +import { + MockAgent, + AsyncMockAgent, + ErrorMockAgent, + createRunAgentInput, + createRunStartedEvent, + createRunFinishedEvent, + createTextMessageStartEvent, + createTextMessageContentEvent, + createTextMessageEndEvent, + createMCPToolWithUI, + createMCPToolWithoutUI, + createMCPToolWithEmptyMeta, + createAssistantMessageWithToolCalls, + createToolResultMessage, + createAGUITool, + collectEvents, + createMCPToolCallResult, + createMCPResourceResult, +} from "./test-utils"; + +// Create mock functions that will be referenced in the mock factory +const mockConnect = vi.fn(); +const mockClose = vi.fn(); +const mockListTools = vi.fn(); +const mockCallTool = vi.fn(); +const mockReadResource = vi.fn(); +const mockNotification = vi.fn(); +const mockPing = vi.fn(); + +// Track Client constructor calls +const mockClientConstructorCalls: Array<{ clientInfo: unknown; options: unknown }> = []; + +// Mock the MCP SDK modules - using factory that returns a function returning our mock +vi.mock("@modelcontextprotocol/sdk/client/index.js", () => { + return { + Client: class MockClient { + connect = mockConnect; + close = mockClose; + listTools = mockListTools; + callTool = mockCallTool; + readResource = mockReadResource; + notification = mockNotification; + ping = mockPing; + + constructor(clientInfo: unknown, options: unknown) { + mockClientConstructorCalls.push({ clientInfo, options }); + } + }, + }; +}); + +vi.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({ + SSEClientTransport: vi.fn().mockImplementation(() => ({ type: "sse" })), +})); + +vi.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ + StreamableHTTPClientTransport: vi.fn().mockImplementation(() => ({ type: "http" })), +})); + +// Mock crypto.randomUUID +vi.mock("crypto", () => ({ + randomUUID: vi.fn(() => `mock-uuid-${Math.random().toString(36).substr(2, 9)}`), +})); + +describe("MCPAppsExtensionMiddleware", () => { + beforeEach(() => { + // Reset all mocks before each test + vi.clearAllMocks(); + + // Clear constructor calls tracking + mockClientConstructorCalls.length = 0; + + // Set default mock implementations + mockConnect.mockResolvedValue(undefined); + mockClose.mockResolvedValue(undefined); + mockListTools.mockResolvedValue({ tools: [] }); + mockCallTool.mockResolvedValue({ content: [] }); + mockReadResource.mockResolvedValue({ contents: [] }); + mockNotification.mockResolvedValue(undefined); + mockPing.mockResolvedValue({}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + // ============================================================================= + // 1. Constructor & Configuration Tests + // ============================================================================= + describe("Constructor & Configuration", () => { + it("creates instance with empty config", () => { + const middleware = new MCPAppsExtensionMiddleware(); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + + it("creates instance with empty object config", () => { + const middleware = new MCPAppsExtensionMiddleware({}); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + + it("creates instance with HTTP server config", () => { + const config = { + mcpServers: [{ type: "http" as const, url: "http://localhost:3000" }], + }; + const middleware = new MCPAppsExtensionMiddleware(config); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + + it("creates instance with SSE server config", () => { + const config = { + mcpServers: [{ type: "sse" as const, url: "http://localhost:3000/sse" }], + }; + const middleware = new MCPAppsExtensionMiddleware(config); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + + it("creates instance with SSE server config including headers", () => { + const config = { + mcpServers: [ + { + type: "sse" as const, + url: "http://localhost:3000/sse", + headers: { Authorization: "Bearer token" }, + }, + ], + }; + const middleware = new MCPAppsExtensionMiddleware(config); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + + it("creates instance with multiple server configs", () => { + const config = { + mcpServers: [ + { type: "http" as const, url: "http://localhost:3001" }, + { type: "sse" as const, url: "http://localhost:3002/sse" }, + ], + }; + const middleware = new MCPAppsExtensionMiddleware(config); + expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); + }); + }); + + // ============================================================================= + // 2. Pass-Through Behavior (No MCP Servers) + // ============================================================================= + describe("Pass-Through Behavior (No MCP Servers)", () => { + it("passes through when mcpServers is empty array", async () => { + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [] }); + const agent = new MockAgent([ + createRunStartedEvent(), + createRunFinishedEvent(), + ]); + + const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(events).toHaveLength(2); + expect(events[0].type).toBe(EventType.RUN_STARTED); + expect(events[1].type).toBe(EventType.RUN_FINISHED); + }); + + it("passes through when mcpServers is undefined", async () => { + const middleware = new MCPAppsExtensionMiddleware({}); + const agent = new MockAgent([ + createRunStartedEvent(), + createTextMessageStartEvent(), + createTextMessageContentEvent(), + createTextMessageEndEvent(), + createRunFinishedEvent(), + ]); + + const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(events.length).toBeGreaterThanOrEqual(2); + expect(events[0].type).toBe(EventType.RUN_STARTED); + expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + + it("events flow through unchanged when no servers configured", async () => { + const middleware = new MCPAppsExtensionMiddleware(); + const inputEvents = [ + createRunStartedEvent("run-1", "thread-1"), + createTextMessageStartEvent("msg-1"), + createTextMessageContentEvent("msg-1", "Hello World"), + createTextMessageEndEvent("msg-1"), + createRunFinishedEvent("run-1", "thread-1", { success: true }), + ]; + const agent = new MockAgent(inputEvents); + + const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); + + // The middleware uses runNextWithState which transforms chunks + expect(events.length).toBeGreaterThanOrEqual(2); + expect(events[0].type).toBe(EventType.RUN_STARTED); + }); + + it("observable completes correctly with no servers", async () => { + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + let completed = false; + await new Promise((resolve) => { + middleware.run(createRunAgentInput(), agent).subscribe({ + complete: () => { + completed = true; + resolve(); + }, + }); + }); + + expect(completed).toBe(true); + }); + + it("error propagation works with no servers", async () => { + const middleware = new MCPAppsExtensionMiddleware(); + const testError = new Error("Test error"); + const agent = new ErrorMockAgent(testError); + + let caughtError: Error | null = null; + await new Promise((resolve) => { + middleware.run(createRunAgentInput(), agent).subscribe({ + error: (err) => { + caughtError = err; + resolve(); + }, + complete: () => resolve(), + }); + }); + + expect(caughtError).toBe(testError); + }); + }); + + // ============================================================================= + // 3. Tool Discovery Tests + // ============================================================================= + describe("Tool Discovery", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + const sseServerConfig: MCPClientConfig = { type: "sse", url: "http://localhost:3001/sse" }; + + it("connects to MCP server with correct capabilities", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(mockClientConstructorCalls).toHaveLength(1); + expect(mockClientConstructorCalls[0].clientInfo).toEqual({ + name: "mcp-apps-middleware", + version: "1.0.0", + }); + expect(mockClientConstructorCalls[0].options).toMatchObject({ + capabilities: { + extensions: { + "io.modelcontextprotocol/ui": { + mimeTypes: ["text/html+mcp"], + }, + }, + }, + }); + }); + + it("calls listTools on connected client", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(mockConnect).toHaveBeenCalled(); + expect(mockListTools).toHaveBeenCalled(); + }); + + it("filters tools by ui/resourceUri presence", async () => { + mockListTools.mockResolvedValue({ + tools: [ + createMCPToolWithUI("ui-tool", "ui://server/dashboard"), + createMCPToolWithoutUI("non-ui-tool"), + createMCPToolWithEmptyMeta("meta-but-no-ui"), + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + // Agent should receive enhanced input with only the UI tool + expect(agent.runCalls).toHaveLength(1); + const enhancedTools = agent.runCalls[0].tools; + expect(enhancedTools).toHaveLength(1); + expect(enhancedTools[0].name).toBe("ui-tool"); + }); + + it("converts MCP tools to AG-UI Tool format correctly", async () => { + mockListTools.mockResolvedValue({ + tools: [ + { + name: "test-tool", + description: "Test tool description", + inputSchema: { type: "object", properties: { foo: { type: "string" } } }, + _meta: { "ui/resourceUri": "ui://server/test" }, + }, + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + const enhancedTools = agent.runCalls[0].tools; + expect(enhancedTools[0].name).toBe("test-tool"); + expect(enhancedTools[0].description).toContain("Test tool description"); + expect(enhancedTools[0].parameters).toEqual({ + type: "object", + properties: { foo: { type: "string" } }, + }); + }); + + it("stores ui/resourceUri in description", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard", "Original description")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + const enhancedTools = agent.runCalls[0].tools; + expect(enhancedTools[0].description).toContain("Original description"); + expect(enhancedTools[0].description).toContain("[UI Resource: ui://server/dashboard]"); + }); + + it("handles tools without _meta", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithoutUI("no-meta-tool")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + // No UI tools should be added + expect(agent.runCalls[0].tools).toHaveLength(0); + }); + + it("handles empty tools list from server", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(agent.runCalls[0].tools).toHaveLength(0); + }); + + it("handles server connection failures gracefully", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + mockConnect.mockRejectedValue(new Error("Connection failed")); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + // Should not throw, should continue + const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(events.length).toBeGreaterThanOrEqual(2); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Failed to fetch tools from MCP server"), + expect.any(Error) + ); + + consoleErrorSpy.mockRestore(); + }); + + it("closes client connection after fetching tools", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(mockClose).toHaveBeenCalled(); + }); + + it("works with HTTP transport", async () => { + const { StreamableHTTPClientTransport } = await import( + "@modelcontextprotocol/sdk/client/streamableHttp.js" + ); + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ + mcpServers: [{ type: "http", url: "http://localhost:3000" }], + }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000")); + }); + + it("works with SSE transport", async () => { + const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js"); + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ + mcpServers: [{ type: "sse", url: "http://localhost:3001/sse" }], + }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(SSEClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3001/sse")); + }); + + it("aggregates tools from multiple servers", async () => { + // We need to track which server each call is for + let callCount = 0; + mockListTools.mockImplementation(() => { + callCount++; + if (callCount === 1) { + return Promise.resolve({ + tools: [createMCPToolWithUI("tool-1", "ui://server1/tool1")], + }); + } + return Promise.resolve({ + tools: [createMCPToolWithUI("tool-2", "ui://server2/tool2")], + }); + }); + + const middleware = new MCPAppsExtensionMiddleware({ + mcpServers: [httpServerConfig, sseServerConfig], + }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(agent.runCalls[0].tools).toHaveLength(2); + expect(agent.runCalls[0].tools.map((t) => t.name)).toContain("tool-1"); + expect(agent.runCalls[0].tools.map((t) => t.name)).toContain("tool-2"); + }); + }); + + // ============================================================================= + // 4. Tool Injection Tests + // ============================================================================= + describe("Tool Injection", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("merges UI tools with existing input tools", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const existingTool = createAGUITool("existing-tool"); + const input = createRunAgentInput({ tools: [existingTool] }); + + await collectEvents(middleware.run(input, agent)); + + expect(agent.runCalls[0].tools).toHaveLength(2); + expect(agent.runCalls[0].tools[0].name).toBe("existing-tool"); + expect(agent.runCalls[0].tools[1].name).toBe("ui-tool"); + }); + + it("preserves original input tools", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const originalTools = [ + createAGUITool("tool-a", "Description A"), + createAGUITool("tool-b", "Description B"), + ]; + const input = createRunAgentInput({ tools: originalTools }); + + await collectEvents(middleware.run(input, agent)); + + const resultTools = agent.runCalls[0].tools; + expect(resultTools[0]).toEqual(originalTools[0]); + expect(resultTools[1]).toEqual(originalTools[1]); + }); + + it("passes enhanced input to next agent", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const input = createRunAgentInput({ + threadId: "custom-thread", + runId: "custom-run", + state: { key: "value" }, + }); + + await collectEvents(middleware.run(input, agent)); + + expect(agent.runCalls[0].threadId).toBe("custom-thread"); + expect(agent.runCalls[0].runId).toBe("custom-run"); + expect(agent.runCalls[0].state).toEqual({ key: "value" }); + expect(agent.runCalls[0].tools.length).toBe(1); + }); + }); + + // ============================================================================= + // 5. Event Stream Processing Tests + // ============================================================================= + describe("Event Stream Processing", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("emits non-RUN_FINISHED events immediately", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([ + createRunStartedEvent(), + createTextMessageStartEvent(), + createTextMessageContentEvent(), + createTextMessageEndEvent(), + createRunFinishedEvent(), + ]); + + const receivedEvents: BaseEvent[] = []; + await new Promise((resolve) => { + middleware.run(createRunAgentInput(), agent).subscribe({ + next: (event) => receivedEvents.push(event), + complete: () => resolve(), + }); + }); + + // First event should be RUN_STARTED + expect(receivedEvents[0].type).toBe(EventType.RUN_STARTED); + // Last event should be RUN_FINISHED + expect(receivedEvents[receivedEvents.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + + it("holds back RUN_FINISHED event until stream ends", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new AsyncMockAgent( + [createRunStartedEvent(), createRunFinishedEvent()], + 10 + ); + + const receivedEvents: BaseEvent[] = []; + let finishedReceived = false; + + await new Promise((resolve) => { + middleware.run(createRunAgentInput(), agent).subscribe({ + next: (event) => { + receivedEvents.push(event); + if (event.type === EventType.RUN_FINISHED) { + finishedReceived = true; + } + }, + complete: () => resolve(), + }); + }); + + expect(finishedReceived).toBe(true); + expect(receivedEvents[receivedEvents.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + + it("handles error events correctly", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const testError = new Error("Stream error"); + const agent = new ErrorMockAgent(testError); + + let caughtError: Error | null = null; + await new Promise((resolve) => { + middleware.run(createRunAgentInput(), agent).subscribe({ + error: (err) => { + caughtError = err; + resolve(); + }, + complete: () => resolve(), + }); + }); + + expect(caughtError).toBe(testError); + }); + + it("subscription cleanup works", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new AsyncMockAgent( + [ + createRunStartedEvent(), + createTextMessageStartEvent(), + createTextMessageContentEvent(), + createRunFinishedEvent(), + ], + 50 + ); + + let eventCount = 0; + const subscription = middleware.run(createRunAgentInput(), agent).subscribe({ + next: () => { + eventCount++; + if (eventCount === 2) { + subscription.unsubscribe(); + } + }, + }); + + // Wait a bit to ensure no more events are received after unsubscribe + await new Promise((resolve) => setTimeout(resolve, 200)); + + expect(eventCount).toBe(2); + }); + }); + + // ============================================================================= + // 6. Pending Tool Call Detection Tests + // ============================================================================= + describe("Pending Tool Call Detection", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("processes pending UI tool calls on stream completion", async () => { + const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Weather result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://weather/dashboard", "text/html+mcp", "Dashboard") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + // Create an assistant message with a tool call that won't have a result + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-weather", args: { city: "London" }, id: "tc-1" }, + ]); + + // Agent emits events but doesn't emit a tool result + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + // Set up input with the assistant message containing the tool call + const input = createRunAgentInput({ + messages: [assistantMsg], + }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should have emitted TOOL_CALL_RESULT and ACTIVITY_SNAPSHOT events + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + + expect(toolResultEvents.length).toBe(1); + expect(activityEvents.length).toBe(1); + }); + + it("identifies resolved tool calls (role: tool messages)", async () => { + const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + // Create assistant message with tool call AND a tool result message + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-weather", args: { city: "London" }, id: "tc-1" }, + ]); + const toolResultMsg = createToolResultMessage("tc-1", "Already resolved"); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const input = createRunAgentInput({ + messages: [assistantMsg, toolResultMsg], + }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should NOT emit additional TOOL_CALL_RESULT since it's already resolved + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(0); + }); + + it("handles empty message arrays", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/tool")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const input = createRunAgentInput({ messages: [] }); + const events = await collectEvents(middleware.run(input, agent)); + + // Should complete without errors + expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + + it("handles messages without tool calls", async () => { + mockListTools.mockResolvedValue({ + tools: [createMCPToolWithUI("ui-tool", "ui://server/tool")], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const input = createRunAgentInput({ + messages: [ + { id: "msg-1", role: "user", content: "Hello" }, + { id: "msg-2", role: "assistant", content: "Hi there" }, + ], + }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should complete without emitting tool results + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(0); + }); + + it("handles multiple tool calls per message", async () => { + const uiTool1 = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); + const uiTool2 = createMCPToolWithUI("ui-stocks", "ui://stocks/chart"); + mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://test", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-weather", args: {}, id: "tc-1" }, + { name: "ui-stocks", args: {}, id: "tc-2" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(2); + }); + }); + + // ============================================================================= + // 7. Tool Execution Tests + // ============================================================================= + describe("Tool Execution", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("passes correct tool name and arguments", async () => { + const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Sunny" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://weather/dashboard", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-weather", args: { city: "London", units: "metric" }, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + await collectEvents(middleware.run(input, agent)); + + expect(mockCallTool).toHaveBeenCalledWith({ + name: "ui-weather", + arguments: { city: "London", units: "metric" }, + }); + }); + + it("returns raw MCP result", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + + const mcpResult = createMCPToolCallResult([ + { type: "text", text: "First" }, + { type: "text", text: "Second" }, + ]); + mockCallTool.mockResolvedValue(mcpResult); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect(activityEvent).toBeDefined(); + expect((activityEvent as any).content.result).toEqual(mcpResult); + }); + + it("handles tool execution errors", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockRejectedValue(new Error("Execution failed")); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should emit error tool result + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(1); + expect((toolResultEvents[0] as any).content).toContain("error"); + + consoleErrorSpy.mockRestore(); + }); + + it("closes connection after execution", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + await collectEvents(middleware.run(input, agent)); + + // close should be called multiple times (once for listTools, once for callTool, once for readResource) + expect(mockClose).toHaveBeenCalled(); + }); + }); + + // ============================================================================= + // 8. Resource Reading Tests + // ============================================================================= + describe("Resource Reading", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("reads resource by URI", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/dashboard"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/dashboard", "text/html+mcp", "Dashboard") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + await collectEvents(middleware.run(input, agent)); + + expect(mockReadResource).toHaveBeenCalledWith({ + uri: "ui://server/dashboard", + }); + }); + + it("returns first content item", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + + const resourceContent = { + uri: "ui://server/tool", + mimeType: "text/html+mcp", + text: "First", + }; + mockReadResource.mockResolvedValue({ + contents: [ + resourceContent, + { uri: "ui://server/tool2", mimeType: "text/html+mcp", text: "Second" }, + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).content.resource).toEqual(resourceContent); + }); + + it("handles read errors", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockRejectedValue(new Error("Resource not found")); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should emit error result + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(1); + expect((toolResultEvents[0] as any).content).toContain("error"); + + consoleErrorSpy.mockRestore(); + }); + }); + + // ============================================================================= + // 9. Tool Result Events Tests + // ============================================================================= + describe("Tool Result Events", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("emits TOOL_CALL_RESULT event with correct toolCallId", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "specific-tc-id" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).toolCallId).toBe("specific-tc-id"); + }); + + it("extracts text content from MCP result", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([ + { type: "text", text: "Line 1" }, + { type: "text", text: "Line 2" }, + ]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).content).toBe("Line 1\nLine 2"); + }); + + it("falls back to JSON.stringify for non-text content", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "image", data: "base64data" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).content).toContain("image"); + expect((toolResultEvent as any).content).toContain("base64data"); + }); + + it("emits ACTIVITY_SNAPSHOT with MCP result and resource", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + + const mcpResult = createMCPToolCallResult([{ type: "text", text: "Result" }]); + const resourceContent = { + uri: "ui://server/tool", + mimeType: "text/html+mcp", + text: "", + }; + + mockCallTool.mockResolvedValue(mcpResult); + mockReadResource.mockResolvedValue({ contents: [resourceContent] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect(activityEvent).toBeDefined(); + expect((activityEvent as any).content.result).toEqual(mcpResult); + expect((activityEvent as any).content.resource).toEqual(resourceContent); + }); + + it("sets activityType to MCP-APPS-EXTENSION", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).activityType).toBe("MCP-APPS-EXTENSION"); + }); + + it("sets replace: true on activity snapshot", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).replace).toBe(true); + }); + }); + + // ============================================================================= + // 10. Content Extraction Tests + // ============================================================================= + describe("Content Extraction", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("joins multiple text items with newline", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([ + { type: "text", text: "First line" }, + { type: "text", text: "Second line" }, + { type: "text", text: "Third line" }, + ]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).content).toBe("First line\nSecond line\nThird line"); + }); + + it("handles empty content array", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue({ content: [] }); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).content).toBe("[]"); + }); + + it("handles mixed content types", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([ + { type: "text", text: "Text content" }, + { type: "image", data: "imagedata" }, + { type: "text", text: "More text" }, + ]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + // Should only extract text items + expect((toolResultEvent as any).content).toBe("Text content\nMore text"); + }); + }); + + // ============================================================================= + // 11. Proxied MCP Request Mode Tests + // ============================================================================= + describe("Proxied MCP Request Mode", () => { + it("detects proxied request in forwardedProps", async () => { + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should bypass normal agent flow (agent.run should not be called with our input) + expect(events[0].type).toBe(EventType.RUN_STARTED); + expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + + it("emits RUN_STARTED event", async () => { + mockPing.mockResolvedValue({}); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + runId: "proxy-run", + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(events[0].type).toBe(EventType.RUN_STARTED); + expect((events[0] as any).runId).toBe("proxy-run"); + }); + + it("emits RUN_FINISHED with result on success", async () => { + const pingResult = { timestamp: Date.now() }; + mockPing.mockResolvedValue(pingResult); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect(finishedEvent).toBeDefined(); + expect((finishedEvent as any).result).toEqual(pingResult); + }); + + it("emits RUN_FINISHED with error on failure", async () => { + mockConnect.mockRejectedValue(new Error("Connection refused")); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result.error).toContain("Connection refused"); + }); + + it("bypasses normal agent flow", async () => { + mockPing.mockResolvedValue({}); + + const middleware = new MCPAppsExtensionMiddleware({ + mcpServers: [{ type: "http", url: "http://localhost:3001" }], + }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + await collectEvents(middleware.run(input, agent)); + + // Agent's run should not have been called + expect(agent.runCalls).toHaveLength(0); + }); + }); + + // ============================================================================= + // 12. Proxied Request Execution Tests + // ============================================================================= + describe("Proxied Request Execution", () => { + it("handles tools/call method", async () => { + const toolResult = createMCPToolCallResult([{ type: "text", text: "Tool executed" }]); + mockCallTool.mockResolvedValue(toolResult); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "tools/call", + params: { name: "test-tool", arguments: { foo: "bar" } }, + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockCallTool).toHaveBeenCalledWith({ + name: "test-tool", + arguments: { foo: "bar" }, + }); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result).toEqual(toolResult); + }); + + it("handles resources/read method", async () => { + const resourceResult = createMCPResourceResult( + "ui://server/resource", + "text/html+mcp", + "" + ); + mockReadResource.mockResolvedValue(resourceResult); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "resources/read", + params: { uri: "ui://server/resource" }, + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockReadResource).toHaveBeenCalledWith({ + uri: "ui://server/resource", + }); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result).toEqual(resourceResult); + }); + + it("handles notifications/message method (one-way)", async () => { + mockNotification.mockResolvedValue(undefined); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "notifications/message", + params: { level: "info", message: "Test notification" }, + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockNotification).toHaveBeenCalledWith({ + method: "notifications/message", + params: { level: "info", message: "Test notification" }, + }); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result).toEqual({ success: true }); + }); + + it("handles ping method", async () => { + const pingResult = { timestamp: 1234567890 }; + mockPing.mockResolvedValue(pingResult); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockPing).toHaveBeenCalled(); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result).toEqual(pingResult); + }); + + it("rejects disallowed methods", async () => { + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "sampling/createMessage", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + const events = await collectEvents(middleware.run(input, agent)); + + const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); + expect((finishedEvent as any).result.error).toContain("not allowed"); + }); + + it("uses HTTP transport when serverType is http", async () => { + const { StreamableHTTPClientTransport } = await import( + "@modelcontextprotocol/sdk/client/streamableHttp.js" + ); + mockPing.mockResolvedValue({}); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000", + serverType: "http", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + await collectEvents(middleware.run(input, agent)); + + expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000")); + }); + + it("uses SSE transport when serverType is sse", async () => { + const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js"); + mockPing.mockResolvedValue({}); + + const middleware = new MCPAppsExtensionMiddleware(); + const agent = new MockAgent([]); + + const proxiedRequest: ProxiedMCPRequest = { + serverUrl: "http://localhost:3000/sse", + serverType: "sse", + method: "ping", + }; + + const input = createRunAgentInput({ + forwardedProps: { __proxiedMCPRequest: proxiedRequest }, + }); + + await collectEvents(middleware.run(input, agent)); + + expect(SSEClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000/sse")); + }); + }); + + // ============================================================================= + // 13. SEP-1865 Spec Compliance Tests + // ============================================================================= + describe("SEP-1865 Spec Compliance", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("tool metadata uses ui/resourceUri key", async () => { + mockListTools.mockResolvedValue({ + tools: [ + { + name: "tool", + description: "desc", + inputSchema: {}, + _meta: { "ui/resourceUri": "ui://server/tool" }, + }, + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + // Tool should be detected as UI tool + expect(agent.runCalls[0].tools).toHaveLength(1); + }); + + it("capability negotiation uses io.modelcontextprotocol/ui extension key", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(mockClientConstructorCalls.length).toBeGreaterThan(0); + const options = mockClientConstructorCalls[0].options as { + capabilities?: { extensions?: Record }; + }; + expect(options.capabilities?.extensions).toHaveProperty("io.modelcontextprotocol/ui"); + }); + + it("advertises text/html+mcp mimeType", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(mockClientConstructorCalls.length).toBeGreaterThan(0); + const options = mockClientConstructorCalls[0].options as { + capabilities?: { extensions?: { "io.modelcontextprotocol/ui"?: { mimeTypes?: string[] } } }; + }; + expect(options.capabilities?.extensions?.["io.modelcontextprotocol/ui"]?.mimeTypes).toContain( + "text/html+mcp" + ); + }); + + it("fallback behavior: ignores tools without ui/resourceUri", async () => { + mockListTools.mockResolvedValue({ + tools: [ + createMCPToolWithoutUI("regular-tool"), + createMCPToolWithUI("ui-tool", "ui://server/tool"), + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + // Only UI tool should be injected + expect(agent.runCalls[0].tools).toHaveLength(1); + expect(agent.runCalls[0].tools[0].name).toBe("ui-tool"); + }); + }); + + // ============================================================================= + // 14. Error Handling Tests + // ============================================================================= + describe("Error Handling", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("handles MCP server connection timeout", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + mockConnect.mockImplementation( + () => new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 10)) + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(events.length).toBeGreaterThanOrEqual(2); + expect(consoleErrorSpy).toHaveBeenCalled(); + + consoleErrorSpy.mockRestore(); + }); + + it("handles JSON parse error on tool arguments", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + // Create malformed tool call + const assistantMsg = { + id: "msg-1", + role: "assistant" as const, + toolCalls: [ + { + id: "tc-1", + type: "function" as const, + function: { + name: "ui-tool", + arguments: "{ invalid json", + }, + }, + ], + }; + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Should emit error result + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents.length).toBe(1); + expect((toolResultEvents[0] as any).content).toContain("error"); + + consoleErrorSpy.mockRestore(); + }); + }); + + // ============================================================================= + // 15. Edge Cases Tests + // ============================================================================= + describe("Edge Cases", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("handles empty tool arguments", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockCallTool).toHaveBeenCalledWith({ + name: "ui-tool", + arguments: {}, + }); + expect(events.some((e) => e.type === EventType.TOOL_CALL_RESULT)).toBe(true); + }); + + it("handles large tool arguments", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const largeData = "x".repeat(100000); + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: { data: largeData }, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + expect(mockCallTool).toHaveBeenCalledWith({ + name: "ui-tool", + arguments: { data: largeData }, + }); + expect(events.some((e) => e.type === EventType.TOOL_CALL_RESULT)).toBe(true); + }); + + it("handles multiple UI tools from same server", async () => { + mockListTools.mockResolvedValue({ + tools: [ + createMCPToolWithUI("ui-tool-1", "ui://server/tool1"), + createMCPToolWithUI("ui-tool-2", "ui://server/tool2"), + createMCPToolWithUI("ui-tool-3", "ui://server/tool3"), + ], + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + + await collectEvents(middleware.run(createRunAgentInput(), agent)); + + expect(agent.runCalls[0].tools).toHaveLength(3); + }); + + it("handles rapid sequential requests", async () => { + mockListTools.mockResolvedValue({ tools: [] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const results = await Promise.all([ + collectEvents( + middleware.run( + createRunAgentInput({ runId: "run-1" }), + new MockAgent([ + createRunStartedEvent("run-1"), + createRunFinishedEvent("run-1"), + ]) + ) + ), + collectEvents( + middleware.run( + createRunAgentInput({ runId: "run-2" }), + new MockAgent([ + createRunStartedEvent("run-2"), + createRunFinishedEvent("run-2"), + ]) + ) + ), + collectEvents( + middleware.run( + createRunAgentInput({ runId: "run-3" }), + new MockAgent([ + createRunStartedEvent("run-3"), + createRunFinishedEvent("run-3"), + ]) + ) + ), + ]); + + expect(results).toHaveLength(3); + results.forEach((events) => { + expect(events[0].type).toBe(EventType.RUN_STARTED); + expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); + }); + }); + }); + + // ============================================================================= + // 16. Activity Event Injection Tests + // ============================================================================= + describe("Activity Event Injection", () => { + const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; + + it("emits ACTIVITY_SNAPSHOT for each pending UI tool call", async () => { + const uiTool1 = createMCPToolWithUI("ui-tool-1", "ui://server/tool1"); + const uiTool2 = createMCPToolWithUI("ui-tool-2", "ui://server/tool2"); + mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool-1", args: { param: "a" }, id: "tc-1" }, + { name: "ui-tool-2", args: { param: "b" }, id: "tc-2" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect(activityEvents).toHaveLength(2); + }); + + it("emits TOOL_CALL_RESULT before ACTIVITY_SNAPSHOT for each tool", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultIndex = events.findIndex((e) => e.type === EventType.TOOL_CALL_RESULT); + const activityIndex = events.findIndex((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + + expect(toolResultIndex).toBeLessThan(activityIndex); + }); + + it("emits TOOL_CALL_RESULT and ACTIVITY_SNAPSHOT before RUN_FINISHED", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const runFinishedIndex = events.findIndex((e) => e.type === EventType.RUN_FINISHED); + const activityIndex = events.findIndex((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + const toolResultIndex = events.findIndex((e) => e.type === EventType.TOOL_CALL_RESULT); + + expect(toolResultIndex).toBeLessThan(runFinishedIndex); + expect(activityIndex).toBeLessThan(runFinishedIndex); + }); + + it("ACTIVITY_SNAPSHOT has a unique messageId", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).messageId).toBeDefined(); + expect(typeof (activityEvent as any).messageId).toBe("string"); + expect((activityEvent as any).messageId.length).toBeGreaterThan(0); + }); + + it("TOOL_CALL_RESULT has a unique messageId", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); + expect((toolResultEvent as any).messageId).toBeDefined(); + expect(typeof (toolResultEvent as any).messageId).toBe("string"); + expect((toolResultEvent as any).messageId.length).toBeGreaterThan(0); + }); + + it("passes tool arguments correctly to MCP server", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const toolArgs = { query: "test", limit: 10, nested: { value: "deep" } }; + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: toolArgs, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + await collectEvents(middleware.run(input, agent)); + + expect(mockCallTool).toHaveBeenCalledWith({ + name: "ui-tool", + arguments: toolArgs, + }); + }); + + it("resource in ACTIVITY_SNAPSHOT includes uri and mimeType", async () => { + const resourceUri = "ui://server/dashboard"; + const uiTool = createMCPToolWithUI("ui-tool", resourceUri); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + + const resourceContent = { + uri: resourceUri, + mimeType: "text/html+mcp", + text: "UI Content", + }; + mockReadResource.mockResolvedValue({ contents: [resourceContent] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).content.resource.uri).toBe(resourceUri); + expect((activityEvent as any).content.resource.mimeType).toBe("text/html+mcp"); + expect((activityEvent as any).content.resource.text).toContain("UI Content"); + }); + + it("handles blob (base64) content in resource", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + + // Resource with blob instead of text + const resourceContent = { + uri: "ui://server/tool", + mimeType: "text/html+mcp", + blob: "PCFET0NUWVBFIGh0bWw+", // base64 encoded "" + }; + mockReadResource.mockResolvedValue({ contents: [resourceContent] }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: {}, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect((activityEvent as any).content.resource.blob).toBe("PCFET0NUWVBFIGh0bWw+"); + }); + + it("full end-to-end flow: RUN_STARTED → tool call → TOOL_CALL_RESULT → ACTIVITY_SNAPSHOT → RUN_FINISHED", async () => { + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + mockListTools.mockResolvedValue({ tools: [uiTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Tool executed successfully" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "UI") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool", args: { action: "render" }, id: "tc-1" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Verify event sequence + const eventTypes = events.map((e) => e.type); + expect(eventTypes).toEqual([ + EventType.RUN_STARTED, + EventType.TOOL_CALL_RESULT, + EventType.ACTIVITY_SNAPSHOT, + EventType.RUN_FINISHED, + ]); + + // Verify content + const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT) as any; + expect(toolResultEvent.content).toBe("Tool executed successfully"); + expect(toolResultEvent.toolCallId).toBe("tc-1"); + + const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT) as any; + expect(activityEvent.activityType).toBe("MCP-APPS-EXTENSION"); + expect(activityEvent.replace).toBe(true); + expect(activityEvent.content.resource.text).toContain("UI"); + }); + + it("does not emit activity events for non-UI tools", async () => { + // Mix of UI and non-UI tools + const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); + const regularTool = createMCPToolWithoutUI("regular-tool"); + mockListTools.mockResolvedValue({ tools: [uiTool, regularTool] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockResolvedValue( + createMCPResourceResult("ui://server/tool", "text/html+mcp", "") + ); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + // Call both UI and non-UI tools + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "regular-tool", args: {}, id: "tc-regular" }, + { name: "ui-tool", args: {}, id: "tc-ui" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + const events = await collectEvents(middleware.run(input, agent)); + + // Only one ACTIVITY_SNAPSHOT (for the UI tool) + const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); + expect(activityEvents).toHaveLength(1); + + // Only one TOOL_CALL_RESULT (for the UI tool) + const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); + expect(toolResultEvents).toHaveLength(1); + expect((toolResultEvents[0] as any).toolCallId).toBe("tc-ui"); + }); + + it("reads the correct resource URI for each UI tool", async () => { + const uiTool1 = createMCPToolWithUI("ui-tool-1", "ui://server/dashboard"); + const uiTool2 = createMCPToolWithUI("ui-tool-2", "ui://server/settings"); + mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); + mockCallTool.mockResolvedValue( + createMCPToolCallResult([{ type: "text", text: "Result" }]) + ); + mockReadResource.mockImplementation((params: { uri: string }) => { + if (params.uri === "ui://server/dashboard") { + return Promise.resolve({ + contents: [{ uri: params.uri, mimeType: "text/html+mcp", text: "Dashboard" }], + }); + } + return Promise.resolve({ + contents: [{ uri: params.uri, mimeType: "text/html+mcp", text: "Settings" }], + }); + }); + + const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); + + const assistantMsg = createAssistantMessageWithToolCalls([ + { name: "ui-tool-1", args: {}, id: "tc-1" }, + { name: "ui-tool-2", args: {}, id: "tc-2" }, + ]); + + const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); + const input = createRunAgentInput({ messages: [assistantMsg] }); + + await collectEvents(middleware.run(input, agent)); + + // Verify readResource was called with correct URIs + expect(mockReadResource).toHaveBeenCalledWith({ uri: "ui://server/dashboard" }); + expect(mockReadResource).toHaveBeenCalledWith({ uri: "ui://server/settings" }); + }); + }); +}); diff --git a/apps/react/demo/src/lib/__tests__/test-utils.ts b/apps/react/demo/src/lib/__tests__/test-utils.ts new file mode 100644 index 0000000..f217b72 --- /dev/null +++ b/apps/react/demo/src/lib/__tests__/test-utils.ts @@ -0,0 +1,403 @@ +import { vi } from "vitest"; +import { AbstractAgent, BaseEvent, EventType, RunAgentInput, Message, Tool, ToolCall, AssistantMessage } from "@ag-ui/client"; +import { Observable } from "rxjs"; +import { firstValueFrom, toArray } from "rxjs"; + +/** + * Mock MCP Client instance + */ +export interface MockMCPClientInstance { + connect: ReturnType; + close: ReturnType; + listTools: ReturnType; + callTool: ReturnType; + readResource: ReturnType; + notification: ReturnType; + ping: ReturnType; +} + +/** + * Create a mock MCP Client instance + */ +export function createMockMCPClient(): MockMCPClientInstance { + return { + connect: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + listTools: vi.fn().mockResolvedValue({ tools: [] }), + callTool: vi.fn().mockResolvedValue({ content: [] }), + readResource: vi.fn().mockResolvedValue({ contents: [] }), + notification: vi.fn().mockResolvedValue(undefined), + ping: vi.fn().mockResolvedValue({}), + }; +} + +/** + * Mock MCP tool with UI resource (per SEP-1865) + */ +export interface MockMCPTool { + name: string; + description?: string; + inputSchema?: Record; + _meta?: Record; +} + +/** + * Create an MCP tool with UI resource attached + */ +export function createMCPToolWithUI( + name: string, + resourceUri: string, + description?: string +): MockMCPTool { + return { + name, + description: description || `Tool ${name}`, + inputSchema: { type: "object", properties: {} }, + _meta: { "ui/resourceUri": resourceUri }, + }; +} + +/** + * Create an MCP tool without UI resource + */ +export function createMCPToolWithoutUI(name: string, description?: string): MockMCPTool { + return { + name, + description: description || `Tool ${name}`, + inputSchema: { type: "object", properties: {} }, + }; +} + +/** + * Create an MCP tool with _meta but no ui/resourceUri + */ +export function createMCPToolWithEmptyMeta(name: string): MockMCPTool { + return { + name, + description: `Tool ${name}`, + inputSchema: { type: "object", properties: {} }, + _meta: { someOtherField: "value" }, + }; +} + +/** + * Mock Agent for testing middleware + */ +export class MockAgent extends AbstractAgent { + private events: BaseEvent[]; + public runCalls: RunAgentInput[] = []; + + constructor(events: BaseEvent[] = []) { + super(); + this.events = events; + } + + run(input: RunAgentInput): Observable { + this.runCalls.push(input); + return new Observable((subscriber) => { + for (const event of this.events) { + subscriber.next(event); + } + subscriber.complete(); + }); + } + + setEvents(events: BaseEvent[]): void { + this.events = events; + } +} + +/** + * Mock Agent that emits events asynchronously + */ +export class AsyncMockAgent extends AbstractAgent { + private events: BaseEvent[]; + private delayMs: number; + + constructor(events: BaseEvent[] = [], delayMs: number = 0) { + super(); + this.events = events; + this.delayMs = delayMs; + } + + run(input: RunAgentInput): Observable { + return new Observable((subscriber) => { + let cancelled = false; + + const emitEvents = async () => { + for (const event of this.events) { + if (cancelled) break; + if (this.delayMs > 0) { + await new Promise((resolve) => setTimeout(resolve, this.delayMs)); + } + if (!cancelled) { + subscriber.next(event); + } + } + if (!cancelled) { + subscriber.complete(); + } + }; + + emitEvents(); + + return () => { + cancelled = true; + }; + }); + } +} + +/** + * Mock Agent that throws an error + */ +export class ErrorMockAgent extends AbstractAgent { + private error: Error; + + constructor(error: Error = new Error("Mock error")) { + super(); + this.error = error; + } + + run(_input: RunAgentInput): Observable { + return new Observable((subscriber) => { + subscriber.error(this.error); + }); + } +} + +/** + * Create a basic RunAgentInput for testing + */ +export function createRunAgentInput(overrides: Partial = {}): RunAgentInput { + return { + threadId: "test-thread", + runId: "test-run", + tools: [], + context: [], + forwardedProps: {}, + state: {}, + messages: [], + ...overrides, + }; +} + +/** + * Create a RUN_STARTED event + */ +export function createRunStartedEvent( + runId: string = "test-run", + threadId: string = "test-thread" +): BaseEvent { + return { + type: EventType.RUN_STARTED, + runId, + threadId, + }; +} + +/** + * Create a RUN_FINISHED event + */ +export function createRunFinishedEvent( + runId: string = "test-run", + threadId: string = "test-thread", + result?: unknown +): BaseEvent { + return { + type: EventType.RUN_FINISHED, + runId, + threadId, + result, + }; +} + +/** + * Create a TEXT_MESSAGE_START event + */ +export function createTextMessageStartEvent(messageId: string = "msg-1"): BaseEvent { + return { + type: EventType.TEXT_MESSAGE_START, + messageId, + role: "assistant", + }; +} + +/** + * Create a TEXT_MESSAGE_CONTENT event + */ +export function createTextMessageContentEvent( + messageId: string = "msg-1", + delta: string = "Hello" +): BaseEvent { + return { + type: EventType.TEXT_MESSAGE_CONTENT, + messageId, + delta, + }; +} + +/** + * Create a TEXT_MESSAGE_END event + */ +export function createTextMessageEndEvent(messageId: string = "msg-1"): BaseEvent { + return { + type: EventType.TEXT_MESSAGE_END, + messageId, + }; +} + +/** + * Create a TOOL_CALL_START event + */ +export function createToolCallStartEvent( + toolCallId: string, + toolCallName: string, + parentMessageId?: string +): BaseEvent { + return { + type: EventType.TOOL_CALL_START, + toolCallId, + toolCallName, + parentMessageId, + }; +} + +/** + * Create a TOOL_CALL_ARGS event + */ +export function createToolCallArgsEvent(toolCallId: string, delta: string): BaseEvent { + return { + type: EventType.TOOL_CALL_ARGS, + toolCallId, + delta, + }; +} + +/** + * Create a TOOL_CALL_END event + */ +export function createToolCallEndEvent(toolCallId: string): BaseEvent { + return { + type: EventType.TOOL_CALL_END, + toolCallId, + }; +} + +/** + * Create a TOOL_CALL_RESULT event + */ +export function createToolCallResultEvent( + toolCallId: string, + content: string, + messageId: string = `result-${toolCallId}` +): BaseEvent { + return { + type: EventType.TOOL_CALL_RESULT, + messageId, + toolCallId, + content, + }; +} + +/** + * Create an assistant message with tool calls + */ +export function createAssistantMessageWithToolCalls( + toolCalls: Array<{ name: string; args?: Record; id?: string }>, + messageId?: string +): AssistantMessage { + return { + id: messageId || `msg-${Math.random().toString(36).substr(2, 9)}`, + role: "assistant", + content: "", + toolCalls: toolCalls.map((tc) => ({ + id: tc.id || `tc-${Math.random().toString(36).substr(2, 9)}`, + type: "function" as const, + function: { + name: tc.name, + arguments: JSON.stringify(tc.args || {}), + }, + })), + }; +} + +/** + * Create a tool result message + */ +export function createToolResultMessage( + toolCallId: string, + content: string, + messageId?: string +): Message { + return { + id: messageId || `msg-${Math.random().toString(36).substr(2, 9)}`, + role: "tool", + toolCallId, + content, + }; +} + +/** + * Create an AG-UI Tool + */ +export function createAGUITool(name: string, description?: string): Tool { + return { + name, + description: description || `Tool ${name}`, + parameters: { type: "object", properties: {} }, + }; +} + +/** + * Collect all events from an Observable + */ +export async function collectEvents(observable: Observable): Promise { + return firstValueFrom(observable.pipe(toArray())); +} + +/** + * Create MCP tool call result (what callTool returns) + */ +export function createMCPToolCallResult( + content: Array<{ type: string; text?: string; [key: string]: unknown }> +): { content: Array<{ type: string; text?: string; [key: string]: unknown }> } { + return { content }; +} + +/** + * Create MCP resource read result + */ +export function createMCPResourceResult( + uri: string, + mimeType: string, + text: string +): { contents: Array<{ uri: string; mimeType: string; text: string }> } { + return { + contents: [{ uri, mimeType, text }], + }; +} + +/** + * Wait for a condition to be true + */ +export async function waitForCondition( + condition: () => boolean, + timeout: number = 1000, + interval: number = 10 +): Promise { + const start = Date.now(); + while (!condition()) { + if (Date.now() - start > timeout) { + throw new Error("Timeout waiting for condition"); + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } +} + +/** + * Generate a random UUID-like string + */ +export function randomId(): string { + return `${Math.random().toString(36).substr(2, 9)}-${Math.random().toString(36).substr(2, 9)}`; +} diff --git a/apps/react/demo/vitest.config.mjs b/apps/react/demo/vitest.config.mjs new file mode 100644 index 0000000..3f6b8d3 --- /dev/null +++ b/apps/react/demo/vitest.config.mjs @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + globals: true, + include: ["src/**/__tests__/**/*.{test,spec}.ts"], + coverage: { + reporter: ["text", "lcov", "html"], + include: ["src/lib/**/*.ts"], + exclude: ["src/**/*.d.ts", "src/**/index.ts", "src/**/__tests__/**"], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 264790c..17abb96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 8.6.14(@types/react@19.1.0)(storybook@8.6.14(prettier@3.6.0)) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.5 - version: 1.0.6(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + version: 1.0.6(webpack@5.100.0(@swc/core@1.12.11)) '@storybook/react-webpack5': specifier: ^8 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) prettier: specifier: ^3.6.0 version: 3.6.0 @@ -74,7 +74,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^18.2.0 - version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) + version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 version: 18.2.20(chokidar@3.6.0) @@ -175,7 +175,7 @@ importers: version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 - version: 18.2.20(chokidar@3.6.0) + version: 18.2.20 '@angular/compiler-cli': specifier: ^18.2.0 version: 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) @@ -341,6 +341,9 @@ importers: typescript: specifier: ^5 version: 5.8.2 + vitest: + specifier: ^3.2.3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) apps/react/storybook: dependencies: @@ -352,7 +355,7 @@ importers: version: 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/nextjs': specifier: ^8 - version: 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + version: 8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) '@storybook/react': specifier: ^8 version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) @@ -12775,7 +12778,6 @@ snapshots: rxjs: 7.8.1 transitivePeerDependencies: - chokidar - optional: true '@angular-devkit/architect@0.1902.15(chokidar@4.0.3)': dependencies: @@ -12784,13 +12786,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - '@angular-devkit/core': 18.2.20(chokidar@3.6.0) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5) + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12843,15 +12845,15 @@ snapshots: tslib: 2.6.3 typescript: 5.4.5 watchpack: 2.4.1 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 3.4.17 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 4.1.11 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -12869,14 +12871,15 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli + optional: true - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12931,7 +12934,7 @@ snapshots: watchpack: 2.4.1 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: @@ -12955,15 +12958,14 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli - optional: true - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) '@angular-devkit/core': 18.2.20(chokidar@3.6.0) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -13020,11 +13022,11 @@ snapshots: webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 4.1.11 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 3.4.17 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -13047,7 +13049,7 @@ snapshots: dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) rxjs: 7.8.1 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) transitivePeerDependencies: - chokidar @@ -13064,10 +13066,10 @@ snapshots: '@angular-devkit/build-webpack@0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0))': dependencies: - '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) rxjs: 7.8.1 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) transitivePeerDependencies: - chokidar @@ -13092,7 +13094,6 @@ snapshots: source-map: 0.7.4 optionalDependencies: chokidar: 4.0.3 - optional: true '@angular-devkit/core@19.2.15(chokidar@4.0.3)': dependencies: @@ -13105,6 +13106,16 @@ snapshots: optionalDependencies: chokidar: 4.0.3 + '@angular-devkit/schematics@18.2.20': + dependencies: + '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.11 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + '@angular-devkit/schematics@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/core': 18.2.20(chokidar@3.6.0) @@ -13120,10 +13131,10 @@ snapshots: '@angular/core': 18.2.13(rxjs@7.8.1)(zone.js@0.14.10) tslib: 2.8.1 - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 @@ -13152,7 +13163,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 3.4.17 + tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' - chokidar @@ -13162,8 +13173,9 @@ snapshots: - sugarss - supports-color - terser + optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13173,7 +13185,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) '@inquirer/confirm': 3.1.22 - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1)) browserslist: 4.25.1 critters: 0.0.24 esbuild: 0.23.0 @@ -13190,11 +13202,11 @@ snapshots: sass: 1.77.6 semver: 7.6.3 typescript: 5.4.5 - vite: 5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6) + vite: 5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1) watchpack: 2.4.1 optionalDependencies: - less: 4.2.0 - postcss: 8.4.41 + less: 4.4.1 + postcss: 8.5.6 tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' @@ -13207,7 +13219,7 @@ snapshots: - terser optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13217,7 +13229,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) '@inquirer/confirm': 3.1.22 - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6)) browserslist: 4.25.1 critters: 0.0.24 esbuild: 0.23.0 @@ -13234,11 +13246,11 @@ snapshots: sass: 1.77.6 semver: 7.6.3 typescript: 5.4.5 - vite: 5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1) + vite: 5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6) watchpack: 2.4.1 optionalDependencies: - less: 4.4.1 - postcss: 8.5.6 + less: 4.2.0 + postcss: 8.4.41 tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' @@ -13249,9 +13261,8 @@ snapshots: - sugarss - supports-color - terser - optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) @@ -13283,7 +13294,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 4.1.11 + tailwindcss: 3.4.17 transitivePeerDependencies: - '@types/node' - chokidar @@ -13303,6 +13314,30 @@ snapshots: optionalDependencies: parse5: 7.3.0 + '@angular/cli@18.2.20': + dependencies: + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + '@angular-devkit/schematics': 18.2.20 + '@inquirer/prompts': 5.3.8 + '@listr2/prompt-adapter-inquirer': 2.0.15(@inquirer/prompts@5.3.8) + '@schematics/angular': 18.2.20 + '@yarnpkg/lockfile': 1.1.0 + ini: 4.1.3 + jsonc-parser: 3.3.1 + listr2: 8.2.4 + npm-package-arg: 11.0.3 + npm-pick-manifest: 9.1.0 + pacote: 18.0.6 + resolve: 1.22.8 + semver: 7.6.3 + symbol-observable: 4.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - chokidar + - supports-color + '@angular/cli@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) @@ -16547,7 +16582,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.44.0 @@ -16557,10 +16592,10 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.3.2 source-map: 0.7.4 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) optionalDependencies: type-fest: 4.41.0 - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)) webpack-hot-middleware: 2.26.1 '@polka/url@1.0.0-next.29': {} @@ -17064,6 +17099,14 @@ snapshots: '@rushstack/eslint-patch@1.12.0': {} + '@schematics/angular@18.2.20': + dependencies: + '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + '@angular-devkit/schematics': 18.2.20 + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - chokidar + '@schematics/angular@18.2.20(chokidar@3.6.0)': dependencies: '@angular-devkit/core': 18.2.20(chokidar@3.6.0) @@ -17432,10 +17475,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.6.14(prettier@3.6.0) - '@storybook/addon-webpack5-compiler-swc@1.0.6(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': + '@storybook/addon-webpack5-compiler-swc@1.0.6(webpack@5.100.0(@swc/core@1.12.11))': dependencies: '@swc/core': 1.12.11 - swc-loader: 0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + swc-loader: 0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) transitivePeerDependencies: - '@swc/helpers' - webpack @@ -17476,7 +17519,7 @@ snapshots: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) optionalDependencies: '@angular/animations': 18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)) - '@angular/cli': 18.2.20(chokidar@3.6.0) + '@angular/cli': 18.2.20 zone.js: 0.14.10 transitivePeerDependencies: - '@rspack/core' @@ -17530,7 +17573,7 @@ snapshots: - uglify-js - webpack-cli - '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/builder-webpack5@8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 @@ -17538,23 +17581,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + webpack: 5.100.0(@swc/core@1.12.11) + webpack-dev-middleware: 6.1.3(webpack@5.100.0(@swc/core@1.12.11)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17618,7 +17661,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/nextjs@8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': + '@storybook/nextjs@8.6.14(@swc/core@1.12.11)(next@15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)(storybook@8.6.14(prettier@3.6.0))(type-fest@4.41.0)(typescript@5.8.2)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) @@ -17633,30 +17676,30 @@ snapshots: '@babel/preset-react': 7.27.1(@babel/core@7.28.0) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) '@babel/runtime': 7.27.6 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)))(webpack-hot-middleware@2.26.1)(webpack@5.100.0(@swc/core@1.12.11)) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@types/semver': 7.7.0 - babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + babel-loader: 9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)) + css-loader: 6.11.0(webpack@5.100.0(@swc/core@1.12.11)) find-up: 5.0.0 image-size: 1.2.1 loader-utils: 3.3.1 next: 15.4.4(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.0(@swc/core@1.12.11)) pnp-webpack-plugin: 1.7.0(typescript@5.8.2) postcss: 8.5.6 - postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + sass-loader: 14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)) semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) - style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + style-loader: 3.3.4(webpack@5.100.0(@swc/core@1.12.11)) styled-jsx: 5.1.7(@babel/core@7.28.0)(react@18.3.1) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -17664,7 +17707,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -17683,11 +17726,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.6.0)) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)) '@types/semver': 7.7.0 find-up: 5.0.0 magic-string: 0.30.17 @@ -17698,7 +17741,7 @@ snapshots: semver: 7.7.2 storybook: 8.6.14(prettier@3.6.0) tsconfig-paths: 4.2.0 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) optionalDependencies: typescript: 5.8.2 transitivePeerDependencies: @@ -17713,7 +17756,7 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11))': dependencies: debug: 4.4.1(supports-color@5.5.0) endent: 2.1.0 @@ -17723,7 +17766,7 @@ snapshots: react-docgen-typescript: 2.4.0(typescript@5.8.2) tslib: 2.8.1 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - supports-color @@ -17733,10 +17776,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.14(prettier@3.6.0) - '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': + '@storybook/react-webpack5@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2)': dependencies: - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(esbuild@0.25.6)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(esbuild@0.25.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/builder-webpack5': 8.6.14(@swc/core@1.12.11)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) + '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(@swc/core@1.12.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.0)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.0))(typescript@5.8.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -18730,6 +18773,14 @@ snapshots: optionalDependencies: vite: 5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + '@vitest/mocker@3.2.4(vite@7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + '@vitest/mocker@3.2.4(vite@7.0.5(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 @@ -18794,7 +18845,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) + vitest: 2.1.9(@types/node@22.15.3)(@vitest/ui@2.1.9)(jsdom@24.1.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1) '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: @@ -18805,7 +18856,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) '@vitest/utils@2.0.5': dependencies: @@ -19224,12 +19275,12 @@ snapshots: schema-utils: 4.3.2 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@babel/core': 7.28.0 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.10): dependencies: @@ -19935,6 +19986,19 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + css-loader@6.11.0(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) + postcss-value-parser: 4.2.0 + semver: 7.7.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11) + css-loader@7.1.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) @@ -21370,7 +21434,7 @@ snapshots: typescript: 5.4.5 webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -21385,7 +21449,7 @@ snapshots: semver: 7.7.2 tapable: 2.2.2 typescript: 5.8.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) form-data-encoder@1.7.2: {} @@ -21920,7 +21984,7 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)): + html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -21928,8 +21992,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) - optional: true + webpack: 5.100.0(@swc/core@1.12.11) htmlparser2@6.1.0: dependencies: @@ -23895,7 +23958,7 @@ snapshots: transitivePeerDependencies: - supports-color - node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.100.0(@swc/core@1.12.11)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -23922,7 +23985,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) node-releases@2.0.19: {} @@ -24516,14 +24579,14 @@ snapshots: transitivePeerDependencies: - typescript - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.2)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.2) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) transitivePeerDependencies: - typescript @@ -25369,12 +25432,12 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + sass-loader@14.2.1(sass@1.90.0)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.90.0 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) sass-loader@16.0.0(sass@1.77.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: @@ -26041,6 +26104,10 @@ snapshots: dependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + style-loader@3.3.4(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + webpack: 5.100.0(@swc/core@1.12.11) + style-loader@4.0.0(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) @@ -26134,11 +26201,11 @@ snapshots: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swc-loader@0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): + swc-loader@0.2.6(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@swc/core': 1.12.11 '@swc/counter': 0.1.3 - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) symbol-observable@4.0.0: {} @@ -26258,17 +26325,16 @@ snapshots: '@swc/core': 1.12.11 esbuild: 0.25.6 - terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): + terser-webpack-plugin@5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@jridgewell/trace-mapping': 0.3.29 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.100.0(@swc/core@1.12.11) optionalDependencies: '@swc/core': 1.12.11 - esbuild: 0.25.6 terser@5.31.6: dependencies: @@ -26891,6 +26957,27 @@ snapshots: - supports-color - terser + vite-node@3.2.4(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-node@3.2.4(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -26952,6 +27039,25 @@ snapshots: sass: 1.90.0 terser: 5.43.1 + vite@7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): + dependencies: + esbuild: 0.25.6 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.1 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 20.19.9 + fsevents: 2.3.3 + jiti: 2.5.1 + less: 4.4.1 + lightningcss: 1.30.1 + sass: 1.90.0 + terser: 5.43.1 + tsx: 4.20.5 + yaml: 2.8.0 + vite@7.0.5(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): dependencies: esbuild: 0.25.6 @@ -27064,6 +27170,50 @@ snapshots: - supports-color - terser + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + debug: 4.4.1(supports-color@5.5.0) + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.19.9 + '@vitest/ui': 3.2.4(vitest@3.2.4) + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.3)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -27175,6 +27325,16 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack-dev-middleware@6.1.3(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11) + webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): dependencies: colorette: 2.0.20 @@ -27185,6 +27345,17 @@ snapshots: schema-utils: 4.3.2 optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) + + webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + colorette: 2.0.20 + memfs: 4.36.3 + mime-types: 2.1.35 + on-finished: 2.4.1 + range-parser: 1.2.1 + schema-utils: 4.3.2 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11) optional: true webpack-dev-middleware@7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): @@ -27235,6 +27406,44 @@ snapshots: - debug - supports-color - utf-8-validate + + webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.23 + '@types/express-serve-static-core': 4.19.6 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.8 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.1 + connect-history-api-fallback: 2.0.0 + express: 4.21.2 + graceful-fs: 4.2.11 + http-proxy-middleware: 2.0.9(@types/express@4.17.23) + ipaddr.js: 2.2.0 + launch-editor: 2.11.1 + open: 10.1.2 + p-retry: 6.2.1 + schema-utils: 4.3.2 + selfsigned: 2.4.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)) + ws: 8.18.3 + optionalDependencies: + webpack: 5.100.0(@swc/core@1.12.11) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate optional: true webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): @@ -27265,7 +27474,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) ws: 8.18.3 optionalDependencies: webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) @@ -27296,16 +27505,16 @@ snapshots: optionalDependencies: html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.25.6) + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6)) + html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)) webpack-virtual-modules@0.6.2: {} - webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6): + webpack@5.100.0(@swc/core@1.12.11): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -27329,7 +27538,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(webpack@5.100.0(@swc/core@1.12.11)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -27337,17 +27546,19 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0): + webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6): dependencies: + '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) + acorn-import-phases: 1.0.3(acorn@8.15.0) browserslist: 4.25.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.3 + enhanced-resolve: 5.18.2 es-module-lexer: 1.7.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -27357,9 +27568,9 @@ snapshots: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -27367,7 +27578,7 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.25.6): + webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0): dependencies: '@types/estree': 1.0.8 '@webassemblyjs/ast': 1.14.1 @@ -27389,7 +27600,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.25.6)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + terser-webpack-plugin: 5.3.14(@swc/core@1.12.11)(esbuild@0.23.0)(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: From d75d26760288b52676186d7b6212b9e68b30712d Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Thu, 4 Dec 2025 17:32:19 +0100 Subject: [PATCH 03/11] add demo --- apps/react/demo/mcp-apps/package-lock.json | 2878 +++++++++++++++++ apps/react/demo/mcp-apps/package.json | 25 + apps/react/demo/mcp-apps/server.ts | 234 ++ apps/react/demo/mcp-apps/src/ui-raw.ts | 344 ++ apps/react/demo/mcp-apps/tsconfig.json | 13 + apps/react/demo/mcp-apps/ui-raw.html | 12 + apps/react/demo/mcp-apps/vite.config.ts | 23 + apps/react/demo/public/sandbox.html | 95 + apps/react/demo/src/app/page.tsx | 9 + .../components/MCPAppsActivityRenderer.tsx | 399 +++ .../src/lib/mcp-apps-extension-middleware.ts | 10 +- 11 files changed, 4040 insertions(+), 2 deletions(-) create mode 100644 apps/react/demo/mcp-apps/package-lock.json create mode 100644 apps/react/demo/mcp-apps/package.json create mode 100644 apps/react/demo/mcp-apps/server.ts create mode 100644 apps/react/demo/mcp-apps/src/ui-raw.ts create mode 100644 apps/react/demo/mcp-apps/tsconfig.json create mode 100644 apps/react/demo/mcp-apps/ui-raw.html create mode 100644 apps/react/demo/mcp-apps/vite.config.ts create mode 100644 apps/react/demo/public/sandbox.html create mode 100644 apps/react/demo/src/components/MCPAppsActivityRenderer.tsx diff --git a/apps/react/demo/mcp-apps/package-lock.json b/apps/react/demo/mcp-apps/package-lock.json new file mode 100644 index 0000000..1543953 --- /dev/null +++ b/apps/react/demo/mcp-apps/package-lock.json @@ -0,0 +1,2878 @@ +{ + "name": "mcp-apps-demo", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-apps-demo", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.22.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.0.0", + "cors": "^2.8.5", + "express": "^5.1.0", + "tsx": "^4.0.0", + "typescript": "^5.7.2", + "vite": "^6.0.0", + "vite-plugin-singlefile": "^2.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz", + "integrity": "sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==", + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.0.tgz", + "integrity": "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.44.1", + "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/apps/react/demo/mcp-apps/package.json b/apps/react/demo/mcp-apps/package.json new file mode 100644 index 0000000..143eacc --- /dev/null +++ b/apps/react/demo/mcp-apps/package.json @@ -0,0 +1,25 @@ +{ + "name": "mcp-apps-demo", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "NODE_ENV=development npm run build && npm run server", + "build": "INPUT=ui-raw.html vite build", + "server": "npx tsx server.ts" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.22.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.0.0", + "cors": "^2.8.5", + "express": "^5.1.0", + "typescript": "^5.7.2", + "tsx": "^4.0.0", + "vite": "^6.0.0", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/apps/react/demo/mcp-apps/server.ts b/apps/react/demo/mcp-apps/server.ts new file mode 100644 index 0000000..9d88107 --- /dev/null +++ b/apps/react/demo/mcp-apps/server.ts @@ -0,0 +1,234 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import express, { Request, Response } from "express"; +import { randomUUID } from "node:crypto"; +import { z } from "zod"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { + CallToolResult, + isInitializeRequest, + ReadResourceResult, + Resource, +} from "@modelcontextprotocol/sdk/types.js"; +import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js"; +import cors from "cors"; +import path from "node:path"; +import fs from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +// Define the resource URI meta key inline (from MCP Apps Extension protocol) +const RESOURCE_URI_META_KEY = "ui/resourceUri"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Load UI HTML file from dist/ +const distDir = path.join(__dirname, "dist"); +const loadHtml = async (name: string) => { + const htmlPath = path.join(distDir, `${name}.html`); + return fs.readFile(htmlPath, "utf-8"); +}; + +// Create an MCP server with UI tools +const getServer = async () => { + const server = new McpServer( + { + name: "mcp-apps-demo-server", + version: "1.0.0", + }, + { capabilities: { logging: {} } }, + ); + + // Load HTML for the raw UI + const rawHtml = await loadHtml("ui-raw"); + + const registerResource = (resource: Resource, htmlContent: string) => { + server.registerResource( + resource.name, + resource.uri, + resource, + async (): Promise => ({ + contents: [ + { + uri: resource.uri, + mimeType: resource.mimeType, + text: htmlContent, + }, + ], + }), + ); + return resource; + }; + + // Register the raw UI resource and tool + { + const rawResource = registerResource( + { + name: "ui-raw-template", + uri: "ui://raw", + title: "Raw UI Template", + description: "A simple raw HTML UI", + mimeType: "text/html+mcp", + }, + rawHtml, + ); + + server.registerTool( + "create-ui-raw", + { + title: "Raw UI", + description: "A tool that returns a raw HTML UI (no Apps SDK runtime)", + inputSchema: { + message: z.string().describe("Message to display"), + }, + _meta: { + [RESOURCE_URI_META_KEY]: rawResource.uri, + }, + }, + async ({ message }): Promise => ({ + content: [{ type: "text", text: JSON.stringify({ message }) }], + structuredContent: { message }, + }), + ); + } + + // Register the get-weather tool (no UI resource, for testing from within UI) + server.registerTool( + "get-weather", + { + title: "Get Weather", + description: "Returns current weather for a location", + inputSchema: { + location: z.string().describe("Location to get weather for"), + }, + }, + async ({ location }): Promise => { + const temperature = 25; + const condition = "sunny"; + return { + content: [ + { + type: "text", + text: `The weather in ${location} is ${condition}, ${temperature}°C.`, + }, + ], + structuredContent: { temperature, condition }, + }; + }, + ); + + return server; +}; + +const MCP_PORT = process.env.MCP_PORT + ? parseInt(process.env.MCP_PORT, 10) + : 3001; + +const app = express(); +app.use(express.json()); +app.use( + cors({ + origin: "*", + exposedHeaders: ["Mcp-Session-Id"], + }), +); + +// Map to store transports by session ID +const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; + +const mcpPostHandler = async (req: Request, res: Response) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + + try { + let transport: StreamableHTTPServerTransport; + if (sessionId && transports[sessionId]) { + transport = transports[sessionId]; + } else if (!sessionId && isInitializeRequest(req.body)) { + const eventStore = new InMemoryEventStore(); + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + eventStore, + onsessioninitialized: (sessionId) => { + console.log(`Session initialized: ${sessionId}`); + transports[sessionId] = transport; + }, + }); + + transport.onclose = () => { + const sid = transport.sessionId; + if (sid && transports[sid]) { + console.log(`Session closed: ${sid}`); + delete transports[sid]; + } + }; + + const server = await getServer(); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + return; + } else { + res.status(400).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Bad Request: No valid session ID" }, + id: null, + }); + return; + } + + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("Error handling MCP request:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } +}; + +app.post("/mcp", mcpPostHandler); + +app.get("/mcp", async (req: Request, res: Response) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send("Invalid or missing session ID"); + return; + } + const transport = transports[sessionId]; + await transport.handleRequest(req, res); +}); + +app.delete("/mcp", async (req: Request, res: Response) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send("Invalid or missing session ID"); + return; + } + try { + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + } catch (error) { + console.error("Error handling session termination:", error); + if (!res.headersSent) { + res.status(500).send("Error processing session termination"); + } + } +}); + +app.listen(MCP_PORT, () => { + console.log(`MCP Server listening on http://localhost:${MCP_PORT}/mcp`); +}); + +process.on("SIGINT", async () => { + console.log("Shutting down..."); + for (const sessionId in transports) { + try { + await transports[sessionId].close(); + delete transports[sessionId]; + } catch (error) { + console.error(`Error closing session ${sessionId}:`, error); + } + } + process.exit(0); +}); diff --git a/apps/react/demo/mcp-apps/src/ui-raw.ts b/apps/react/demo/mcp-apps/src/ui-raw.ts new file mode 100644 index 0000000..9f4e445 --- /dev/null +++ b/apps/react/demo/mcp-apps/src/ui-raw.ts @@ -0,0 +1,344 @@ +/** + * @file App that does NOT depend on Apps SDK runtime. + * + * The Raw UI example has no runtime dependency to the Apps SDK + * but still defines types inline for static type safety. + * + * We implement a barebones JSON-RPC message sender/receiver (see `app` object below), + * but without timeouts or runtime type validation of any kind. + */ + +import type { + CallToolRequest, + CallToolResult, + JSONRPCMessage, + LoggingMessageNotification, +} from "@modelcontextprotocol/sdk/types.js"; + +// ============================================================================= +// Inline Type Definitions (from MCP Apps Extension protocol) +// ============================================================================= + +interface Implementation { + name: string; + version: string; +} + +interface McpUiAppCapabilities { + tools?: { listChanged?: boolean }; + experimental?: Record; +} + +interface McpUiHostCapabilities { + openLinks?: Record; + serverTools?: { listChanged?: boolean }; + serverResources?: { listChanged?: boolean }; + logging?: Record; + experimental?: Record; +} + +interface McpUiHostContext { + toolInfo?: { id: string | number; tool: unknown }; + theme?: "light" | "dark" | "system"; + displayMode?: "inline" | "fullscreen" | "pip"; + availableDisplayModes?: string[]; + viewport?: { width: number; height: number; maxHeight?: number; maxWidth?: number }; + locale?: string; + timeZone?: string; + userAgent?: string; + platform?: "web" | "desktop" | "mobile"; + deviceCapabilities?: { touch?: boolean; hover?: boolean }; + safeAreaInsets?: { top: number; right: number; bottom: number; left: number }; +} + +type McpUiInitializeRequest = { + method: "ui/initialize"; + params: { + protocolVersion: string; + appInfo: Implementation; + appCapabilities: McpUiAppCapabilities; + }; +}; + +type McpUiInitializeResult = { + protocolVersion: string; + hostInfo: Implementation; + hostCapabilities: McpUiHostCapabilities; + hostContext?: McpUiHostContext; +}; + +type McpUiInitializedNotification = { + method: "ui/notifications/initialized"; + params: Record; +}; + +type McpUiToolInputNotification = { + method: "ui/notifications/tool-input"; + params: { + arguments?: Record; + }; +}; + +type McpUiToolResultNotification = { + method: "ui/notifications/tool-result"; + params: { + content: Array<{ type: string; text?: string; [key: string]: unknown }>; + structuredContent?: unknown; + isError?: boolean; + }; +}; + +type McpUiHostContextChangedNotification = { + method: "ui/notifications/host-context-changed"; + params: Partial; +}; + +type McpUiSizeChangeNotification = { + method: "ui/notifications/size-change"; + params: { + width: number; + height: number; + }; +}; + +type McpUiMessageRequest = { + method: "ui/message"; + params: { + role: "user" | "assistant"; + content: Array<{ type: string; text?: string; [key: string]: unknown }>; + }; +}; + +type McpUiMessageResult = { + isError?: boolean; +}; + +type McpUiOpenLinkRequest = { + method: "ui/open-link"; + params: { + url: string; + }; +}; + +type McpUiOpenLinkResult = { + isError?: boolean; +}; + +// ============================================================================= +// Barebones JSON-RPC App Implementation +// ============================================================================= + +const app = (() => { + type Sendable = { method: string; params: unknown }; + + let nextId = 1; + + return { + sendRequest({ method, params }: T) { + const id = nextId++; + window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*"); + return new Promise((resolve, reject) => { + window.addEventListener("message", function listener(event) { + const data: JSONRPCMessage = event.data; + if (event.data?.id === id) { + window.removeEventListener("message", listener); + if (event.data?.result) { + resolve(event.data.result as Result); + } else if (event.data?.error) { + reject(new Error(event.data.error)); + } + } else { + reject(new Error(`Unsupported message: ${JSON.stringify(data)}`)); + } + }); + }); + }, + sendNotification({ method, params }: T) { + window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*"); + }, + onNotification( + method: T["method"], + handler: (params: T["params"]) => void, + ) { + window.addEventListener("message", function listener(event) { + if (event.data?.method === method) { + handler(event.data.params); + } + }); + }, + }; +})(); + +// ============================================================================= +// UI Initialization and Event Handlers +// ============================================================================= + +window.addEventListener("load", async () => { + const root = document.getElementById("root")!; + const appendText = (textContent: string, opts: Partial = {}) => { + root.appendChild( + Object.assign(document.createElement("div"), { + textContent, + ...opts, + }), + ); + }; + const appendError = (error: unknown) => + appendText( + `Error: ${error instanceof Error ? error.message : String(error)}`, + { style: "color: red;" } as Partial, + ); + + // Register notification handlers + app.onNotification( + "ui/notifications/tool-input", + async (params) => { + appendText(`Tool call input: ${JSON.stringify(params)}`); + }, + ); + app.onNotification( + "ui/notifications/tool-result", + async (params) => { + appendText(`Tool call result: ${JSON.stringify(params)}`); + }, + ); + app.onNotification( + "ui/notifications/host-context-changed", + async (params) => { + appendText(`Host context changed: ${JSON.stringify(params)}`); + }, + ); + + // Initialize with host + const initializeResult = await app.sendRequest< + McpUiInitializeRequest, + McpUiInitializeResult + >({ + method: "ui/initialize", + params: { + appCapabilities: {}, + appInfo: { name: "My UI", version: "1.0.0" }, + protocolVersion: "2025-06-18", + }, + }); + + appendText(`Initialize result: ${JSON.stringify(initializeResult)}`); + + // Notify host that we're initialized + app.sendNotification({ + method: "ui/notifications/initialized", + params: {}, + }); + + // Set up automatic size reporting + new ResizeObserver(() => { + const rect = ( + document.body.parentElement ?? document.body + ).getBoundingClientRect(); + const width = Math.ceil(rect.width); + const height = Math.ceil(rect.height); + app.sendNotification({ + method: "ui/notifications/size-change", + params: { width, height }, + }); + }).observe(document.body); + + // ============================================================================= + // Interactive Buttons + // ============================================================================= + + // Get Weather Tool button + root.appendChild( + Object.assign(document.createElement("button"), { + textContent: "Get Weather (Tool)", + onclick: async () => { + try { + const result = await app.sendRequest( + { + method: "tools/call", + params: { + name: "get-weather", + arguments: { location: "Tokyo" }, + }, + }, + ); + + appendText(`Weather tool result: ${JSON.stringify(result)}`); + } catch (e) { + appendError(e); + } + }, + }), + ); + + // Notify Cart Updated button + root.appendChild( + Object.assign(document.createElement("button"), { + textContent: "Notify Cart Updated", + onclick: async () => { + app.sendNotification({ + method: "notifications/message", + params: { + level: "info", + data: "cart-updated", + }, + }); + }, + }), + ); + + // Prompt Weather button + root.appendChild( + Object.assign(document.createElement("button"), { + textContent: "Prompt Weather in Tokyo", + onclick: async () => { + try { + const { isError } = await app.sendRequest< + McpUiMessageRequest, + McpUiMessageResult + >({ + method: "ui/message", + params: { + role: "user", + content: [ + { + type: "text", + text: "What is the weather in Tokyo?", + }, + ], + }, + }); + + appendText(`Message result: ${isError ? "error" : "success"}`); + } catch (e) { + appendError(e); + } + }, + }), + ); + + // Open Link button + root.appendChild( + Object.assign(document.createElement("button"), { + textContent: "Open Link to Google", + onclick: async () => { + try { + const { isError } = await app.sendRequest< + McpUiOpenLinkRequest, + McpUiOpenLinkResult + >({ + method: "ui/open-link", + params: { + url: "https://www.google.com", + }, + }); + appendText(`Link result: ${isError ? "error" : "success"}`); + } catch (e) { + appendError(e); + } + }, + }), + ); + + console.log("Initialized with host info:", initializeResult.hostInfo); +}); diff --git a/apps/react/demo/mcp-apps/tsconfig.json b/apps/react/demo/mcp-apps/tsconfig.json new file mode 100644 index 0000000..532f8f5 --- /dev/null +++ b/apps/react/demo/mcp-apps/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "declaration": true + }, + "include": ["server.ts", "src/**/*.ts"] +} diff --git a/apps/react/demo/mcp-apps/ui-raw.html b/apps/react/demo/mcp-apps/ui-raw.html new file mode 100644 index 0000000..cf32084 --- /dev/null +++ b/apps/react/demo/mcp-apps/ui-raw.html @@ -0,0 +1,12 @@ + + + + + + MCP UI Client (Raw) + + +
+ + + diff --git a/apps/react/demo/mcp-apps/vite.config.ts b/apps/react/demo/mcp-apps/vite.config.ts new file mode 100644 index 0000000..66966f4 --- /dev/null +++ b/apps/react/demo/mcp-apps/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +const INPUT = process.env.INPUT; +if (!INPUT) { + throw new Error("INPUT environment variable is not set"); +} + +const isDevelopment = process.env.NODE_ENV === "development"; + +export default defineConfig({ + plugins: [viteSingleFile()], + build: { + sourcemap: isDevelopment ? "inline" : undefined, + cssMinify: !isDevelopment, + minify: !isDevelopment, + rollupOptions: { + input: INPUT, + }, + outDir: "dist", + emptyOutDir: true, + }, +}); diff --git a/apps/react/demo/public/sandbox.html b/apps/react/demo/public/sandbox.html new file mode 100644 index 0000000..44e34cf --- /dev/null +++ b/apps/react/demo/public/sandbox.html @@ -0,0 +1,95 @@ + + + + + + + MCP-UI Proxy + + + + + + diff --git a/apps/react/demo/src/app/page.tsx b/apps/react/demo/src/app/page.tsx index 8fe3d1d..a0eb843 100644 --- a/apps/react/demo/src/app/page.tsx +++ b/apps/react/demo/src/app/page.tsx @@ -10,6 +10,10 @@ import { import type { ToolsMenuItem } from "@copilotkitnext/react"; import { z } from "zod"; import { useMemo, useState } from "react"; +import { + MCPAppsActivityRenderer, + MCPAppsActivityContentSchema, +} from "../components/MCPAppsActivityRenderer"; // Disable static optimization for this page export const dynamic = "force-dynamic"; @@ -44,6 +48,11 @@ export default function Home() { renderToolCalls={[wildcardRenderer]} showDevConsole="auto" renderActivityMessages={[ + { + activityType: "MCP-APPS-EXTENSION", + content: MCPAppsActivityContentSchema, + render: MCPAppsActivityRenderer, + }, { activityType: "a2ui-surface", content: z.any(), diff --git a/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx b/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx new file mode 100644 index 0000000..0e0a7d9 --- /dev/null +++ b/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx @@ -0,0 +1,399 @@ +"use client"; + +import React, { useEffect, useRef, useState, useCallback } from "react"; +import { z } from "zod"; + +// Protocol version supported +const PROTOCOL_VERSION = "2025-06-18"; + +// Zod schema for activity content validation +export const MCPAppsActivityContentSchema = z.object({ + result: z.object({ + content: z.array(z.any()).optional(), + structuredContent: z.any().optional(), + isError: z.boolean().optional(), + }), + resource: z.object({ + uri: z.string(), + mimeType: z.string().optional(), + text: z.string().optional(), + blob: z.string().optional(), + }), + // Server info for proxying requests + serverUrl: z.string().optional(), + serverType: z.enum(["http", "sse"]).optional(), + // Original tool input arguments + toolInput: z.record(z.unknown()).optional(), +}); + +export type MCPAppsActivityContent = z.infer; + +interface JSONRPCRequest { + jsonrpc: "2.0"; + id: string | number; + method: string; + params?: Record; +} + +interface JSONRPCResponse { + jsonrpc: "2.0"; + id: string | number; + result?: unknown; + error?: { code: number; message: string }; +} + +interface JSONRPCNotification { + jsonrpc: "2.0"; + method: string; + params?: Record; +} + +type JSONRPCMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification; + +function isRequest(msg: JSONRPCMessage): msg is JSONRPCRequest { + return "id" in msg && "method" in msg; +} + +function isNotification(msg: JSONRPCMessage): msg is JSONRPCNotification { + return !("id" in msg) && "method" in msg; +} + +/** + * Extract HTML content from a resource object + */ +function extractHtmlFromResource(resource: MCPAppsActivityContent["resource"]): string { + if (resource.text) { + return resource.text; + } + if (resource.blob) { + // Base64 decode + return atob(resource.blob); + } + throw new Error("Resource has no text or blob content"); +} + +/** + * Props for the activity renderer component + * We use generic types to avoid requiring @ag-ui/core as a direct dependency + */ +interface MCPAppsActivityRendererProps { + activityType: string; + content: MCPAppsActivityContent; + message: unknown; // ActivityMessage from @ag-ui/core + agent: unknown | undefined; // AbstractAgent from @ag-ui/client +} + +/** + * MCP Apps Extension Activity Renderer + * + * Renders MCP Apps UI in a sandboxed iframe with full protocol support. + */ +export const MCPAppsActivityRenderer: React.FC = ({ content }) => { + const containerRef = useRef(null); + const iframeRef = useRef(null); + const [iframeReady, setIframeReady] = useState(false); + const [error, setError] = useState(null); + const [iframeSize, setIframeSize] = useState<{ width?: number; height?: number }>({}); + + // Use refs for values that shouldn't trigger re-renders but need latest values + const contentRef = useRef(content); + contentRef.current = content; + + // Callback to send a message to the iframe + const sendToIframe = useCallback((msg: JSONRPCMessage) => { + if (iframeRef.current?.contentWindow) { + console.log("[MCPAppsRenderer] Sending to iframe:", msg); + iframeRef.current.contentWindow.postMessage(msg, "*"); + } + }, []); + + // Callback to send a JSON-RPC response + const sendResponse = useCallback((id: string | number, result: unknown) => { + sendToIframe({ + jsonrpc: "2.0", + id, + result, + }); + }, [sendToIframe]); + + // Callback to send a JSON-RPC error response + const sendErrorResponse = useCallback((id: string | number, code: number, message: string) => { + sendToIframe({ + jsonrpc: "2.0", + id, + error: { code, message }, + }); + }, [sendToIframe]); + + // Callback to send a notification + const sendNotification = useCallback((method: string, params?: Record) => { + sendToIframe({ + jsonrpc: "2.0", + method, + params: params || {}, + }); + }, [sendToIframe]); + + // Effect 1: Setup sandbox proxy iframe and communication + useEffect(() => { + if (!containerRef.current) return; + + let mounted = true; + let messageHandler: ((event: MessageEvent) => void) | null = null; + + const setup = async () => { + try { + // Create sandbox proxy iframe + const iframe = document.createElement("iframe"); + iframe.style.width = "100%"; + iframe.style.height = "300px"; + iframe.style.border = "none"; + iframe.style.backgroundColor = "transparent"; + iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms"); + + // Wait for sandbox proxy to be ready + const sandboxReady = new Promise((resolve) => { + const initialListener = (event: MessageEvent) => { + if (event.source === iframe.contentWindow) { + if (event.data?.method === "ui/notifications/sandbox-proxy-ready") { + window.removeEventListener("message", initialListener); + resolve(); + } + } + }; + window.addEventListener("message", initialListener); + }); + + // Set iframe source and add to DOM + iframe.src = "/sandbox.html"; + iframeRef.current = iframe; + containerRef.current?.appendChild(iframe); + + // Wait for sandbox proxy to signal ready + await sandboxReady; + if (!mounted) return; + + console.log("[MCPAppsRenderer] Sandbox proxy ready"); + + // Setup message handler for JSON-RPC messages from the inner iframe + messageHandler = async (event: MessageEvent) => { + if (event.source !== iframe.contentWindow) return; + + const msg = event.data as JSONRPCMessage; + if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0") return; + + console.log("[MCPAppsRenderer] Received from iframe:", msg); + + // Handle requests (need response) + if (isRequest(msg)) { + switch (msg.method) { + case "ui/initialize": { + // Respond with host capabilities + sendResponse(msg.id, { + protocolVersion: PROTOCOL_VERSION, + hostInfo: { + name: "CopilotKit MCP Apps Host", + version: "1.0.0", + }, + hostCapabilities: { + openLinks: {}, + logging: {}, + }, + hostContext: { + theme: "light", + platform: "web", + }, + }); + break; + } + + case "ui/message": { + // For now, just acknowledge the message + // TODO: Hook into CopilotKit chat to add messages + console.log("[MCPAppsRenderer] ui/message request:", msg.params); + sendResponse(msg.id, { isError: false }); + break; + } + + case "ui/open-link": { + // Open URL in new tab + const url = msg.params?.url as string | undefined; + if (url) { + window.open(url, "_blank", "noopener,noreferrer"); + sendResponse(msg.id, { isError: false }); + } else { + sendErrorResponse(msg.id, -32602, "Missing url parameter"); + } + break; + } + + case "tools/call": { + // Proxy tool call to MCP server + const { serverUrl, serverType } = contentRef.current; + if (!serverUrl) { + sendErrorResponse(msg.id, -32603, "No server URL available for proxying"); + break; + } + + try { + const response = await fetch("/api/copilotkit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + forwardedProps: { + __proxiedMCPRequest: { + serverUrl, + serverType: serverType || "http", + method: "tools/call", + params: msg.params, + }, + }, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + // Parse SSE response to get the result + const text = await response.text(); + // Look for RUN_FINISHED event with result + const lines = text.split("\n"); + let result: unknown = null; + for (const line of lines) { + if (line.startsWith("data: ")) { + try { + const data = JSON.parse(line.slice(6)); + if (data.type === "RUN_FINISHED" && data.result) { + result = data.result; + } + } catch { + // Ignore parse errors + } + } + } + + sendResponse(msg.id, result || {}); + } catch (err) { + console.error("[MCPAppsRenderer] tools/call error:", err); + sendErrorResponse(msg.id, -32603, String(err)); + } + break; + } + + default: + sendErrorResponse(msg.id, -32601, `Method not found: ${msg.method}`); + } + } + + // Handle notifications (no response needed) + if (isNotification(msg)) { + switch (msg.method) { + case "ui/notifications/initialized": { + console.log("[MCPAppsRenderer] Inner iframe initialized"); + if (mounted) { + setIframeReady(true); + } + break; + } + + case "ui/notifications/size-change": { + const { width, height } = msg.params || {}; + console.log("[MCPAppsRenderer] Size change:", { width, height }); + if (mounted) { + setIframeSize({ + width: typeof width === "number" ? width : undefined, + height: typeof height === "number" ? height : undefined, + }); + } + break; + } + + case "notifications/message": { + // Logging notification from the app + console.log("[MCPAppsRenderer] App log:", msg.params); + break; + } + } + } + }; + + window.addEventListener("message", messageHandler); + + // Send the HTML resource to the sandbox proxy + const html = extractHtmlFromResource(content.resource); + sendNotification("ui/notifications/sandbox-resource-ready", { html }); + + } catch (err) { + console.error("[MCPAppsRenderer] Setup error:", err); + if (mounted) { + setError(err instanceof Error ? err : new Error(String(err))); + } + } + }; + + setup(); + + return () => { + mounted = false; + if (messageHandler) { + window.removeEventListener("message", messageHandler); + } + // Remove iframe from DOM + if (iframeRef.current && containerRef.current?.contains(iframeRef.current)) { + containerRef.current.removeChild(iframeRef.current); + } + iframeRef.current = null; + }; + }, [content.resource, sendNotification, sendResponse, sendErrorResponse]); + + // Effect 2: Update iframe size when it changes + useEffect(() => { + if (iframeRef.current) { + if (iframeSize.width !== undefined) { + iframeRef.current.style.width = `${iframeSize.width}px`; + } + if (iframeSize.height !== undefined) { + iframeRef.current.style.height = `${iframeSize.height}px`; + } + } + }, [iframeSize]); + + // Effect 3: Send tool input when iframe ready + useEffect(() => { + if (iframeReady && content.toolInput) { + console.log("[MCPAppsRenderer] Sending tool input:", content.toolInput); + sendNotification("ui/notifications/tool-input", { + arguments: content.toolInput, + }); + } + }, [iframeReady, content.toolInput, sendNotification]); + + // Effect 4: Send tool result when iframe ready + useEffect(() => { + if (iframeReady && content.result) { + console.log("[MCPAppsRenderer] Sending tool result:", content.result); + sendNotification("ui/notifications/tool-result", content.result); + } + }, [iframeReady, content.result, sendNotification]); + + return ( +
+ {error && ( +
+ Error: {error.message} +
+ )} +
+ ); +}; diff --git a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts index 2392216..89a6968 100644 --- a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts +++ b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts @@ -313,12 +313,18 @@ export class MCPAppsExtensionMiddleware extends Middleware { }; subscriber.next(resultEvent); - // Emit activity snapshot with full MCP result and resource + // Emit activity snapshot with full MCP result, resource, and server info const activityEvent: ActivitySnapshotEvent = { type: EventType.ACTIVITY_SNAPSHOT, messageId: randomUUID(), activityType: "MCP-APPS-EXTENSION", - content: { result: mcpResult, resource }, + content: { + result: mcpResult, + resource, + serverUrl: toolInfo.serverConfig.url, + serverType: toolInfo.serverConfig.type, + toolInput: args, + }, replace: true, }; subscriber.next(activityEvent); From ab15b0f82cd8b6a99f5a77e2ff598d795bf6fc58 Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Thu, 4 Dec 2025 18:54:26 +0100 Subject: [PATCH 04/11] Update MCPAppsActivityRenderer.tsx --- .../components/MCPAppsActivityRenderer.tsx | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx b/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx index 0e0a7d9..cdb979a 100644 --- a/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx +++ b/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import { z } from "zod"; +import type { AbstractAgent } from "@ag-ui/client"; // Protocol version supported const PROTOCOL_VERSION = "2025-06-18"; @@ -74,13 +75,12 @@ function extractHtmlFromResource(resource: MCPAppsActivityContent["resource"]): /** * Props for the activity renderer component - * We use generic types to avoid requiring @ag-ui/core as a direct dependency */ interface MCPAppsActivityRendererProps { activityType: string; content: MCPAppsActivityContent; message: unknown; // ActivityMessage from @ag-ui/core - agent: unknown | undefined; // AbstractAgent from @ag-ui/client + agent: AbstractAgent | undefined; } /** @@ -88,7 +88,7 @@ interface MCPAppsActivityRendererProps { * * Renders MCP Apps UI in a sandboxed iframe with full protocol support. */ -export const MCPAppsActivityRenderer: React.FC = ({ content }) => { +export const MCPAppsActivityRenderer: React.FC = ({ content, agent }) => { const containerRef = useRef(null); const iframeRef = useRef(null); const [iframeReady, setIframeReady] = useState(false); @@ -99,6 +99,10 @@ export const MCPAppsActivityRenderer: React.FC = ( const contentRef = useRef(content); contentRef.current = content; + // Store agent in a ref for use in async handlers + const agentRef = useRef(agent); + agentRef.current = agent; + // Callback to send a message to the iframe const sendToIframe = useCallback((msg: JSONRPCMessage) => { if (iframeRef.current?.contentWindow) { @@ -228,52 +232,36 @@ export const MCPAppsActivityRenderer: React.FC = ( } case "tools/call": { - // Proxy tool call to MCP server + // Proxy tool call to MCP server via agent.runAgent() const { serverUrl, serverType } = contentRef.current; + const currentAgent = agentRef.current; + if (!serverUrl) { sendErrorResponse(msg.id, -32603, "No server URL available for proxying"); break; } + if (!currentAgent) { + sendErrorResponse(msg.id, -32603, "No agent available for proxying"); + break; + } + try { - const response = await fetch("/api/copilotkit", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - forwardedProps: { - __proxiedMCPRequest: { - serverUrl, - serverType: serverType || "http", - method: "tools/call", - params: msg.params, - }, + // Use agent.runAgent() to proxy the MCP request + // The middleware will intercept forwardedProps.__proxiedMCPRequest + const runResult = await currentAgent.runAgent({ + forwardedProps: { + __proxiedMCPRequest: { + serverUrl, + serverType: serverType || "http", + method: "tools/call", + params: msg.params, }, - }), + }, }); - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); - } - - // Parse SSE response to get the result - const text = await response.text(); - // Look for RUN_FINISHED event with result - const lines = text.split("\n"); - let result: unknown = null; - for (const line of lines) { - if (line.startsWith("data: ")) { - try { - const data = JSON.parse(line.slice(6)); - if (data.type === "RUN_FINISHED" && data.result) { - result = data.result; - } - } catch { - // Ignore parse errors - } - } - } - - sendResponse(msg.id, result || {}); + // The result from runAgent contains the MCP response + sendResponse(msg.id, runResult.result || {}); } catch (err) { console.error("[MCPAppsRenderer] tools/call error:", err); sendErrorResponse(msg.id, -32603, String(err)); From 2916c0f5ec45ee71e8a5b3bd1dfb84e2e4684a8a Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Fri, 5 Dec 2025 10:42:33 +0100 Subject: [PATCH 05/11] hook up demo server --- apps/react/demo/mcp-apps/package.json | 3 +- apps/react/demo/mcp-apps/src/ui-raw.ts | 16 +- .../app/api/copilotkit/[[...slug]]/route.ts | 2 +- apps/react/demo/src/app/page.tsx | 5 +- .../mcp-apps-extension-middleware.test.ts | 6 +- .../demo/src/lib/__tests__/test-utils.ts | 5 +- pnpm-lock.yaml | 199 +++++++++++++++++- 7 files changed, 210 insertions(+), 26 deletions(-) diff --git a/apps/react/demo/mcp-apps/package.json b/apps/react/demo/mcp-apps/package.json index 143eacc..9797e77 100644 --- a/apps/react/demo/mcp-apps/package.json +++ b/apps/react/demo/mcp-apps/package.json @@ -13,12 +13,13 @@ "zod": "^3.25.0" }, "devDependencies": { + "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.0.0", "cors": "^2.8.5", "express": "^5.1.0", - "typescript": "^5.7.2", "tsx": "^4.0.0", + "typescript": "^5.7.2", "vite": "^6.0.0", "vite-plugin-singlefile": "^2.3.0" } diff --git a/apps/react/demo/mcp-apps/src/ui-raw.ts b/apps/react/demo/mcp-apps/src/ui-raw.ts index 9f4e445..8a9ad27 100644 --- a/apps/react/demo/mcp-apps/src/ui-raw.ts +++ b/apps/react/demo/mcp-apps/src/ui-raw.ts @@ -175,18 +175,18 @@ const app = (() => { window.addEventListener("load", async () => { const root = document.getElementById("root")!; - const appendText = (textContent: string, opts: Partial = {}) => { - root.appendChild( - Object.assign(document.createElement("div"), { - textContent, - ...opts, - }), - ); + const appendText = (textContent: string, style?: string) => { + const div = document.createElement("div"); + div.textContent = textContent; + if (style) { + div.setAttribute("style", style); + } + root.appendChild(div); }; const appendError = (error: unknown) => appendText( `Error: ${error instanceof Error ? error.message : String(error)}`, - { style: "color: red;" } as Partial, + "color: red;", ); // Register notification handlers diff --git a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts index ea82346..3ba3b16 100644 --- a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts +++ b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts @@ -22,7 +22,7 @@ const agent = new BasicAgent({ temperature: 0.7, }).use(new MCPAppsExtensionMiddleware({ mcpServers: [ - // Example: { type: "sse", url: "http://localhost:3001/sse" } + { type: "http", url: "http://localhost:3001/mcp" } ], })); diff --git a/apps/react/demo/src/app/page.tsx b/apps/react/demo/src/app/page.tsx index a0eb843..bb74d40 100644 --- a/apps/react/demo/src/app/page.tsx +++ b/apps/react/demo/src/app/page.tsx @@ -10,10 +10,7 @@ import { import type { ToolsMenuItem } from "@copilotkitnext/react"; import { z } from "zod"; import { useMemo, useState } from "react"; -import { - MCPAppsActivityRenderer, - MCPAppsActivityContentSchema, -} from "../components/MCPAppsActivityRenderer"; +import { MCPAppsActivityRenderer, MCPAppsActivityContentSchema } from "../components/MCPAppsActivityRenderer"; // Disable static optimization for this page export const dynamic = "force-dynamic"; diff --git a/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts b/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts index e34078c..acb0860 100644 --- a/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts +++ b/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts @@ -1,6 +1,6 @@ -import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest"; -import { Observable } from "rxjs"; -import { EventType, BaseEvent, RunAgentInput } from "@ag-ui/client"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { EventType, BaseEvent } from "@ag-ui/client"; import { MCPAppsExtensionMiddleware, MCPClientConfig, diff --git a/apps/react/demo/src/lib/__tests__/test-utils.ts b/apps/react/demo/src/lib/__tests__/test-utils.ts index f217b72..c505f4f 100644 --- a/apps/react/demo/src/lib/__tests__/test-utils.ts +++ b/apps/react/demo/src/lib/__tests__/test-utils.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { vi } from "vitest"; -import { AbstractAgent, BaseEvent, EventType, RunAgentInput, Message, Tool, ToolCall, AssistantMessage } from "@ag-ui/client"; +import { AbstractAgent, BaseEvent, EventType, RunAgentInput, Message, Tool, AssistantMessage } from "@ag-ui/client"; import { Observable } from "rxjs"; import { firstValueFrom, toArray } from "rxjs"; @@ -120,7 +121,7 @@ export class AsyncMockAgent extends AbstractAgent { this.delayMs = delayMs; } - run(input: RunAgentInput): Observable { + run(_input: RunAgentInput): Observable { return new Observable((subscriber) => { let cancelled = false; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17abb96..d4362f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,6 +345,43 @@ importers: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + apps/react/demo/mcp-apps: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.22.0 + version: 1.24.3(@cfworker/json-schema@4.1.1)(zod@3.25.75) + zod: + specifier: ^3.25.0 + version: 3.25.75 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^5.0.0 + version: 5.0.6 + '@types/node': + specifier: ^22.0.0 + version: 22.15.3 + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^5.1.0 + version: 5.1.0 + tsx: + specifier: ^4.0.0 + version: 4.20.5 + typescript: + specifier: ^5.7.2 + version: 5.8.2 + vite: + specifier: ^6.0.0 + version: 6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + vite-plugin-singlefile: + specifier: ^2.3.0 + version: 2.3.0(rollup@4.45.1)(vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0)) + apps/react/storybook: dependencies: '@storybook/addon-essentials': @@ -3466,6 +3503,16 @@ packages: resolution: {integrity: sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==} engines: {node: '>=18'} + '@modelcontextprotocol/sdk@1.24.3': + resolution: {integrity: sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -5348,9 +5395,15 @@ packages: '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@5.1.0': + resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -5463,6 +5516,9 @@ packages: '@types/serve-static@1.15.8': resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -8689,6 +8745,9 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -12075,6 +12134,13 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-plugin-singlefile@2.3.0: + resolution: {integrity: sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==} + engines: {node: '>18.0.0'} + peerDependencies: + rollup: ^4.44.1 + vite: ^5.4.11 || ^6.0.0 || ^7.0.0 + vite@5.4.19: resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12106,6 +12172,46 @@ packages: terser: optional: true + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@7.0.5: resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -12572,6 +12678,11 @@ packages: peerDependencies: zod: ^3.24.1 + zod-to-json-schema@3.25.0: + resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} + peerDependencies: + zod: ^3.25 || ^4 + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -15781,7 +15892,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.30 '@jridgewell/remapping@2.3.5': dependencies: @@ -16267,6 +16378,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.24.3(@cfworker/json-schema@4.1.1)(zod@3.25.75)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.1(express@5.1.0) + jose: 6.1.3 + pkce-challenge: 5.0.0 + raw-body: 3.0.1 + zod: 3.25.75 + zod-to-json-schema: 3.25.0(zod@3.25.75) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -18326,6 +18458,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.5 + '@types/express-serve-static-core@5.1.0': + dependencies: + '@types/node': 22.15.3 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + '@types/express@4.17.23': dependencies: '@types/body-parser': 1.19.6 @@ -18333,6 +18472,12 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.0 + '@types/serve-static': 2.2.0 + '@types/geojson@7946.0.16': {} '@types/hast@3.0.4': @@ -18444,6 +18589,11 @@ snapshots: '@types/node': 22.15.3 '@types/send': 0.17.5 + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.15.3 + '@types/sockjs@0.3.36': dependencies: '@types/node': 22.15.3 @@ -19415,7 +19565,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -20295,6 +20445,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -21191,7 +21345,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -21348,7 +21502,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -22516,6 +22670,8 @@ snapshots: jiti@2.5.1: {} + jose@6.1.3: {} + joycon@3.1.1: {} js-tiktoken@1.0.21: @@ -25372,7 +25528,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -25526,7 +25682,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -26390,7 +26546,7 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 tinypool@1.1.1: {} @@ -26999,6 +27155,12 @@ snapshots: - tsx - yaml + vite-plugin-singlefile@2.3.0(rollup@4.45.1)(vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0)): + dependencies: + micromatch: 4.0.8 + rollup: 4.45.1 + vite: 6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) + vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6): dependencies: esbuild: 0.21.5 @@ -27039,6 +27201,25 @@ snapshots: sass: 1.90.0 terser: 5.43.1 + vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): + dependencies: + esbuild: 0.25.6 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.1 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.15.3 + fsevents: 2.3.3 + jiti: 2.5.1 + less: 4.4.1 + lightningcss: 1.30.1 + sass: 1.90.0 + terser: 5.43.1 + tsx: 4.20.5 + yaml: 2.8.0 + vite@7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): dependencies: esbuild: 0.25.6 @@ -27793,6 +27974,10 @@ snapshots: dependencies: zod: 3.25.75 + zod-to-json-schema@3.25.0(zod@3.25.75): + dependencies: + zod: 3.25.75 + zod@3.23.8: {} zod@3.25.75: {} From c3b00b5e6963ecef534f48043cddf904246f6004 Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Fri, 5 Dec 2025 11:03:21 +0100 Subject: [PATCH 06/11] properly clone agent --- apps/react/demo/src/lib/mcp-apps-extension-middleware.ts | 1 + packages/agent/src/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts index 89a6968..70e22ef 100644 --- a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts +++ b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts @@ -146,6 +146,7 @@ export class MCPAppsExtensionMiddleware extends Middleware { ...input, tools: [...input.tools, ...uiToolInfos.map((info) => info.tool)], }; + // Use runNextWithState to get state with each event return this.processStream(this.runNextWithState(enhancedInput, next), uiToolsMap); }) diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 46b6429..c4a27ff 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -951,7 +951,11 @@ export class BasicAgent extends AbstractAgent { } clone() { - return new BasicAgent(this.config); + const cloned = new BasicAgent(this.config); + // Copy middlewares from parent class + // @ts-expect-error - accessing protected property from parent + cloned.middlewares = [...this.middlewares]; + return cloned; } abortRun(): void { From dd3c380d5d8592cb7921272dd49570d3eabdfb55 Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Fri, 5 Dec 2025 11:49:06 +0100 Subject: [PATCH 07/11] move middleware to AG-UI project --- .../app/api/copilotkit/[[...slug]]/route.ts | 15 +- .../mcp-apps-extension-middleware.test.ts | 2144 ----------------- .../demo/src/lib/__tests__/test-utils.ts | 404 ---- .../src/lib/mcp-apps-extension-middleware.ts | 550 ----- 4 files changed, 9 insertions(+), 3104 deletions(-) delete mode 100644 apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts delete mode 100644 apps/react/demo/src/lib/__tests__/test-utils.ts delete mode 100644 apps/react/demo/src/lib/mcp-apps-extension-middleware.ts diff --git a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts index 3ba3b16..30ecac4 100644 --- a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts +++ b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts @@ -1,7 +1,8 @@ import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; import { handle } from "hono/vercel"; import { BasicAgent } from "@copilotkitnext/agent"; -import { MCPAppsExtensionMiddleware } from "@/lib/mcp-apps-extension-middleware"; +// TODO: Re-enable once @ag-ui/mcp-apps-middleware is published +// import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware"; const determineModel = () => { if (process.env.OPENAI_API_KEY?.trim()) { @@ -20,11 +21,13 @@ const agent = new BasicAgent({ model: determineModel(), prompt: "You are a helpful AI assistant.", temperature: 0.7, -}).use(new MCPAppsExtensionMiddleware({ - mcpServers: [ - { type: "http", url: "http://localhost:3001/mcp" } - ], -})); +}); +// TODO: Re-enable once @ag-ui/mcp-apps-middleware is published +// .use(new MCPAppsMiddleware({ +// mcpServers: [ +// { type: "http", url: "http://localhost:3001/mcp" } +// ], +// })); const honoRuntime = new CopilotRuntime({ agents: { diff --git a/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts b/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts deleted file mode 100644 index acb0860..0000000 --- a/apps/react/demo/src/lib/__tests__/mcp-apps-extension-middleware.test.ts +++ /dev/null @@ -1,2144 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { EventType, BaseEvent } from "@ag-ui/client"; -import { - MCPAppsExtensionMiddleware, - MCPClientConfig, - ProxiedMCPRequest, -} from "../mcp-apps-extension-middleware"; -import { - MockAgent, - AsyncMockAgent, - ErrorMockAgent, - createRunAgentInput, - createRunStartedEvent, - createRunFinishedEvent, - createTextMessageStartEvent, - createTextMessageContentEvent, - createTextMessageEndEvent, - createMCPToolWithUI, - createMCPToolWithoutUI, - createMCPToolWithEmptyMeta, - createAssistantMessageWithToolCalls, - createToolResultMessage, - createAGUITool, - collectEvents, - createMCPToolCallResult, - createMCPResourceResult, -} from "./test-utils"; - -// Create mock functions that will be referenced in the mock factory -const mockConnect = vi.fn(); -const mockClose = vi.fn(); -const mockListTools = vi.fn(); -const mockCallTool = vi.fn(); -const mockReadResource = vi.fn(); -const mockNotification = vi.fn(); -const mockPing = vi.fn(); - -// Track Client constructor calls -const mockClientConstructorCalls: Array<{ clientInfo: unknown; options: unknown }> = []; - -// Mock the MCP SDK modules - using factory that returns a function returning our mock -vi.mock("@modelcontextprotocol/sdk/client/index.js", () => { - return { - Client: class MockClient { - connect = mockConnect; - close = mockClose; - listTools = mockListTools; - callTool = mockCallTool; - readResource = mockReadResource; - notification = mockNotification; - ping = mockPing; - - constructor(clientInfo: unknown, options: unknown) { - mockClientConstructorCalls.push({ clientInfo, options }); - } - }, - }; -}); - -vi.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({ - SSEClientTransport: vi.fn().mockImplementation(() => ({ type: "sse" })), -})); - -vi.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ - StreamableHTTPClientTransport: vi.fn().mockImplementation(() => ({ type: "http" })), -})); - -// Mock crypto.randomUUID -vi.mock("crypto", () => ({ - randomUUID: vi.fn(() => `mock-uuid-${Math.random().toString(36).substr(2, 9)}`), -})); - -describe("MCPAppsExtensionMiddleware", () => { - beforeEach(() => { - // Reset all mocks before each test - vi.clearAllMocks(); - - // Clear constructor calls tracking - mockClientConstructorCalls.length = 0; - - // Set default mock implementations - mockConnect.mockResolvedValue(undefined); - mockClose.mockResolvedValue(undefined); - mockListTools.mockResolvedValue({ tools: [] }); - mockCallTool.mockResolvedValue({ content: [] }); - mockReadResource.mockResolvedValue({ contents: [] }); - mockNotification.mockResolvedValue(undefined); - mockPing.mockResolvedValue({}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - // ============================================================================= - // 1. Constructor & Configuration Tests - // ============================================================================= - describe("Constructor & Configuration", () => { - it("creates instance with empty config", () => { - const middleware = new MCPAppsExtensionMiddleware(); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - - it("creates instance with empty object config", () => { - const middleware = new MCPAppsExtensionMiddleware({}); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - - it("creates instance with HTTP server config", () => { - const config = { - mcpServers: [{ type: "http" as const, url: "http://localhost:3000" }], - }; - const middleware = new MCPAppsExtensionMiddleware(config); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - - it("creates instance with SSE server config", () => { - const config = { - mcpServers: [{ type: "sse" as const, url: "http://localhost:3000/sse" }], - }; - const middleware = new MCPAppsExtensionMiddleware(config); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - - it("creates instance with SSE server config including headers", () => { - const config = { - mcpServers: [ - { - type: "sse" as const, - url: "http://localhost:3000/sse", - headers: { Authorization: "Bearer token" }, - }, - ], - }; - const middleware = new MCPAppsExtensionMiddleware(config); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - - it("creates instance with multiple server configs", () => { - const config = { - mcpServers: [ - { type: "http" as const, url: "http://localhost:3001" }, - { type: "sse" as const, url: "http://localhost:3002/sse" }, - ], - }; - const middleware = new MCPAppsExtensionMiddleware(config); - expect(middleware).toBeInstanceOf(MCPAppsExtensionMiddleware); - }); - }); - - // ============================================================================= - // 2. Pass-Through Behavior (No MCP Servers) - // ============================================================================= - describe("Pass-Through Behavior (No MCP Servers)", () => { - it("passes through when mcpServers is empty array", async () => { - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [] }); - const agent = new MockAgent([ - createRunStartedEvent(), - createRunFinishedEvent(), - ]); - - const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(events).toHaveLength(2); - expect(events[0].type).toBe(EventType.RUN_STARTED); - expect(events[1].type).toBe(EventType.RUN_FINISHED); - }); - - it("passes through when mcpServers is undefined", async () => { - const middleware = new MCPAppsExtensionMiddleware({}); - const agent = new MockAgent([ - createRunStartedEvent(), - createTextMessageStartEvent(), - createTextMessageContentEvent(), - createTextMessageEndEvent(), - createRunFinishedEvent(), - ]); - - const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(events.length).toBeGreaterThanOrEqual(2); - expect(events[0].type).toBe(EventType.RUN_STARTED); - expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - - it("events flow through unchanged when no servers configured", async () => { - const middleware = new MCPAppsExtensionMiddleware(); - const inputEvents = [ - createRunStartedEvent("run-1", "thread-1"), - createTextMessageStartEvent("msg-1"), - createTextMessageContentEvent("msg-1", "Hello World"), - createTextMessageEndEvent("msg-1"), - createRunFinishedEvent("run-1", "thread-1", { success: true }), - ]; - const agent = new MockAgent(inputEvents); - - const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); - - // The middleware uses runNextWithState which transforms chunks - expect(events.length).toBeGreaterThanOrEqual(2); - expect(events[0].type).toBe(EventType.RUN_STARTED); - }); - - it("observable completes correctly with no servers", async () => { - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - let completed = false; - await new Promise((resolve) => { - middleware.run(createRunAgentInput(), agent).subscribe({ - complete: () => { - completed = true; - resolve(); - }, - }); - }); - - expect(completed).toBe(true); - }); - - it("error propagation works with no servers", async () => { - const middleware = new MCPAppsExtensionMiddleware(); - const testError = new Error("Test error"); - const agent = new ErrorMockAgent(testError); - - let caughtError: Error | null = null; - await new Promise((resolve) => { - middleware.run(createRunAgentInput(), agent).subscribe({ - error: (err) => { - caughtError = err; - resolve(); - }, - complete: () => resolve(), - }); - }); - - expect(caughtError).toBe(testError); - }); - }); - - // ============================================================================= - // 3. Tool Discovery Tests - // ============================================================================= - describe("Tool Discovery", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - const sseServerConfig: MCPClientConfig = { type: "sse", url: "http://localhost:3001/sse" }; - - it("connects to MCP server with correct capabilities", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(mockClientConstructorCalls).toHaveLength(1); - expect(mockClientConstructorCalls[0].clientInfo).toEqual({ - name: "mcp-apps-middleware", - version: "1.0.0", - }); - expect(mockClientConstructorCalls[0].options).toMatchObject({ - capabilities: { - extensions: { - "io.modelcontextprotocol/ui": { - mimeTypes: ["text/html+mcp"], - }, - }, - }, - }); - }); - - it("calls listTools on connected client", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(mockConnect).toHaveBeenCalled(); - expect(mockListTools).toHaveBeenCalled(); - }); - - it("filters tools by ui/resourceUri presence", async () => { - mockListTools.mockResolvedValue({ - tools: [ - createMCPToolWithUI("ui-tool", "ui://server/dashboard"), - createMCPToolWithoutUI("non-ui-tool"), - createMCPToolWithEmptyMeta("meta-but-no-ui"), - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - // Agent should receive enhanced input with only the UI tool - expect(agent.runCalls).toHaveLength(1); - const enhancedTools = agent.runCalls[0].tools; - expect(enhancedTools).toHaveLength(1); - expect(enhancedTools[0].name).toBe("ui-tool"); - }); - - it("converts MCP tools to AG-UI Tool format correctly", async () => { - mockListTools.mockResolvedValue({ - tools: [ - { - name: "test-tool", - description: "Test tool description", - inputSchema: { type: "object", properties: { foo: { type: "string" } } }, - _meta: { "ui/resourceUri": "ui://server/test" }, - }, - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - const enhancedTools = agent.runCalls[0].tools; - expect(enhancedTools[0].name).toBe("test-tool"); - expect(enhancedTools[0].description).toContain("Test tool description"); - expect(enhancedTools[0].parameters).toEqual({ - type: "object", - properties: { foo: { type: "string" } }, - }); - }); - - it("stores ui/resourceUri in description", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard", "Original description")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - const enhancedTools = agent.runCalls[0].tools; - expect(enhancedTools[0].description).toContain("Original description"); - expect(enhancedTools[0].description).toContain("[UI Resource: ui://server/dashboard]"); - }); - - it("handles tools without _meta", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithoutUI("no-meta-tool")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - // No UI tools should be added - expect(agent.runCalls[0].tools).toHaveLength(0); - }); - - it("handles empty tools list from server", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(agent.runCalls[0].tools).toHaveLength(0); - }); - - it("handles server connection failures gracefully", async () => { - const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - mockConnect.mockRejectedValue(new Error("Connection failed")); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - // Should not throw, should continue - const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(events.length).toBeGreaterThanOrEqual(2); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining("Failed to fetch tools from MCP server"), - expect.any(Error) - ); - - consoleErrorSpy.mockRestore(); - }); - - it("closes client connection after fetching tools", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(mockClose).toHaveBeenCalled(); - }); - - it("works with HTTP transport", async () => { - const { StreamableHTTPClientTransport } = await import( - "@modelcontextprotocol/sdk/client/streamableHttp.js" - ); - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ - mcpServers: [{ type: "http", url: "http://localhost:3000" }], - }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000")); - }); - - it("works with SSE transport", async () => { - const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js"); - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ - mcpServers: [{ type: "sse", url: "http://localhost:3001/sse" }], - }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(SSEClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3001/sse")); - }); - - it("aggregates tools from multiple servers", async () => { - // We need to track which server each call is for - let callCount = 0; - mockListTools.mockImplementation(() => { - callCount++; - if (callCount === 1) { - return Promise.resolve({ - tools: [createMCPToolWithUI("tool-1", "ui://server1/tool1")], - }); - } - return Promise.resolve({ - tools: [createMCPToolWithUI("tool-2", "ui://server2/tool2")], - }); - }); - - const middleware = new MCPAppsExtensionMiddleware({ - mcpServers: [httpServerConfig, sseServerConfig], - }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(agent.runCalls[0].tools).toHaveLength(2); - expect(agent.runCalls[0].tools.map((t) => t.name)).toContain("tool-1"); - expect(agent.runCalls[0].tools.map((t) => t.name)).toContain("tool-2"); - }); - }); - - // ============================================================================= - // 4. Tool Injection Tests - // ============================================================================= - describe("Tool Injection", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("merges UI tools with existing input tools", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const existingTool = createAGUITool("existing-tool"); - const input = createRunAgentInput({ tools: [existingTool] }); - - await collectEvents(middleware.run(input, agent)); - - expect(agent.runCalls[0].tools).toHaveLength(2); - expect(agent.runCalls[0].tools[0].name).toBe("existing-tool"); - expect(agent.runCalls[0].tools[1].name).toBe("ui-tool"); - }); - - it("preserves original input tools", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const originalTools = [ - createAGUITool("tool-a", "Description A"), - createAGUITool("tool-b", "Description B"), - ]; - const input = createRunAgentInput({ tools: originalTools }); - - await collectEvents(middleware.run(input, agent)); - - const resultTools = agent.runCalls[0].tools; - expect(resultTools[0]).toEqual(originalTools[0]); - expect(resultTools[1]).toEqual(originalTools[1]); - }); - - it("passes enhanced input to next agent", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/dashboard")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const input = createRunAgentInput({ - threadId: "custom-thread", - runId: "custom-run", - state: { key: "value" }, - }); - - await collectEvents(middleware.run(input, agent)); - - expect(agent.runCalls[0].threadId).toBe("custom-thread"); - expect(agent.runCalls[0].runId).toBe("custom-run"); - expect(agent.runCalls[0].state).toEqual({ key: "value" }); - expect(agent.runCalls[0].tools.length).toBe(1); - }); - }); - - // ============================================================================= - // 5. Event Stream Processing Tests - // ============================================================================= - describe("Event Stream Processing", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("emits non-RUN_FINISHED events immediately", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([ - createRunStartedEvent(), - createTextMessageStartEvent(), - createTextMessageContentEvent(), - createTextMessageEndEvent(), - createRunFinishedEvent(), - ]); - - const receivedEvents: BaseEvent[] = []; - await new Promise((resolve) => { - middleware.run(createRunAgentInput(), agent).subscribe({ - next: (event) => receivedEvents.push(event), - complete: () => resolve(), - }); - }); - - // First event should be RUN_STARTED - expect(receivedEvents[0].type).toBe(EventType.RUN_STARTED); - // Last event should be RUN_FINISHED - expect(receivedEvents[receivedEvents.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - - it("holds back RUN_FINISHED event until stream ends", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new AsyncMockAgent( - [createRunStartedEvent(), createRunFinishedEvent()], - 10 - ); - - const receivedEvents: BaseEvent[] = []; - let finishedReceived = false; - - await new Promise((resolve) => { - middleware.run(createRunAgentInput(), agent).subscribe({ - next: (event) => { - receivedEvents.push(event); - if (event.type === EventType.RUN_FINISHED) { - finishedReceived = true; - } - }, - complete: () => resolve(), - }); - }); - - expect(finishedReceived).toBe(true); - expect(receivedEvents[receivedEvents.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - - it("handles error events correctly", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const testError = new Error("Stream error"); - const agent = new ErrorMockAgent(testError); - - let caughtError: Error | null = null; - await new Promise((resolve) => { - middleware.run(createRunAgentInput(), agent).subscribe({ - error: (err) => { - caughtError = err; - resolve(); - }, - complete: () => resolve(), - }); - }); - - expect(caughtError).toBe(testError); - }); - - it("subscription cleanup works", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new AsyncMockAgent( - [ - createRunStartedEvent(), - createTextMessageStartEvent(), - createTextMessageContentEvent(), - createRunFinishedEvent(), - ], - 50 - ); - - let eventCount = 0; - const subscription = middleware.run(createRunAgentInput(), agent).subscribe({ - next: () => { - eventCount++; - if (eventCount === 2) { - subscription.unsubscribe(); - } - }, - }); - - // Wait a bit to ensure no more events are received after unsubscribe - await new Promise((resolve) => setTimeout(resolve, 200)); - - expect(eventCount).toBe(2); - }); - }); - - // ============================================================================= - // 6. Pending Tool Call Detection Tests - // ============================================================================= - describe("Pending Tool Call Detection", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("processes pending UI tool calls on stream completion", async () => { - const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Weather result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://weather/dashboard", "text/html+mcp", "Dashboard") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - // Create an assistant message with a tool call that won't have a result - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-weather", args: { city: "London" }, id: "tc-1" }, - ]); - - // Agent emits events but doesn't emit a tool result - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - // Set up input with the assistant message containing the tool call - const input = createRunAgentInput({ - messages: [assistantMsg], - }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should have emitted TOOL_CALL_RESULT and ACTIVITY_SNAPSHOT events - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - - expect(toolResultEvents.length).toBe(1); - expect(activityEvents.length).toBe(1); - }); - - it("identifies resolved tool calls (role: tool messages)", async () => { - const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - // Create assistant message with tool call AND a tool result message - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-weather", args: { city: "London" }, id: "tc-1" }, - ]); - const toolResultMsg = createToolResultMessage("tc-1", "Already resolved"); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const input = createRunAgentInput({ - messages: [assistantMsg, toolResultMsg], - }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should NOT emit additional TOOL_CALL_RESULT since it's already resolved - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(0); - }); - - it("handles empty message arrays", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/tool")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const input = createRunAgentInput({ messages: [] }); - const events = await collectEvents(middleware.run(input, agent)); - - // Should complete without errors - expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - - it("handles messages without tool calls", async () => { - mockListTools.mockResolvedValue({ - tools: [createMCPToolWithUI("ui-tool", "ui://server/tool")], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const input = createRunAgentInput({ - messages: [ - { id: "msg-1", role: "user", content: "Hello" }, - { id: "msg-2", role: "assistant", content: "Hi there" }, - ], - }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should complete without emitting tool results - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(0); - }); - - it("handles multiple tool calls per message", async () => { - const uiTool1 = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); - const uiTool2 = createMCPToolWithUI("ui-stocks", "ui://stocks/chart"); - mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://test", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-weather", args: {}, id: "tc-1" }, - { name: "ui-stocks", args: {}, id: "tc-2" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(2); - }); - }); - - // ============================================================================= - // 7. Tool Execution Tests - // ============================================================================= - describe("Tool Execution", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("passes correct tool name and arguments", async () => { - const uiTool = createMCPToolWithUI("ui-weather", "ui://weather/dashboard"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Sunny" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://weather/dashboard", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-weather", args: { city: "London", units: "metric" }, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - await collectEvents(middleware.run(input, agent)); - - expect(mockCallTool).toHaveBeenCalledWith({ - name: "ui-weather", - arguments: { city: "London", units: "metric" }, - }); - }); - - it("returns raw MCP result", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - - const mcpResult = createMCPToolCallResult([ - { type: "text", text: "First" }, - { type: "text", text: "Second" }, - ]); - mockCallTool.mockResolvedValue(mcpResult); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect(activityEvent).toBeDefined(); - expect((activityEvent as any).content.result).toEqual(mcpResult); - }); - - it("handles tool execution errors", async () => { - const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockRejectedValue(new Error("Execution failed")); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should emit error tool result - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(1); - expect((toolResultEvents[0] as any).content).toContain("error"); - - consoleErrorSpy.mockRestore(); - }); - - it("closes connection after execution", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - await collectEvents(middleware.run(input, agent)); - - // close should be called multiple times (once for listTools, once for callTool, once for readResource) - expect(mockClose).toHaveBeenCalled(); - }); - }); - - // ============================================================================= - // 8. Resource Reading Tests - // ============================================================================= - describe("Resource Reading", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("reads resource by URI", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/dashboard"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/dashboard", "text/html+mcp", "Dashboard") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - await collectEvents(middleware.run(input, agent)); - - expect(mockReadResource).toHaveBeenCalledWith({ - uri: "ui://server/dashboard", - }); - }); - - it("returns first content item", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - - const resourceContent = { - uri: "ui://server/tool", - mimeType: "text/html+mcp", - text: "First", - }; - mockReadResource.mockResolvedValue({ - contents: [ - resourceContent, - { uri: "ui://server/tool2", mimeType: "text/html+mcp", text: "Second" }, - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).content.resource).toEqual(resourceContent); - }); - - it("handles read errors", async () => { - const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockRejectedValue(new Error("Resource not found")); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should emit error result - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(1); - expect((toolResultEvents[0] as any).content).toContain("error"); - - consoleErrorSpy.mockRestore(); - }); - }); - - // ============================================================================= - // 9. Tool Result Events Tests - // ============================================================================= - describe("Tool Result Events", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("emits TOOL_CALL_RESULT event with correct toolCallId", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "specific-tc-id" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).toolCallId).toBe("specific-tc-id"); - }); - - it("extracts text content from MCP result", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([ - { type: "text", text: "Line 1" }, - { type: "text", text: "Line 2" }, - ]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).content).toBe("Line 1\nLine 2"); - }); - - it("falls back to JSON.stringify for non-text content", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "image", data: "base64data" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).content).toContain("image"); - expect((toolResultEvent as any).content).toContain("base64data"); - }); - - it("emits ACTIVITY_SNAPSHOT with MCP result and resource", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - - const mcpResult = createMCPToolCallResult([{ type: "text", text: "Result" }]); - const resourceContent = { - uri: "ui://server/tool", - mimeType: "text/html+mcp", - text: "", - }; - - mockCallTool.mockResolvedValue(mcpResult); - mockReadResource.mockResolvedValue({ contents: [resourceContent] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect(activityEvent).toBeDefined(); - expect((activityEvent as any).content.result).toEqual(mcpResult); - expect((activityEvent as any).content.resource).toEqual(resourceContent); - }); - - it("sets activityType to MCP-APPS-EXTENSION", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).activityType).toBe("MCP-APPS-EXTENSION"); - }); - - it("sets replace: true on activity snapshot", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).replace).toBe(true); - }); - }); - - // ============================================================================= - // 10. Content Extraction Tests - // ============================================================================= - describe("Content Extraction", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("joins multiple text items with newline", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([ - { type: "text", text: "First line" }, - { type: "text", text: "Second line" }, - { type: "text", text: "Third line" }, - ]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).content).toBe("First line\nSecond line\nThird line"); - }); - - it("handles empty content array", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue({ content: [] }); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).content).toBe("[]"); - }); - - it("handles mixed content types", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([ - { type: "text", text: "Text content" }, - { type: "image", data: "imagedata" }, - { type: "text", text: "More text" }, - ]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - // Should only extract text items - expect((toolResultEvent as any).content).toBe("Text content\nMore text"); - }); - }); - - // ============================================================================= - // 11. Proxied MCP Request Mode Tests - // ============================================================================= - describe("Proxied MCP Request Mode", () => { - it("detects proxied request in forwardedProps", async () => { - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should bypass normal agent flow (agent.run should not be called with our input) - expect(events[0].type).toBe(EventType.RUN_STARTED); - expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - - it("emits RUN_STARTED event", async () => { - mockPing.mockResolvedValue({}); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - runId: "proxy-run", - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(events[0].type).toBe(EventType.RUN_STARTED); - expect((events[0] as any).runId).toBe("proxy-run"); - }); - - it("emits RUN_FINISHED with result on success", async () => { - const pingResult = { timestamp: Date.now() }; - mockPing.mockResolvedValue(pingResult); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect(finishedEvent).toBeDefined(); - expect((finishedEvent as any).result).toEqual(pingResult); - }); - - it("emits RUN_FINISHED with error on failure", async () => { - mockConnect.mockRejectedValue(new Error("Connection refused")); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result.error).toContain("Connection refused"); - }); - - it("bypasses normal agent flow", async () => { - mockPing.mockResolvedValue({}); - - const middleware = new MCPAppsExtensionMiddleware({ - mcpServers: [{ type: "http", url: "http://localhost:3001" }], - }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - await collectEvents(middleware.run(input, agent)); - - // Agent's run should not have been called - expect(agent.runCalls).toHaveLength(0); - }); - }); - - // ============================================================================= - // 12. Proxied Request Execution Tests - // ============================================================================= - describe("Proxied Request Execution", () => { - it("handles tools/call method", async () => { - const toolResult = createMCPToolCallResult([{ type: "text", text: "Tool executed" }]); - mockCallTool.mockResolvedValue(toolResult); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "tools/call", - params: { name: "test-tool", arguments: { foo: "bar" } }, - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockCallTool).toHaveBeenCalledWith({ - name: "test-tool", - arguments: { foo: "bar" }, - }); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result).toEqual(toolResult); - }); - - it("handles resources/read method", async () => { - const resourceResult = createMCPResourceResult( - "ui://server/resource", - "text/html+mcp", - "" - ); - mockReadResource.mockResolvedValue(resourceResult); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "resources/read", - params: { uri: "ui://server/resource" }, - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockReadResource).toHaveBeenCalledWith({ - uri: "ui://server/resource", - }); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result).toEqual(resourceResult); - }); - - it("handles notifications/message method (one-way)", async () => { - mockNotification.mockResolvedValue(undefined); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "notifications/message", - params: { level: "info", message: "Test notification" }, - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockNotification).toHaveBeenCalledWith({ - method: "notifications/message", - params: { level: "info", message: "Test notification" }, - }); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result).toEqual({ success: true }); - }); - - it("handles ping method", async () => { - const pingResult = { timestamp: 1234567890 }; - mockPing.mockResolvedValue(pingResult); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockPing).toHaveBeenCalled(); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result).toEqual(pingResult); - }); - - it("rejects disallowed methods", async () => { - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "sampling/createMessage", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - const events = await collectEvents(middleware.run(input, agent)); - - const finishedEvent = events.find((e) => e.type === EventType.RUN_FINISHED); - expect((finishedEvent as any).result.error).toContain("not allowed"); - }); - - it("uses HTTP transport when serverType is http", async () => { - const { StreamableHTTPClientTransport } = await import( - "@modelcontextprotocol/sdk/client/streamableHttp.js" - ); - mockPing.mockResolvedValue({}); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000", - serverType: "http", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - await collectEvents(middleware.run(input, agent)); - - expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000")); - }); - - it("uses SSE transport when serverType is sse", async () => { - const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js"); - mockPing.mockResolvedValue({}); - - const middleware = new MCPAppsExtensionMiddleware(); - const agent = new MockAgent([]); - - const proxiedRequest: ProxiedMCPRequest = { - serverUrl: "http://localhost:3000/sse", - serverType: "sse", - method: "ping", - }; - - const input = createRunAgentInput({ - forwardedProps: { __proxiedMCPRequest: proxiedRequest }, - }); - - await collectEvents(middleware.run(input, agent)); - - expect(SSEClientTransport).toHaveBeenCalledWith(new URL("http://localhost:3000/sse")); - }); - }); - - // ============================================================================= - // 13. SEP-1865 Spec Compliance Tests - // ============================================================================= - describe("SEP-1865 Spec Compliance", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("tool metadata uses ui/resourceUri key", async () => { - mockListTools.mockResolvedValue({ - tools: [ - { - name: "tool", - description: "desc", - inputSchema: {}, - _meta: { "ui/resourceUri": "ui://server/tool" }, - }, - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - // Tool should be detected as UI tool - expect(agent.runCalls[0].tools).toHaveLength(1); - }); - - it("capability negotiation uses io.modelcontextprotocol/ui extension key", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(mockClientConstructorCalls.length).toBeGreaterThan(0); - const options = mockClientConstructorCalls[0].options as { - capabilities?: { extensions?: Record }; - }; - expect(options.capabilities?.extensions).toHaveProperty("io.modelcontextprotocol/ui"); - }); - - it("advertises text/html+mcp mimeType", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(mockClientConstructorCalls.length).toBeGreaterThan(0); - const options = mockClientConstructorCalls[0].options as { - capabilities?: { extensions?: { "io.modelcontextprotocol/ui"?: { mimeTypes?: string[] } } }; - }; - expect(options.capabilities?.extensions?.["io.modelcontextprotocol/ui"]?.mimeTypes).toContain( - "text/html+mcp" - ); - }); - - it("fallback behavior: ignores tools without ui/resourceUri", async () => { - mockListTools.mockResolvedValue({ - tools: [ - createMCPToolWithoutUI("regular-tool"), - createMCPToolWithUI("ui-tool", "ui://server/tool"), - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - // Only UI tool should be injected - expect(agent.runCalls[0].tools).toHaveLength(1); - expect(agent.runCalls[0].tools[0].name).toBe("ui-tool"); - }); - }); - - // ============================================================================= - // 14. Error Handling Tests - // ============================================================================= - describe("Error Handling", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("handles MCP server connection timeout", async () => { - const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - mockConnect.mockImplementation( - () => new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 10)) - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - const events = await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(events.length).toBeGreaterThanOrEqual(2); - expect(consoleErrorSpy).toHaveBeenCalled(); - - consoleErrorSpy.mockRestore(); - }); - - it("handles JSON parse error on tool arguments", async () => { - const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - // Create malformed tool call - const assistantMsg = { - id: "msg-1", - role: "assistant" as const, - toolCalls: [ - { - id: "tc-1", - type: "function" as const, - function: { - name: "ui-tool", - arguments: "{ invalid json", - }, - }, - ], - }; - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Should emit error result - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents.length).toBe(1); - expect((toolResultEvents[0] as any).content).toContain("error"); - - consoleErrorSpy.mockRestore(); - }); - }); - - // ============================================================================= - // 15. Edge Cases Tests - // ============================================================================= - describe("Edge Cases", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("handles empty tool arguments", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockCallTool).toHaveBeenCalledWith({ - name: "ui-tool", - arguments: {}, - }); - expect(events.some((e) => e.type === EventType.TOOL_CALL_RESULT)).toBe(true); - }); - - it("handles large tool arguments", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const largeData = "x".repeat(100000); - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: { data: largeData }, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - expect(mockCallTool).toHaveBeenCalledWith({ - name: "ui-tool", - arguments: { data: largeData }, - }); - expect(events.some((e) => e.type === EventType.TOOL_CALL_RESULT)).toBe(true); - }); - - it("handles multiple UI tools from same server", async () => { - mockListTools.mockResolvedValue({ - tools: [ - createMCPToolWithUI("ui-tool-1", "ui://server/tool1"), - createMCPToolWithUI("ui-tool-2", "ui://server/tool2"), - createMCPToolWithUI("ui-tool-3", "ui://server/tool3"), - ], - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - - await collectEvents(middleware.run(createRunAgentInput(), agent)); - - expect(agent.runCalls[0].tools).toHaveLength(3); - }); - - it("handles rapid sequential requests", async () => { - mockListTools.mockResolvedValue({ tools: [] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const results = await Promise.all([ - collectEvents( - middleware.run( - createRunAgentInput({ runId: "run-1" }), - new MockAgent([ - createRunStartedEvent("run-1"), - createRunFinishedEvent("run-1"), - ]) - ) - ), - collectEvents( - middleware.run( - createRunAgentInput({ runId: "run-2" }), - new MockAgent([ - createRunStartedEvent("run-2"), - createRunFinishedEvent("run-2"), - ]) - ) - ), - collectEvents( - middleware.run( - createRunAgentInput({ runId: "run-3" }), - new MockAgent([ - createRunStartedEvent("run-3"), - createRunFinishedEvent("run-3"), - ]) - ) - ), - ]); - - expect(results).toHaveLength(3); - results.forEach((events) => { - expect(events[0].type).toBe(EventType.RUN_STARTED); - expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED); - }); - }); - }); - - // ============================================================================= - // 16. Activity Event Injection Tests - // ============================================================================= - describe("Activity Event Injection", () => { - const httpServerConfig: MCPClientConfig = { type: "http", url: "http://localhost:3000" }; - - it("emits ACTIVITY_SNAPSHOT for each pending UI tool call", async () => { - const uiTool1 = createMCPToolWithUI("ui-tool-1", "ui://server/tool1"); - const uiTool2 = createMCPToolWithUI("ui-tool-2", "ui://server/tool2"); - mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool-1", args: { param: "a" }, id: "tc-1" }, - { name: "ui-tool-2", args: { param: "b" }, id: "tc-2" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect(activityEvents).toHaveLength(2); - }); - - it("emits TOOL_CALL_RESULT before ACTIVITY_SNAPSHOT for each tool", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultIndex = events.findIndex((e) => e.type === EventType.TOOL_CALL_RESULT); - const activityIndex = events.findIndex((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - - expect(toolResultIndex).toBeLessThan(activityIndex); - }); - - it("emits TOOL_CALL_RESULT and ACTIVITY_SNAPSHOT before RUN_FINISHED", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const runFinishedIndex = events.findIndex((e) => e.type === EventType.RUN_FINISHED); - const activityIndex = events.findIndex((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - const toolResultIndex = events.findIndex((e) => e.type === EventType.TOOL_CALL_RESULT); - - expect(toolResultIndex).toBeLessThan(runFinishedIndex); - expect(activityIndex).toBeLessThan(runFinishedIndex); - }); - - it("ACTIVITY_SNAPSHOT has a unique messageId", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).messageId).toBeDefined(); - expect(typeof (activityEvent as any).messageId).toBe("string"); - expect((activityEvent as any).messageId.length).toBeGreaterThan(0); - }); - - it("TOOL_CALL_RESULT has a unique messageId", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT); - expect((toolResultEvent as any).messageId).toBeDefined(); - expect(typeof (toolResultEvent as any).messageId).toBe("string"); - expect((toolResultEvent as any).messageId.length).toBeGreaterThan(0); - }); - - it("passes tool arguments correctly to MCP server", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const toolArgs = { query: "test", limit: 10, nested: { value: "deep" } }; - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: toolArgs, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - await collectEvents(middleware.run(input, agent)); - - expect(mockCallTool).toHaveBeenCalledWith({ - name: "ui-tool", - arguments: toolArgs, - }); - }); - - it("resource in ACTIVITY_SNAPSHOT includes uri and mimeType", async () => { - const resourceUri = "ui://server/dashboard"; - const uiTool = createMCPToolWithUI("ui-tool", resourceUri); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - - const resourceContent = { - uri: resourceUri, - mimeType: "text/html+mcp", - text: "UI Content", - }; - mockReadResource.mockResolvedValue({ contents: [resourceContent] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).content.resource.uri).toBe(resourceUri); - expect((activityEvent as any).content.resource.mimeType).toBe("text/html+mcp"); - expect((activityEvent as any).content.resource.text).toContain("UI Content"); - }); - - it("handles blob (base64) content in resource", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - - // Resource with blob instead of text - const resourceContent = { - uri: "ui://server/tool", - mimeType: "text/html+mcp", - blob: "PCFET0NUWVBFIGh0bWw+", // base64 encoded "" - }; - mockReadResource.mockResolvedValue({ contents: [resourceContent] }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: {}, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect((activityEvent as any).content.resource.blob).toBe("PCFET0NUWVBFIGh0bWw+"); - }); - - it("full end-to-end flow: RUN_STARTED → tool call → TOOL_CALL_RESULT → ACTIVITY_SNAPSHOT → RUN_FINISHED", async () => { - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - mockListTools.mockResolvedValue({ tools: [uiTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Tool executed successfully" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "UI") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool", args: { action: "render" }, id: "tc-1" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Verify event sequence - const eventTypes = events.map((e) => e.type); - expect(eventTypes).toEqual([ - EventType.RUN_STARTED, - EventType.TOOL_CALL_RESULT, - EventType.ACTIVITY_SNAPSHOT, - EventType.RUN_FINISHED, - ]); - - // Verify content - const toolResultEvent = events.find((e) => e.type === EventType.TOOL_CALL_RESULT) as any; - expect(toolResultEvent.content).toBe("Tool executed successfully"); - expect(toolResultEvent.toolCallId).toBe("tc-1"); - - const activityEvent = events.find((e) => e.type === EventType.ACTIVITY_SNAPSHOT) as any; - expect(activityEvent.activityType).toBe("MCP-APPS-EXTENSION"); - expect(activityEvent.replace).toBe(true); - expect(activityEvent.content.resource.text).toContain("UI"); - }); - - it("does not emit activity events for non-UI tools", async () => { - // Mix of UI and non-UI tools - const uiTool = createMCPToolWithUI("ui-tool", "ui://server/tool"); - const regularTool = createMCPToolWithoutUI("regular-tool"); - mockListTools.mockResolvedValue({ tools: [uiTool, regularTool] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockResolvedValue( - createMCPResourceResult("ui://server/tool", "text/html+mcp", "") - ); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - // Call both UI and non-UI tools - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "regular-tool", args: {}, id: "tc-regular" }, - { name: "ui-tool", args: {}, id: "tc-ui" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - const events = await collectEvents(middleware.run(input, agent)); - - // Only one ACTIVITY_SNAPSHOT (for the UI tool) - const activityEvents = events.filter((e) => e.type === EventType.ACTIVITY_SNAPSHOT); - expect(activityEvents).toHaveLength(1); - - // Only one TOOL_CALL_RESULT (for the UI tool) - const toolResultEvents = events.filter((e) => e.type === EventType.TOOL_CALL_RESULT); - expect(toolResultEvents).toHaveLength(1); - expect((toolResultEvents[0] as any).toolCallId).toBe("tc-ui"); - }); - - it("reads the correct resource URI for each UI tool", async () => { - const uiTool1 = createMCPToolWithUI("ui-tool-1", "ui://server/dashboard"); - const uiTool2 = createMCPToolWithUI("ui-tool-2", "ui://server/settings"); - mockListTools.mockResolvedValue({ tools: [uiTool1, uiTool2] }); - mockCallTool.mockResolvedValue( - createMCPToolCallResult([{ type: "text", text: "Result" }]) - ); - mockReadResource.mockImplementation((params: { uri: string }) => { - if (params.uri === "ui://server/dashboard") { - return Promise.resolve({ - contents: [{ uri: params.uri, mimeType: "text/html+mcp", text: "Dashboard" }], - }); - } - return Promise.resolve({ - contents: [{ uri: params.uri, mimeType: "text/html+mcp", text: "Settings" }], - }); - }); - - const middleware = new MCPAppsExtensionMiddleware({ mcpServers: [httpServerConfig] }); - - const assistantMsg = createAssistantMessageWithToolCalls([ - { name: "ui-tool-1", args: {}, id: "tc-1" }, - { name: "ui-tool-2", args: {}, id: "tc-2" }, - ]); - - const agent = new MockAgent([createRunStartedEvent(), createRunFinishedEvent()]); - const input = createRunAgentInput({ messages: [assistantMsg] }); - - await collectEvents(middleware.run(input, agent)); - - // Verify readResource was called with correct URIs - expect(mockReadResource).toHaveBeenCalledWith({ uri: "ui://server/dashboard" }); - expect(mockReadResource).toHaveBeenCalledWith({ uri: "ui://server/settings" }); - }); - }); -}); diff --git a/apps/react/demo/src/lib/__tests__/test-utils.ts b/apps/react/demo/src/lib/__tests__/test-utils.ts deleted file mode 100644 index c505f4f..0000000 --- a/apps/react/demo/src/lib/__tests__/test-utils.ts +++ /dev/null @@ -1,404 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { vi } from "vitest"; -import { AbstractAgent, BaseEvent, EventType, RunAgentInput, Message, Tool, AssistantMessage } from "@ag-ui/client"; -import { Observable } from "rxjs"; -import { firstValueFrom, toArray } from "rxjs"; - -/** - * Mock MCP Client instance - */ -export interface MockMCPClientInstance { - connect: ReturnType; - close: ReturnType; - listTools: ReturnType; - callTool: ReturnType; - readResource: ReturnType; - notification: ReturnType; - ping: ReturnType; -} - -/** - * Create a mock MCP Client instance - */ -export function createMockMCPClient(): MockMCPClientInstance { - return { - connect: vi.fn().mockResolvedValue(undefined), - close: vi.fn().mockResolvedValue(undefined), - listTools: vi.fn().mockResolvedValue({ tools: [] }), - callTool: vi.fn().mockResolvedValue({ content: [] }), - readResource: vi.fn().mockResolvedValue({ contents: [] }), - notification: vi.fn().mockResolvedValue(undefined), - ping: vi.fn().mockResolvedValue({}), - }; -} - -/** - * Mock MCP tool with UI resource (per SEP-1865) - */ -export interface MockMCPTool { - name: string; - description?: string; - inputSchema?: Record; - _meta?: Record; -} - -/** - * Create an MCP tool with UI resource attached - */ -export function createMCPToolWithUI( - name: string, - resourceUri: string, - description?: string -): MockMCPTool { - return { - name, - description: description || `Tool ${name}`, - inputSchema: { type: "object", properties: {} }, - _meta: { "ui/resourceUri": resourceUri }, - }; -} - -/** - * Create an MCP tool without UI resource - */ -export function createMCPToolWithoutUI(name: string, description?: string): MockMCPTool { - return { - name, - description: description || `Tool ${name}`, - inputSchema: { type: "object", properties: {} }, - }; -} - -/** - * Create an MCP tool with _meta but no ui/resourceUri - */ -export function createMCPToolWithEmptyMeta(name: string): MockMCPTool { - return { - name, - description: `Tool ${name}`, - inputSchema: { type: "object", properties: {} }, - _meta: { someOtherField: "value" }, - }; -} - -/** - * Mock Agent for testing middleware - */ -export class MockAgent extends AbstractAgent { - private events: BaseEvent[]; - public runCalls: RunAgentInput[] = []; - - constructor(events: BaseEvent[] = []) { - super(); - this.events = events; - } - - run(input: RunAgentInput): Observable { - this.runCalls.push(input); - return new Observable((subscriber) => { - for (const event of this.events) { - subscriber.next(event); - } - subscriber.complete(); - }); - } - - setEvents(events: BaseEvent[]): void { - this.events = events; - } -} - -/** - * Mock Agent that emits events asynchronously - */ -export class AsyncMockAgent extends AbstractAgent { - private events: BaseEvent[]; - private delayMs: number; - - constructor(events: BaseEvent[] = [], delayMs: number = 0) { - super(); - this.events = events; - this.delayMs = delayMs; - } - - run(_input: RunAgentInput): Observable { - return new Observable((subscriber) => { - let cancelled = false; - - const emitEvents = async () => { - for (const event of this.events) { - if (cancelled) break; - if (this.delayMs > 0) { - await new Promise((resolve) => setTimeout(resolve, this.delayMs)); - } - if (!cancelled) { - subscriber.next(event); - } - } - if (!cancelled) { - subscriber.complete(); - } - }; - - emitEvents(); - - return () => { - cancelled = true; - }; - }); - } -} - -/** - * Mock Agent that throws an error - */ -export class ErrorMockAgent extends AbstractAgent { - private error: Error; - - constructor(error: Error = new Error("Mock error")) { - super(); - this.error = error; - } - - run(_input: RunAgentInput): Observable { - return new Observable((subscriber) => { - subscriber.error(this.error); - }); - } -} - -/** - * Create a basic RunAgentInput for testing - */ -export function createRunAgentInput(overrides: Partial = {}): RunAgentInput { - return { - threadId: "test-thread", - runId: "test-run", - tools: [], - context: [], - forwardedProps: {}, - state: {}, - messages: [], - ...overrides, - }; -} - -/** - * Create a RUN_STARTED event - */ -export function createRunStartedEvent( - runId: string = "test-run", - threadId: string = "test-thread" -): BaseEvent { - return { - type: EventType.RUN_STARTED, - runId, - threadId, - }; -} - -/** - * Create a RUN_FINISHED event - */ -export function createRunFinishedEvent( - runId: string = "test-run", - threadId: string = "test-thread", - result?: unknown -): BaseEvent { - return { - type: EventType.RUN_FINISHED, - runId, - threadId, - result, - }; -} - -/** - * Create a TEXT_MESSAGE_START event - */ -export function createTextMessageStartEvent(messageId: string = "msg-1"): BaseEvent { - return { - type: EventType.TEXT_MESSAGE_START, - messageId, - role: "assistant", - }; -} - -/** - * Create a TEXT_MESSAGE_CONTENT event - */ -export function createTextMessageContentEvent( - messageId: string = "msg-1", - delta: string = "Hello" -): BaseEvent { - return { - type: EventType.TEXT_MESSAGE_CONTENT, - messageId, - delta, - }; -} - -/** - * Create a TEXT_MESSAGE_END event - */ -export function createTextMessageEndEvent(messageId: string = "msg-1"): BaseEvent { - return { - type: EventType.TEXT_MESSAGE_END, - messageId, - }; -} - -/** - * Create a TOOL_CALL_START event - */ -export function createToolCallStartEvent( - toolCallId: string, - toolCallName: string, - parentMessageId?: string -): BaseEvent { - return { - type: EventType.TOOL_CALL_START, - toolCallId, - toolCallName, - parentMessageId, - }; -} - -/** - * Create a TOOL_CALL_ARGS event - */ -export function createToolCallArgsEvent(toolCallId: string, delta: string): BaseEvent { - return { - type: EventType.TOOL_CALL_ARGS, - toolCallId, - delta, - }; -} - -/** - * Create a TOOL_CALL_END event - */ -export function createToolCallEndEvent(toolCallId: string): BaseEvent { - return { - type: EventType.TOOL_CALL_END, - toolCallId, - }; -} - -/** - * Create a TOOL_CALL_RESULT event - */ -export function createToolCallResultEvent( - toolCallId: string, - content: string, - messageId: string = `result-${toolCallId}` -): BaseEvent { - return { - type: EventType.TOOL_CALL_RESULT, - messageId, - toolCallId, - content, - }; -} - -/** - * Create an assistant message with tool calls - */ -export function createAssistantMessageWithToolCalls( - toolCalls: Array<{ name: string; args?: Record; id?: string }>, - messageId?: string -): AssistantMessage { - return { - id: messageId || `msg-${Math.random().toString(36).substr(2, 9)}`, - role: "assistant", - content: "", - toolCalls: toolCalls.map((tc) => ({ - id: tc.id || `tc-${Math.random().toString(36).substr(2, 9)}`, - type: "function" as const, - function: { - name: tc.name, - arguments: JSON.stringify(tc.args || {}), - }, - })), - }; -} - -/** - * Create a tool result message - */ -export function createToolResultMessage( - toolCallId: string, - content: string, - messageId?: string -): Message { - return { - id: messageId || `msg-${Math.random().toString(36).substr(2, 9)}`, - role: "tool", - toolCallId, - content, - }; -} - -/** - * Create an AG-UI Tool - */ -export function createAGUITool(name: string, description?: string): Tool { - return { - name, - description: description || `Tool ${name}`, - parameters: { type: "object", properties: {} }, - }; -} - -/** - * Collect all events from an Observable - */ -export async function collectEvents(observable: Observable): Promise { - return firstValueFrom(observable.pipe(toArray())); -} - -/** - * Create MCP tool call result (what callTool returns) - */ -export function createMCPToolCallResult( - content: Array<{ type: string; text?: string; [key: string]: unknown }> -): { content: Array<{ type: string; text?: string; [key: string]: unknown }> } { - return { content }; -} - -/** - * Create MCP resource read result - */ -export function createMCPResourceResult( - uri: string, - mimeType: string, - text: string -): { contents: Array<{ uri: string; mimeType: string; text: string }> } { - return { - contents: [{ uri, mimeType, text }], - }; -} - -/** - * Wait for a condition to be true - */ -export async function waitForCondition( - condition: () => boolean, - timeout: number = 1000, - interval: number = 10 -): Promise { - const start = Date.now(); - while (!condition()) { - if (Date.now() - start > timeout) { - throw new Error("Timeout waiting for condition"); - } - await new Promise((resolve) => setTimeout(resolve, interval)); - } -} - -/** - * Generate a random UUID-like string - */ -export function randomId(): string { - return `${Math.random().toString(36).substr(2, 9)}-${Math.random().toString(36).substr(2, 9)}`; -} diff --git a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts b/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts deleted file mode 100644 index 70e22ef..0000000 --- a/apps/react/demo/src/lib/mcp-apps-extension-middleware.ts +++ /dev/null @@ -1,550 +0,0 @@ -import { Middleware, RunAgentInput, AbstractAgent, BaseEvent, Tool, EventType, Message, ToolCall, ToolCallResultEvent, ActivitySnapshotEvent, RunStartedEvent, RunFinishedEvent } from "@ag-ui/client"; -import { Observable, from, switchMap } from "rxjs"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { randomUUID } from "crypto"; - -/** - * Proxied MCP request structure from the frontend iframe - */ -export interface ProxiedMCPRequest { - /** The MCP server URL to connect to */ - serverUrl: string; - /** The MCP server transport type */ - serverType: "http" | "sse"; - /** The JSON-RPC method to call */ - method: string; - /** The JSON-RPC params */ - params?: Record; -} - -/** - * Extract EventWithState type from Middleware.runNextWithState return type - */ -type ExtractObservableType = T extends Observable ? U : never; -type RunNextWithStateReturn = ReturnType; -export type EventWithState = ExtractObservableType; - -/** - * UI Tool with its source server config and resource URI - */ -interface UIToolInfo { - tool: Tool; - serverConfig: MCPClientConfig; - resourceUri: string; -} - -/** - * MCP Client configuration for HTTP transport - */ -export interface MCPClientConfigHTTP { - type: "http"; - url: string; -} - -/** - * MCP Client configuration for SSE transport - */ -export interface MCPClientConfigSSE { - type: "sse"; - url: string; - headers?: Record; -} - -/** - * MCP Client configuration - */ -export type MCPClientConfig = MCPClientConfigHTTP | MCPClientConfigSSE; - -/** - * Configuration for MCPAppsExtensionMiddleware - */ -export interface MCPAppsExtensionMiddlewareConfig { - /** - * List of MCP server configurations - */ - mcpServers?: MCPClientConfig[]; -} - -/** - * Check if a tool has a UI resource attached (per SEP-1865) - */ -function hasUIResource(tool: { _meta?: Record }): boolean { - return typeof tool._meta?.["ui/resourceUri"] === "string"; -} - -/** - * Extended tool type that includes MCP Apps metadata - */ -export interface MCPAppTool extends Tool { - /** UI resource URI from SEP-1865 */ - uiResourceUri?: string; -} - -/** - * Convert MCP tool to AG-UI tool format, preserving UI resource info - */ -function convertMCPToolToAGUITool(mcpTool: { - name: string; - description?: string; - inputSchema?: Record; - _meta?: Record; -}): Tool { - const tool: Tool = { - name: mcpTool.name, - description: mcpTool.description || "", - parameters: mcpTool.inputSchema || { type: "object", properties: {} }, - }; - - // Store UI resource URI in the description for now - // TODO: Once AG-UI Tool type supports _meta, use that instead - const uiResourceUri = mcpTool._meta?.["ui/resourceUri"]; - if (typeof uiResourceUri === "string") { - tool.description = `${tool.description}\n[UI Resource: ${uiResourceUri}]`; - } - - return tool; -} - -/** - * MCP Apps Extension middleware - fetches UI-enabled tools from MCP servers. - */ -export class MCPAppsExtensionMiddleware extends Middleware { - private config: MCPAppsExtensionMiddlewareConfig; - /** Map of tool name -> server config for UI tools */ - private uiToolsMap: Map = new Map(); - - constructor(config: MCPAppsExtensionMiddlewareConfig = {}) { - super(); - this.config = config; - } - - run(input: RunAgentInput, next: AbstractAgent): Observable { - // Check for proxied MCP request mode - const proxiedRequest = input.forwardedProps?.__proxiedMCPRequest as ProxiedMCPRequest | undefined; - if (proxiedRequest) { - return this.handleProxiedMCPRequest(input.runId, proxiedRequest); - } - - // If no MCP servers configured, pass through using runNextWithState - if (!this.config.mcpServers?.length) { - return this.processStream(this.runNextWithState(input, next), new Map()); - } - - // Fetch UI tools from MCP servers and inject them - return from(this.fetchUITools()).pipe( - switchMap((uiToolInfos) => { - // Build map of tool name -> UIToolInfo - const uiToolsMap = new Map(); - for (const info of uiToolInfos) { - uiToolsMap.set(info.tool.name, info); - } - - // Merge UI tools with existing input tools - const enhancedInput: RunAgentInput = { - ...input, - tools: [...input.tools, ...uiToolInfos.map((info) => info.tool)], - }; - - // Use runNextWithState to get state with each event - return this.processStream(this.runNextWithState(enhancedInput, next), uiToolsMap); - }) - ); - } - - /** - * Handle a proxied MCP request from the frontend iframe. - * This bypasses the normal agent flow and directly executes the MCP request. - */ - private handleProxiedMCPRequest(runId: string, request: ProxiedMCPRequest): Observable { - return new Observable((subscriber) => { - // Emit RunStarted - const runStartedEvent: RunStartedEvent = { - type: EventType.RUN_STARTED, - runId, - threadId: runId, - }; - subscriber.next(runStartedEvent); - - // Execute the MCP request - this.executeMCPRequest(request) - .then((result) => { - // Emit RunFinished with the MCP result - const runFinishedEvent: RunFinishedEvent = { - type: EventType.RUN_FINISHED, - runId, - threadId: runId, - result, - }; - subscriber.next(runFinishedEvent); - subscriber.complete(); - }) - .catch((error) => { - // Emit RunFinished with error - const runFinishedEvent: RunFinishedEvent = { - type: EventType.RUN_FINISHED, - runId, - threadId: runId, - result: { error: String(error) }, - }; - subscriber.next(runFinishedEvent); - subscriber.complete(); - }); - }); - } - - /** - * Execute a generic MCP request (tools/call, resources/read, etc.) - */ - private async executeMCPRequest(request: ProxiedMCPRequest): Promise { - let transport; - - if (request.serverType === "sse") { - transport = new SSEClientTransport(new URL(request.serverUrl)); - } else { - transport = new StreamableHTTPClientTransport(new URL(request.serverUrl)); - } - - const client = new Client( - { name: "mcp-apps-middleware", version: "1.0.0" }, - { - capabilities: { - extensions: { - "io.modelcontextprotocol/ui": { - mimeTypes: ["text/html+mcp"], - }, - }, - }, - } - ); - - try { - await client.connect(transport); - - // Per SEP-1865: Forward any method that doesn't start with "ui/" - // Methods starting with "ui/" are handled by the host, not the MCP server - switch (request.method) { - case "tools/call": - return await client.callTool(request.params as { name: string; arguments?: Record }); - case "resources/read": - return await client.readResource(request.params as { uri: string }); - case "notifications/message": - // notifications/message is a one-way notification (no response expected) - await client.notification({ method: "notifications/message", params: request.params }); - return { success: true }; - case "ping": - return await client.ping(); - default: - throw new Error(`MCP method not allowed for UI proxy: ${request.method}`); - } - } finally { - await client.close(); - } - } - - /** - * Process the event stream, holding back RunFinished events until either: - * a) Another event comes -> flush the held RunFinished immediately - * b) Stream ends -> do special processing, then flush RunFinished and complete - */ - private processStream( - source: Observable, - uiToolsMap: Map - ): Observable { - return new Observable((subscriber) => { - let heldRunFinished: EventWithState | null = null; - let isProcessing = false; - - const subscription = source.subscribe({ - next: (eventWithState) => { - const event = eventWithState.event; - - // If we have a held RunFinished and a new event comes, flush it first - if (heldRunFinished) { - subscriber.next(heldRunFinished.event); - heldRunFinished = null; - } - - // If this is a RunFinished event, hold it back - if (event.type === EventType.RUN_FINISHED) { - heldRunFinished = eventWithState; - } else { - subscriber.next(event); - } - }, - error: (err) => { - // On error, flush any held event and propagate error - if (heldRunFinished) { - subscriber.next(heldRunFinished.event); - heldRunFinished = null; - } - subscriber.error(err); - }, - complete: async () => { - // Stream ended - do special processing if we have a held RunFinished - if (heldRunFinished && !isProcessing) { - isProcessing = true; - - try { - // Find tool calls that don't have a corresponding result message - const pendingToolCalls = this.findPendingToolCalls(heldRunFinished.messages); - - // Filter for UI tool calls (tools we injected from MCP servers) - const pendingUIToolCalls = pendingToolCalls.filter((tc) => - uiToolsMap.has(tc.function.name) - ); - - // Execute pending UI tool calls and emit results - for (const toolCall of pendingUIToolCalls) { - const toolInfo = uiToolsMap.get(toolCall.function.name)!; - try { - const args = JSON.parse(toolCall.function.arguments || "{}"); - const mcpResult = await this.executeToolCall(toolInfo.serverConfig, toolCall.function.name, args); - - // Fetch the UI resource - const resource = await this.readResource(toolInfo.serverConfig, toolInfo.resourceUri); - - // Emit tool result event - const resultEvent: ToolCallResultEvent = { - type: EventType.TOOL_CALL_RESULT, - messageId: randomUUID(), - toolCallId: toolCall.id, - content: this.extractTextContent(mcpResult), - }; - subscriber.next(resultEvent); - - // Emit activity snapshot with full MCP result, resource, and server info - const activityEvent: ActivitySnapshotEvent = { - type: EventType.ACTIVITY_SNAPSHOT, - messageId: randomUUID(), - activityType: "MCP-APPS-EXTENSION", - content: { - result: mcpResult, - resource, - serverUrl: toolInfo.serverConfig.url, - serverType: toolInfo.serverConfig.type, - toolInput: args, - }, - replace: true, - }; - subscriber.next(activityEvent); - } catch (error) { - console.error(`Failed to execute UI tool call ${toolCall.function.name}:`, error); - // Emit error result - const errorResult: ToolCallResultEvent = { - type: EventType.TOOL_CALL_RESULT, - messageId: randomUUID(), - toolCallId: toolCall.id, - content: JSON.stringify({ error: String(error) }), - }; - subscriber.next(errorResult); - } - } - - subscriber.next(heldRunFinished.event); - } finally { - heldRunFinished = null; - isProcessing = false; - } - } - subscriber.complete(); - }, - }); - - return () => subscription.unsubscribe(); - }); - } - - /** - * Execute a tool call on the MCP server and return the raw result - */ - private async executeToolCall( - serverConfig: MCPClientConfig, - toolName: string, - args: Record - ): Promise { - let transport; - - if (serverConfig.type === "sse") { - transport = new SSEClientTransport(new URL(serverConfig.url)); - } else { - transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); - } - - const client = new Client( - { name: "mcp-apps-middleware", version: "1.0.0" }, - { - capabilities: { - extensions: { - "io.modelcontextprotocol/ui": { - mimeTypes: ["text/html+mcp"], - }, - }, - }, - } - ); - - try { - await client.connect(transport); - - const result = await client.callTool({ - name: toolName, - arguments: args, - }); - - return result; - } finally { - await client.close(); - } - } - - /** - * Read a UI resource from the MCP server - */ - private async readResource( - serverConfig: MCPClientConfig, - resourceUri: string - ): Promise { - let transport; - - if (serverConfig.type === "sse") { - transport = new SSEClientTransport(new URL(serverConfig.url)); - } else { - transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); - } - - const client = new Client( - { name: "mcp-apps-middleware", version: "1.0.0" }, - { - capabilities: { - extensions: { - "io.modelcontextprotocol/ui": { - mimeTypes: ["text/html+mcp"], - }, - }, - }, - } - ); - - try { - await client.connect(transport); - - const result = await client.readResource({ - uri: resourceUri, - }); - - // Return the first content item (the UI resource) - return result.contents[0]; - } finally { - await client.close(); - } - } - - /** - * Extract text content from MCP result, fallback to JSON stringified content - */ - private extractTextContent(mcpResult: unknown): string { - const result = mcpResult as { content?: unknown }; - if (Array.isArray(result.content)) { - const textContent = result.content - .filter((c): c is { type: "text"; text: string } => - c && typeof c === "object" && c.type === "text" && typeof c.text === "string" - ) - .map((c) => c.text) - .join("\n"); - return textContent || JSON.stringify(result.content); - } - return JSON.stringify(result.content); - } - - /** - * Find tool calls that don't have a corresponding result (role: "tool") message - */ - private findPendingToolCalls(messages: Message[]): ToolCall[] { - // Collect all tool calls from assistant messages - const allToolCalls: ToolCall[] = []; - for (const message of messages) { - if (message.role === "assistant" && "toolCalls" in message && message.toolCalls) { - allToolCalls.push(...message.toolCalls); - } - } - - // Collect all tool call IDs that have results - const resolvedToolCallIds = new Set(); - for (const message of messages) { - if (message.role === "tool" && "toolCallId" in message) { - resolvedToolCallIds.add(message.toolCallId); - } - } - - // Return tool calls that don't have results - return allToolCalls.filter((tc) => !resolvedToolCallIds.has(tc.id)); - } - - /** - * Connect to all configured MCP servers and fetch tools with UI resources - */ - private async fetchUITools(): Promise { - const allUITools: UIToolInfo[] = []; - - for (const serverConfig of this.config.mcpServers || []) { - try { - const tools = await this.fetchToolsFromServer(serverConfig); - allUITools.push(...tools); - } catch (error) { - console.error(`Failed to fetch tools from MCP server ${serverConfig.url}:`, error); - } - } - - return allUITools; - } - - /** - * Connect to a single MCP server and fetch its UI-enabled tools - */ - private async fetchToolsFromServer(serverConfig: MCPClientConfig): Promise { - let transport; - - if (serverConfig.type === "sse") { - transport = new SSEClientTransport(new URL(serverConfig.url)); - } else { - transport = new StreamableHTTPClientTransport(new URL(serverConfig.url)); - } - - const client = new Client( - { name: "mcp-apps-middleware", version: "1.0.0" }, - { - capabilities: { - // Advertise MCP Apps UI support per SEP-1865 - extensions: { - "io.modelcontextprotocol/ui": { - mimeTypes: ["text/html+mcp"], - }, - }, - }, - } - ); - - try { - await client.connect(transport); - - // Fetch tools from the server - const response = await client.listTools(); - - // Filter for tools with UI resources and convert to AG-UI format with server config - const uiTools = response.tools - .filter(hasUIResource) - .map((mcpTool) => ({ - tool: convertMCPToolToAGUITool(mcpTool), - serverConfig, - resourceUri: mcpTool._meta!["ui/resourceUri"] as string, - })); - - return uiTools; - } finally { - // Always close the connection - await client.close(); - } - } -} From 2436a1ca615e713aeabf03133e6de3f3f91611d6 Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Fri, 5 Dec 2025 12:12:23 +0100 Subject: [PATCH 08/11] Add MCPAppsActivityRenderer by default --- apps/react/demo/src/app/page.tsx | 21 +--------------- apps/react/demo/src/app/single/page.tsx | 17 +------------ .../components/MCPAppsActivityRenderer.tsx | 19 ++++++++------- packages/react/src/components/index.ts | 1 + .../src/providers/CopilotKitProvider.tsx | 24 +++++++++++++++++-- 5 files changed, 36 insertions(+), 46 deletions(-) rename {apps/react/demo => packages/react}/src/components/MCPAppsActivityRenderer.tsx (96%) diff --git a/apps/react/demo/src/app/page.tsx b/apps/react/demo/src/app/page.tsx index bb74d40..b6bbd54 100644 --- a/apps/react/demo/src/app/page.tsx +++ b/apps/react/demo/src/app/page.tsx @@ -10,7 +10,6 @@ import { import type { ToolsMenuItem } from "@copilotkitnext/react"; import { z } from "zod"; import { useMemo, useState } from "react"; -import { MCPAppsActivityRenderer, MCPAppsActivityContentSchema } from "../components/MCPAppsActivityRenderer"; // Disable static optimization for this page export const dynamic = "force-dynamic"; @@ -40,25 +39,7 @@ export default function Home() { }); return ( - { - return
{JSON.stringify(content, null, 2)}
; - }, - }, - ]} - > +
diff --git a/apps/react/demo/src/app/single/page.tsx b/apps/react/demo/src/app/single/page.tsx index e7eb026..d7e086b 100644 --- a/apps/react/demo/src/app/single/page.tsx +++ b/apps/react/demo/src/app/single/page.tsx @@ -1,11 +1,6 @@ "use client"; -import { - CopilotChat, - CopilotKitProvider, - useFrontendTool, - defineToolCallRenderer, -} from "@copilotkitnext/react"; +import { CopilotChat, CopilotKitProvider, useFrontendTool, defineToolCallRenderer } from "@copilotkitnext/react"; import type { ToolsMenuItem } from "@copilotkitnext/react"; import { z } from "zod"; import { useMemo } from "react"; @@ -40,15 +35,6 @@ export default function SingleEndpointDemo() { useSingleEndpoint renderToolCalls={[wildcardRenderer]} showDevConsole="auto" - renderActivityMessages={[ - { - activityType: "a2ui-surface", - content: z.any(), - render: ({ content }) => { - return
{JSON.stringify(content, null, 2)}
; - }, - }, - ]} >
@@ -103,4 +89,3 @@ function Chat() { return ; } - diff --git a/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx b/packages/react/src/components/MCPAppsActivityRenderer.tsx similarity index 96% rename from apps/react/demo/src/components/MCPAppsActivityRenderer.tsx rename to packages/react/src/components/MCPAppsActivityRenderer.tsx index cdb979a..47e7beb 100644 --- a/apps/react/demo/src/components/MCPAppsActivityRenderer.tsx +++ b/packages/react/src/components/MCPAppsActivityRenderer.tsx @@ -7,6 +7,11 @@ import type { AbstractAgent } from "@ag-ui/client"; // Protocol version supported const PROTOCOL_VERSION = "2025-06-18"; +/** + * Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType + */ +export const MCPAppsActivityType = "mcp-apps"; + // Zod schema for activity content validation export const MCPAppsActivityContentSchema = z.object({ result: z.object({ @@ -20,9 +25,8 @@ export const MCPAppsActivityContentSchema = z.object({ text: z.string().optional(), blob: z.string().optional(), }), - // Server info for proxying requests - serverUrl: z.string().optional(), - serverType: z.enum(["http", "sse"]).optional(), + // Server ID for proxying requests (MD5 hash of server config) + serverId: z.string().optional(), // Original tool input arguments toolInput: z.record(z.unknown()).optional(), }); @@ -233,11 +237,11 @@ export const MCPAppsActivityRenderer: React.FC = ( case "tools/call": { // Proxy tool call to MCP server via agent.runAgent() - const { serverUrl, serverType } = contentRef.current; + const { serverId } = contentRef.current; const currentAgent = agentRef.current; - if (!serverUrl) { - sendErrorResponse(msg.id, -32603, "No server URL available for proxying"); + if (!serverId) { + sendErrorResponse(msg.id, -32603, "No server ID available for proxying"); break; } @@ -252,8 +256,7 @@ export const MCPAppsActivityRenderer: React.FC = ( const runResult = await currentAgent.runAgent({ forwardedProps: { __proxiedMCPRequest: { - serverUrl, - serverType: serverType || "http", + serverId, method: "tools/call", params: msg.params, }, diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 988e35d..8f24bea 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -4,3 +4,4 @@ export * from "./chat"; export * from "./WildcardToolCallRender"; export * from "./CopilotKitInspector"; +export * from "./MCPAppsActivityRenderer"; diff --git a/packages/react/src/providers/CopilotKitProvider.tsx b/packages/react/src/providers/CopilotKitProvider.tsx index cb5e264..96e7090 100644 --- a/packages/react/src/providers/CopilotKitProvider.tsx +++ b/packages/react/src/providers/CopilotKitProvider.tsx @@ -19,6 +19,11 @@ import { FrontendTool } from "@copilotkitnext/core"; import { AbstractAgent } from "@ag-ui/client"; import { CopilotKitCoreReact } from "../lib/react-core"; import { CopilotKitInspector } from "../components/CopilotKitInspector"; +import { + MCPAppsActivityRenderer, + MCPAppsActivityContentSchema, + MCPAppsActivityType, +} from "../components/MCPAppsActivityRenderer"; // Define the context value interface - idiomatic React naming export interface CopilotKitContextValue { @@ -135,6 +140,21 @@ export const CopilotKitProvider: React.FC = ({ "renderActivityMessages must be a stable array.", ); + // Built-in activity renderers that are always included + const builtInActivityRenderers = useMemo[]>(() => [ + { + activityType: MCPAppsActivityType, + content: MCPAppsActivityContentSchema, + render: MCPAppsActivityRenderer, + }, + ], []); + + // Combine user-provided activity renderers with built-in ones + // User-provided renderers take precedence (come first) so they can override built-ins + const allActivityRenderers = useMemo(() => { + return [...renderActivityMessagesList, ...builtInActivityRenderers]; + }, [renderActivityMessagesList, builtInActivityRenderers]); + const frontendToolsList = useStableArrayProp( frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.", @@ -233,13 +253,13 @@ export const CopilotKitProvider: React.FC = ({ agents__unsafe_dev_only: agents, tools: allTools, renderToolCalls: allRenderToolCalls, - renderActivityMessages: renderActivityMessagesList, + renderActivityMessages: allActivityRenderers, renderCustomMessages: renderCustomMessagesList, }); return copilotkit; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [allTools, allRenderToolCalls, renderActivityMessagesList, renderCustomMessagesList, useSingleEndpoint]); + }, [allTools, allRenderToolCalls, allActivityRenderers, renderCustomMessagesList, useSingleEndpoint]); // Subscribe to render tool calls changes to force re-renders const [, forceUpdate] = useReducer((x) => x + 1, 0); From 4a72d1cc59d85ade893f9a1e0c28d040db3e9754 Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Fri, 5 Dec 2025 13:46:22 +0100 Subject: [PATCH 09/11] add AG-UI package to demo --- apps/react/demo/mcp-apps/package-lock.json | 11 + apps/react/demo/package.json | 1 + .../app/api/copilotkit/[[...slug]]/route.ts | 15 +- pnpm-lock.yaml | 315 +++++------------- 4 files changed, 96 insertions(+), 246 deletions(-) diff --git a/apps/react/demo/mcp-apps/package-lock.json b/apps/react/demo/mcp-apps/package-lock.json index 1543953..0d0aeeb 100644 --- a/apps/react/demo/mcp-apps/package-lock.json +++ b/apps/react/demo/mcp-apps/package-lock.json @@ -12,6 +12,7 @@ "zod": "^3.25.0" }, "devDependencies": { + "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.0.0", "cors": "^2.8.5", @@ -830,6 +831,16 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", diff --git a/apps/react/demo/package.json b/apps/react/demo/package.json index d474764..04032a2 100644 --- a/apps/react/demo/package.json +++ b/apps/react/demo/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/mcp-apps-middleware": "0.0.1", "@copilotkitnext/agent": "workspace:*", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/react": "workspace:*", diff --git a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts index 30ecac4..d130ad8 100644 --- a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts +++ b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts @@ -1,8 +1,7 @@ import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; import { handle } from "hono/vercel"; import { BasicAgent } from "@copilotkitnext/agent"; -// TODO: Re-enable once @ag-ui/mcp-apps-middleware is published -// import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware"; +import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware"; const determineModel = () => { if (process.env.OPENAI_API_KEY?.trim()) { @@ -21,13 +20,11 @@ const agent = new BasicAgent({ model: determineModel(), prompt: "You are a helpful AI assistant.", temperature: 0.7, -}); -// TODO: Re-enable once @ag-ui/mcp-apps-middleware is published -// .use(new MCPAppsMiddleware({ -// mcpServers: [ -// { type: "http", url: "http://localhost:3001/mcp" } -// ], -// })); +}).use(new MCPAppsMiddleware({ + mcpServers: [ + { type: "http", url: "http://localhost:3001/mcp" } + ], +})); const honoRuntime = new CopilotRuntime({ agents: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4362f3..0fb451c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,7 +74,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^18.2.0 - version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) + version: 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5) '@angular/cli': specifier: ^18.2.0 version: 18.2.20(chokidar@3.6.0) @@ -271,6 +271,9 @@ importers: '@ag-ui/client': specifier: 0.0.42-alpha.3 version: 0.0.42-alpha.3 + '@ag-ui/mcp-apps-middleware': + specifier: 0.0.1 + version: 0.0.1(@ag-ui/client@0.0.42-alpha.3)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75) '@copilotkitnext/agent': specifier: workspace:* version: link:../../../packages/agent @@ -345,43 +348,6 @@ importers: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) - apps/react/demo/mcp-apps: - dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.22.0 - version: 1.24.3(@cfworker/json-schema@4.1.1)(zod@3.25.75) - zod: - specifier: ^3.25.0 - version: 3.25.75 - devDependencies: - '@types/cors': - specifier: ^2.8.17 - version: 2.8.19 - '@types/express': - specifier: ^5.0.0 - version: 5.0.6 - '@types/node': - specifier: ^22.0.0 - version: 22.15.3 - cors: - specifier: ^2.8.5 - version: 2.8.5 - express: - specifier: ^5.1.0 - version: 5.1.0 - tsx: - specifier: ^4.0.0 - version: 4.20.5 - typescript: - specifier: ^5.7.2 - version: 5.8.2 - vite: - specifier: ^6.0.0 - version: 6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) - vite-plugin-singlefile: - specifier: ^2.3.0 - version: 2.3.0(rollup@4.45.1)(vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0)) - apps/react/storybook: dependencies: '@storybook/addon-essentials': @@ -1095,6 +1061,12 @@ packages: '@ag-ui/langgraph@0.0.11': resolution: {integrity: sha512-3xUkaOelnpQ5tbsbuoOTin71tTgWEN0GDZBjGs/7xAwly2Dn4fahbBAoscXullO/pH9kTGGgbuJ0rWDUgo6fKQ==} + '@ag-ui/mcp-apps-middleware@0.0.1': + resolution: {integrity: sha512-GndsrxBr12Ji/Ioa4zYALbmEGD7cglExIwL7wlco3/PVyqk1WsjybxRNyDj+p9NQh2HJ3s/ctEJWGxwVUyC+fw==} + peerDependencies: + '@ag-ui/client': '>=0.0.40' + rxjs: 7.8.1 + '@ag-ui/proto@0.0.36': resolution: {integrity: sha512-yaWLwJQmBaCtFstSoZEALztVckCYv+RD8guU91kL5AvywRXvZPP5mjiN+bEwvtw8VU3idXoee1ZbJGpSlSAQ8A==} @@ -5395,15 +5367,9 @@ packages: '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} - '@types/express-serve-static-core@5.1.0': - resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} - '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} - '@types/express@5.0.6': - resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -5516,9 +5482,6 @@ packages: '@types/serve-static@1.15.8': resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} - '@types/serve-static@2.2.0': - resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -12134,13 +12097,6 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-plugin-singlefile@2.3.0: - resolution: {integrity: sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==} - engines: {node: '>18.0.0'} - peerDependencies: - rollup: ^4.44.1 - vite: ^5.4.11 || ^6.0.0 || ^7.0.0 - vite@5.4.19: resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12172,46 +12128,6 @@ packages: terser: optional: true - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@7.0.5: resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -12782,6 +12698,16 @@ snapshots: - react - react-dom + '@ag-ui/mcp-apps-middleware@0.0.1(@ag-ui/client@0.0.42-alpha.3)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75)': + dependencies: + '@ag-ui/client': 0.0.42-alpha.3 + '@modelcontextprotocol/sdk': 1.24.3(@cfworker/json-schema@4.1.1)(zod@3.25.75) + rxjs: 7.8.1 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + - zod + '@ag-ui/proto@0.0.36': dependencies: '@ag-ui/core': 0.0.36 @@ -12897,13 +12823,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/core': 18.2.20(chokidar@3.6.0) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -12960,11 +12886,11 @@ snapshots: webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 4.1.11 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 3.4.17 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -12982,15 +12908,14 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli - optional: true - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(chokidar@4.0.3)(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/build-webpack': 0.1802.20(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) '@angular-devkit/core': 18.2.20(chokidar@4.0.3) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -13045,7 +12970,7 @@ snapshots: watchpack: 2.4.1 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: @@ -13069,14 +12994,15 @@ snapshots: - uglify-js - utf-8-validate - webpack-cli + optional: true - '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.17)(typescript@5.4.5)': + '@angular-devkit/build-angular@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@swc/core@1.12.11)(@types/node@22.15.3)(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(lightningcss@1.30.1)(ng-packagr@18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@4.1.11)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1802.20(chokidar@3.6.0)(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) - '@angular-devkit/core': 18.2.20(chokidar@3.6.0) - '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5) + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/build-webpack': 0.1802.20(webpack-dev-server@5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + '@angular-devkit/core': 18.2.20(chokidar@4.0.3) + '@angular/build': 18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -13133,11 +13059,11 @@ snapshots: webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) optionalDependencies: esbuild: 0.23.0 - ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@3.4.17)(tslib@2.8.1)(typescript@5.4.5) - tailwindcss: 3.4.17 + ng-packagr: 18.2.1(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tailwindcss@4.1.11)(tslib@2.8.1)(typescript@5.4.5) + tailwindcss: 4.1.11 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -13180,7 +13106,7 @@ snapshots: '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) rxjs: 7.8.1 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - webpack-dev-server: 5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + webpack-dev-server: 5.2.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) transitivePeerDependencies: - chokidar @@ -13242,10 +13168,10 @@ snapshots: '@angular/core': 18.2.13(rxjs@7.8.1)(zone.js@0.14.10) tslib: 2.8.1 - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) + '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 @@ -13274,7 +13200,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 4.1.11 + tailwindcss: 3.4.17 transitivePeerDependencies: - '@types/node' - chokidar @@ -13284,9 +13210,8 @@ snapshots: - sugarss - supports-color - terser - optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13296,7 +13221,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) '@inquirer/confirm': 3.1.22 - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6)) browserslist: 4.25.1 critters: 0.0.24 esbuild: 0.23.0 @@ -13313,11 +13238,11 @@ snapshots: sass: 1.77.6 semver: 7.6.3 typescript: 5.4.5 - vite: 5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1) + vite: 5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6) watchpack: 2.4.1 optionalDependencies: - less: 4.4.1 - postcss: 8.5.6 + less: 4.2.0 + postcss: 8.4.41 tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' @@ -13330,7 +13255,7 @@ snapshots: - terser optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(chokidar@4.0.3)(less@4.4.1)(lightningcss@1.30.1)(postcss@8.5.6)(tailwindcss@4.1.11)(terser@5.43.1)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) @@ -13340,7 +13265,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) '@inquirer/confirm': 3.1.22 - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1)) browserslist: 4.25.1 critters: 0.0.24 esbuild: 0.23.0 @@ -13357,11 +13282,11 @@ snapshots: sass: 1.77.6 semver: 7.6.3 typescript: 5.4.5 - vite: 5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6) + vite: 5.4.19(@types/node@22.15.3)(less@4.4.1)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.43.1) watchpack: 2.4.1 optionalDependencies: - less: 4.2.0 - postcss: 8.4.41 + less: 4.4.1 + postcss: 8.5.6 tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' @@ -13372,11 +13297,12 @@ snapshots: - sugarss - supports-color - terser + optional: true - '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(chokidar@3.6.0)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@3.4.17)(terser@5.31.6)(typescript@5.4.5)': + '@angular/build@18.2.20(@angular/compiler-cli@18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(postcss@8.4.41)(tailwindcss@4.1.11)(terser@5.31.6)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1802.20(chokidar@3.6.0) + '@angular-devkit/architect': 0.1802.20(chokidar@4.0.3) '@angular/compiler-cli': 18.2.13(@angular/compiler@18.2.13(@angular/core@18.2.13(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 @@ -13405,7 +13331,7 @@ snapshots: optionalDependencies: less: 4.2.0 postcss: 8.4.41 - tailwindcss: 3.4.17 + tailwindcss: 4.1.11 transitivePeerDependencies: - '@types/node' - chokidar @@ -18458,13 +18384,6 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.5 - '@types/express-serve-static-core@5.1.0': - dependencies: - '@types/node': 22.15.3 - '@types/qs': 6.14.0 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.5 - '@types/express@4.17.23': dependencies: '@types/body-parser': 1.19.6 @@ -18472,12 +18391,6 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 - '@types/express@5.0.6': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.0 - '@types/serve-static': 2.2.0 - '@types/geojson@7946.0.16': {} '@types/hast@3.0.4': @@ -18589,11 +18502,6 @@ snapshots: '@types/node': 22.15.3 '@types/send': 0.17.5 - '@types/serve-static@2.2.0': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 22.15.3 - '@types/sockjs@0.3.36': dependencies: '@types/node': 22.15.3 @@ -19565,7 +19473,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -20445,10 +20353,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -21345,7 +21249,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -21502,7 +21406,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -22148,6 +22052,17 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11) + html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.17.21 + pretty-error: 4.0.0 + tapable: 2.2.2 + optionalDependencies: + webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) + optional: true + htmlparser2@6.1.0: dependencies: domelementtype: 2.3.0 @@ -25528,7 +25443,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -25682,7 +25597,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -27155,12 +27070,6 @@ snapshots: - tsx - yaml - vite-plugin-singlefile@2.3.0(rollup@4.45.1)(vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0)): - dependencies: - micromatch: 4.0.8 - rollup: 4.45.1 - vite: 6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0) - vite@5.4.19(@types/node@22.15.3)(less@4.2.0)(lightningcss@1.30.1)(sass@1.77.6)(terser@5.31.6): dependencies: esbuild: 0.21.5 @@ -27201,25 +27110,6 @@ snapshots: sass: 1.90.0 terser: 5.43.1 - vite@6.4.1(@types/node@22.15.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): - dependencies: - esbuild: 0.25.6 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.45.1 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.15.3 - fsevents: 2.3.3 - jiti: 2.5.1 - less: 4.4.1 - lightningcss: 1.30.1 - sass: 1.90.0 - terser: 5.43.1 - tsx: 4.20.5 - yaml: 2.8.0 - vite@7.0.5(@types/node@20.19.9)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.0): dependencies: esbuild: 0.25.6 @@ -27516,17 +27406,6 @@ snapshots: optionalDependencies: webpack: 5.100.0(@swc/core@1.12.11) - webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): - dependencies: - colorette: 2.0.20 - memfs: 4.36.3 - mime-types: 2.1.35 - on-finished: 2.4.1 - range-parser: 1.2.1 - schema-utils: 4.3.2 - optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - webpack-dev-middleware@7.4.2(webpack@5.100.0(@swc/core@1.12.11)): dependencies: colorette: 2.0.20 @@ -27550,44 +27429,6 @@ snapshots: optionalDependencies: webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) - webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)): - dependencies: - '@types/bonjour': 3.5.13 - '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.23 - '@types/express-serve-static-core': 4.19.6 - '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.8 - '@types/sockjs': 0.3.36 - '@types/ws': 8.18.1 - ansi-html-community: 0.0.8 - bonjour-service: 1.3.0 - chokidar: 3.6.0 - colorette: 2.0.20 - compression: 1.8.1 - connect-history-api-fallback: 2.0.0 - express: 4.21.2 - graceful-fs: 4.2.11 - http-proxy-middleware: 2.0.9(@types/express@4.17.23) - ipaddr.js: 2.2.0 - launch-editor: 2.11.1 - open: 10.1.2 - p-retry: 6.2.1 - schema-utils: 4.3.2 - selfsigned: 2.4.1 - serve-index: 1.9.1 - sockjs: 0.3.24 - spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - ws: 8.18.3 - optionalDependencies: - webpack: 5.100.0(@swc/core@1.12.11)(esbuild@0.25.6) - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - webpack-dev-server@5.2.2(webpack@5.100.0(@swc/core@1.12.11)): dependencies: '@types/bonjour': 3.5.13 @@ -27655,7 +27496,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) + webpack-dev-middleware: 7.4.2(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) ws: 8.18.3 optionalDependencies: webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) @@ -27686,12 +27527,12 @@ snapshots: optionalDependencies: html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)(esbuild@0.25.6)) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.100.0(@swc/core@1.12.11)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)))(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)): dependencies: typed-assert: 1.0.9 webpack: 5.94.0(@swc/core@1.12.11)(esbuild@0.23.0) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.100.0(@swc/core@1.12.11)) + html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.12.11)(esbuild@0.23.0)) webpack-virtual-modules@0.6.2: {} From bce8590731f7f273cda682fd3f3fd30bb9bfa6ce Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Wed, 17 Dec 2025 16:08:20 +0100 Subject: [PATCH 10/11] move mcp apps to a separate endpoint --- .../api/copilotkit-mcp/[[...slug]]/route.ts | 42 ++++++++++++++ .../app/api/copilotkit/[[...slug]]/route.ts | 7 +-- apps/react/demo/src/app/mcp-apps/page.tsx | 55 +++++++++++++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 apps/react/demo/src/app/api/copilotkit-mcp/[[...slug]]/route.ts create mode 100644 apps/react/demo/src/app/mcp-apps/page.tsx diff --git a/apps/react/demo/src/app/api/copilotkit-mcp/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit-mcp/[[...slug]]/route.ts new file mode 100644 index 0000000..7888203 --- /dev/null +++ b/apps/react/demo/src/app/api/copilotkit-mcp/[[...slug]]/route.ts @@ -0,0 +1,42 @@ +import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; +import { handle } from "hono/vercel"; +import { BasicAgent } from "@copilotkitnext/agent"; +import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware"; + +const determineModel = () => { + if (process.env.OPENAI_API_KEY?.trim()) { + return "openai/gpt-4o"; + } + if (process.env.ANTHROPIC_API_KEY?.trim()) { + return "anthropic/claude-sonnet-4.5"; + } + if (process.env.GOOGLE_API_KEY?.trim()) { + return "google/gemini-2.5-pro"; + } + return "openai/gpt-4o"; +}; + +const agent = new BasicAgent({ + model: determineModel(), + prompt: "You are a helpful AI assistant with access to MCP apps and tools.", + temperature: 0.7, +}).use(new MCPAppsMiddleware({ + mcpServers: [ + { type: "http", url: "http://localhost:3001/mcp" } + ], +})); + +const honoRuntime = new CopilotRuntime({ + agents: { + default: agent, + }, + runner: new InMemoryAgentRunner(), +}); + +const app = createCopilotEndpoint({ + runtime: honoRuntime, + basePath: "/api/copilotkit-mcp", +}); + +export const GET = handle(app); +export const POST = handle(app); diff --git a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts index d130ad8..38bb58d 100644 --- a/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts +++ b/apps/react/demo/src/app/api/copilotkit/[[...slug]]/route.ts @@ -1,7 +1,6 @@ import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from "@copilotkitnext/runtime"; import { handle } from "hono/vercel"; import { BasicAgent } from "@copilotkitnext/agent"; -import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware"; const determineModel = () => { if (process.env.OPENAI_API_KEY?.trim()) { @@ -20,11 +19,7 @@ const agent = new BasicAgent({ model: determineModel(), prompt: "You are a helpful AI assistant.", temperature: 0.7, -}).use(new MCPAppsMiddleware({ - mcpServers: [ - { type: "http", url: "http://localhost:3001/mcp" } - ], -})); +}); const honoRuntime = new CopilotRuntime({ agents: { diff --git a/apps/react/demo/src/app/mcp-apps/page.tsx b/apps/react/demo/src/app/mcp-apps/page.tsx new file mode 100644 index 0000000..47a7301 --- /dev/null +++ b/apps/react/demo/src/app/mcp-apps/page.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { CopilotKitProvider, CopilotSidebar } from "@copilotkitnext/react"; + +export const dynamic = "force-dynamic"; + +export default function MCPAppsDemoPage() { + return ( + + + + ); +} + +function AppLayout() { + return ( +
+
+
+

MCP Apps Demo

+

+ This page demonstrates the MCP Apps Extension. The assistant has access to MCP tools + that can render interactive UI components directly in the chat. +

+
+

Available MCP Tools:

+
    +
  • create-ui-raw - Renders a raw HTML UI with a custom message
  • +
  • get-weather - Returns weather data for a location
  • +
+

+ Note: Make sure the MCP server is running on port 3001 (cd mcp-apps && npm start) +

+
+
+ +
+ {Array.from({ length: 4 }).map((_, index) => ( +
+

Demo Card {index + 1}

+

+ Try asking the assistant to use MCP tools. The UI will render directly in the chat sidebar. +

+
+ ))} +
+
+ + +
+ ); +} From 7ae110351923f7200ef7716139df62c61d9e4a3a Mon Sep 17 00:00:00 2001 From: Markus Ecker Date: Thu, 18 Dec 2025 10:57:17 +0100 Subject: [PATCH 11/11] update AG-UI version --- apps/react/demo/package.json | 2 +- packages/agent/package.json | 2 +- packages/angular/package.json | 4 +- packages/core/package.json | 2 +- packages/react/package.json | 4 +- packages/runtime/package.json | 6 +- packages/shared/package.json | 2 +- packages/sqlite-runner/package.json | 2 +- packages/web-inspector/package.json | 2 +- pnpm-lock.yaml | 120 ++++++++++++++-------------- 10 files changed, 73 insertions(+), 73 deletions(-) diff --git a/apps/react/demo/package.json b/apps/react/demo/package.json index 04032a2..8929b4f 100644 --- a/apps/react/demo/package.json +++ b/apps/react/demo/package.json @@ -12,7 +12,7 @@ "test:coverage": "vitest --coverage" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "@ag-ui/mcp-apps-middleware": "0.0.1", "@copilotkitnext/agent": "workspace:*", "@copilotkitnext/core": "workspace:*", diff --git a/packages/agent/package.json b/packages/agent/package.json index 21894a0..cc54546 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -36,7 +36,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "@ai-sdk/anthropic": "^2.0.22", "@ai-sdk/google": "^2.0.17", "@ai-sdk/mcp": "^0.0.8", diff --git a/packages/angular/package.json b/packages/angular/package.json index d81f42b..837e9b5 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -31,8 +31,8 @@ "test:watch": "vitest --watch" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", - "@ag-ui/core": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", + "@ag-ui/core": "0.0.42", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "clsx": "^2.1.1", diff --git a/packages/core/package.json b/packages/core/package.json index 1755eac..1ca6b7a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,7 +36,7 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "@copilotkitnext/shared": "workspace:*", "rxjs": "7.8.1", "zod": "^3.25.75", diff --git a/packages/react/package.json b/packages/react/package.json index ac255ad..42eaead 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -53,8 +53,8 @@ "vitest": "^3.2.4" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", - "@ag-ui/core": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", + "@ag-ui/core": "0.0.42", "@copilotkitnext/core": "workspace:*", "@copilotkitnext/shared": "workspace:*", "@copilotkitnext/web-inspector": "workspace:*", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 340776b..00333e1 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -44,9 +44,9 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", - "@ag-ui/core": "0.0.42-alpha.3", - "@ag-ui/encoder": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", + "@ag-ui/core": "0.0.42", + "@ag-ui/encoder": "0.0.42", "@copilotkitnext/shared": "workspace:*", "cors": "^2.8.5", "express": "^4.21.2", diff --git a/packages/shared/package.json b/packages/shared/package.json index 9931398..0b39315 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -31,7 +31,7 @@ "typescript": "5.8.2" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "uuid": "^11.1.0", "partial-json": "^0.1.7" }, diff --git a/packages/sqlite-runner/package.json b/packages/sqlite-runner/package.json index 1660407..265460a 100644 --- a/packages/sqlite-runner/package.json +++ b/packages/sqlite-runner/package.json @@ -37,7 +37,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "@copilotkitnext/runtime": "workspace:*", "rxjs": "7.8.1" }, diff --git a/packages/web-inspector/package.json b/packages/web-inspector/package.json index 02024d9..0068015 100644 --- a/packages/web-inspector/package.json +++ b/packages/web-inspector/package.json @@ -25,7 +25,7 @@ "clean": "rm -rf dist src/styles/generated.css" }, "dependencies": { - "@ag-ui/client": "0.0.42-alpha.3", + "@ag-ui/client": "0.0.42", "@copilotkitnext/core": "workspace:*", "lit": "^3.2.0", "lucide": "^0.525.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fb451c..515359a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -269,11 +269,11 @@ importers: apps/react/demo: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ag-ui/mcp-apps-middleware': specifier: 0.0.1 - version: 0.0.1(@ag-ui/client@0.0.42-alpha.3)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75) + version: 0.0.1(@ag-ui/client@0.0.42)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75) '@copilotkitnext/agent': specifier: workspace:* version: link:../../../packages/agent @@ -412,8 +412,8 @@ importers: packages/agent: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ai-sdk/anthropic': specifier: ^2.0.22 version: 2.0.23(zod@3.25.75) @@ -467,11 +467,11 @@ importers: packages/angular: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ag-ui/core': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -609,8 +609,8 @@ importers: packages/core: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared @@ -722,11 +722,11 @@ importers: packages/react: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ag-ui/core': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -852,14 +852,14 @@ importers: packages/runtime: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ag-ui/core': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@ag-ui/encoder': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/shared': specifier: workspace:* version: link:../shared @@ -913,8 +913,8 @@ importers: packages/shared: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 partial-json: specifier: ^0.1.7 version: 0.1.7 @@ -944,8 +944,8 @@ importers: packages/sqlite-runner: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/runtime': specifier: workspace:* version: link:../runtime @@ -986,8 +986,8 @@ importers: packages/web-inspector: dependencies: '@ag-ui/client': - specifier: 0.0.42-alpha.3 - version: 0.0.42-alpha.3 + specifier: 0.0.42 + version: 0.0.42 '@copilotkitnext/core': specifier: workspace:* version: link:../core @@ -1034,30 +1034,30 @@ packages: '@ag-ui/client@0.0.36': resolution: {integrity: sha512-1Ey2KqK9KQpRJcnJvKPfVyLiTK4+CLBQZ085oJvr6T1nznw224j0KyzXNJ7cRjXeEGnuafmXTgpU+xEbN3xuYQ==} + '@ag-ui/client@0.0.42': + resolution: {integrity: sha512-zAbP+sZJImR5bUpR2ni7RtuuNZMuesaxviynyIgzKlr1k2VCM49mFpbDUKU4TH4Cneu+Xe7OEnO8qCOCIzBAww==} + '@ag-ui/client@0.0.42-alpha.1': resolution: {integrity: sha512-vnaCclWRpVtsuidUF1X08XGRc1NDp0z15p50DWPXSgD2fXnsXC7DCI3IZEN5B9ExhY0CqlhdngM4qYtl3Fr2Uw==} - '@ag-ui/client@0.0.42-alpha.3': - resolution: {integrity: sha512-VXJlCJ0XAYqBB5+RaKXs5lHrwVqm5pXEjwdPhBSjwyOWD04bTkIxPrjtzdZFyT/6mrJnnqcd4zywHSEsCtvOnQ==} - '@ag-ui/core@0.0.36': resolution: {integrity: sha512-uYUrzw6uxuw4qVQ61mdSeiG0mFh2n/VAWmWsWzwETDuhqJZT7rFmd07IajcFWcyItMr1wjqxFDdlklucAyEYNA==} + '@ag-ui/core@0.0.42': + resolution: {integrity: sha512-C2hMg4Gs5oiUDgK9cA2RsTwSSmFZdIsqPklDrFw/Ue+quH6EU3vKp5YoOq7nuaQYO4pO8Em+Z+l5/M5PpcvP1g==} + '@ag-ui/core@0.0.42-alpha.1': resolution: {integrity: sha512-jnGJFax6YG6pjqDcIC1RQajjFke8tiLaNF4GmRTUp5vz0/MN+8FsZF/Df6K81pzuPu+FueUmr62TprDO5qo8rw==} - '@ag-ui/core@0.0.42-alpha.3': - resolution: {integrity: sha512-n6/ExW3yct8N+vJYS3YrSah9BKUWi8cEugwJobGr8mOXVVEK8+pHKYLf2UAWsvz6L+mTYoYyn5LD4faOr51DBQ==} - '@ag-ui/encoder@0.0.36': resolution: {integrity: sha512-p8UNh6a77G/oe/4EZmwkTeYCN/5SnqSY2Cz8f8psZpk4LKzzrPkRNykrUAIBsi1wMp50/VQiM27oTRaade/Qkw==} + '@ag-ui/encoder@0.0.42': + resolution: {integrity: sha512-97B5MMCSs82t/y41uk2NrLBYFhbvn4kYsKQHMCfy8tjSWubyxh3zP7N9yHo8zJeSPe3WvzTvclyXNiGxSOsorg==} + '@ag-ui/encoder@0.0.42-alpha.1': resolution: {integrity: sha512-GwTh81t3fDoN7BN7VOsxPtcAvcPhHjn1aMkYUIm1cQ+s5sU9+JTlhgNemFE/N6T5ssddnfX+6ntOWdB8JWtXkA==} - '@ag-ui/encoder@0.0.42-alpha.3': - resolution: {integrity: sha512-slpJOFlWFNTt3iXV0WiGQjqJBL2SFVuj0m7ddDEEht9SXdA3xlx6x3e3D/on3sVn06nFUTTR5MMlAD8ff1nv0Q==} - '@ag-ui/langgraph@0.0.11': resolution: {integrity: sha512-3xUkaOelnpQ5tbsbuoOTin71tTgWEN0GDZBjGs/7xAwly2Dn4fahbBAoscXullO/pH9kTGGgbuJ0rWDUgo6fKQ==} @@ -1070,12 +1070,12 @@ packages: '@ag-ui/proto@0.0.36': resolution: {integrity: sha512-yaWLwJQmBaCtFstSoZEALztVckCYv+RD8guU91kL5AvywRXvZPP5mjiN+bEwvtw8VU3idXoee1ZbJGpSlSAQ8A==} + '@ag-ui/proto@0.0.42': + resolution: {integrity: sha512-NDUwSgMnGEqxZGkWIJ1ge5t3Q7Kiddj360x2JAWaIfv9w+7tDJ0pmgyzf3/SXp605aY2wZiDLBtJ6jKZeg1lFg==} + '@ag-ui/proto@0.0.42-alpha.1': resolution: {integrity: sha512-rOPWdDv40tu9I5qonxj3diGYsN8aX9qIDV4Ef5GQN6UwbzDAx3MQI7AmwgcoPuyRQFMtGiepyDpZZN9RETGDfw==} - '@ag-ui/proto@0.0.42-alpha.3': - resolution: {integrity: sha512-CKK+SB4FbrZt/lE54Dh/a5lplyFkVwf1YJykazHSb6iKZT65nFARt1Z9tWfR56zNLKX0gJ8/Yq30/fuChcFpZw==} - '@ai-sdk/anthropic@2.0.23': resolution: {integrity: sha512-ZEBiiv1UhjGjBwUU63pFhLK5LCSlNDb1idY9K1oZHm5/Fda1cuTojf32tOp0opH0RPbPAN/F8fyyNjbU33n9Kw==} engines: {node: '>=18'} @@ -12627,11 +12627,11 @@ snapshots: uuid: 11.1.0 zod: 3.25.75 - '@ag-ui/client@0.0.42-alpha.1': + '@ag-ui/client@0.0.42': dependencies: - '@ag-ui/core': 0.0.42-alpha.1 - '@ag-ui/encoder': 0.0.42-alpha.1 - '@ag-ui/proto': 0.0.42-alpha.1 + '@ag-ui/core': 0.0.42 + '@ag-ui/encoder': 0.0.42 + '@ag-ui/proto': 0.0.42 '@types/uuid': 10.0.0 compare-versions: 6.1.1 fast-json-patch: 3.1.1 @@ -12640,11 +12640,11 @@ snapshots: uuid: 11.1.0 zod: 3.25.75 - '@ag-ui/client@0.0.42-alpha.3': + '@ag-ui/client@0.0.42-alpha.1': dependencies: - '@ag-ui/core': 0.0.42-alpha.3 - '@ag-ui/encoder': 0.0.42-alpha.3 - '@ag-ui/proto': 0.0.42-alpha.3 + '@ag-ui/core': 0.0.42-alpha.1 + '@ag-ui/encoder': 0.0.42-alpha.1 + '@ag-ui/proto': 0.0.42-alpha.1 '@types/uuid': 10.0.0 compare-versions: 6.1.1 fast-json-patch: 3.1.1 @@ -12658,12 +12658,12 @@ snapshots: rxjs: 7.8.1 zod: 3.25.75 - '@ag-ui/core@0.0.42-alpha.1': + '@ag-ui/core@0.0.42': dependencies: rxjs: 7.8.1 zod: 3.25.75 - '@ag-ui/core@0.0.42-alpha.3': + '@ag-ui/core@0.0.42-alpha.1': dependencies: rxjs: 7.8.1 zod: 3.25.75 @@ -12673,16 +12673,16 @@ snapshots: '@ag-ui/core': 0.0.36 '@ag-ui/proto': 0.0.36 + '@ag-ui/encoder@0.0.42': + dependencies: + '@ag-ui/core': 0.0.42 + '@ag-ui/proto': 0.0.42 + '@ag-ui/encoder@0.0.42-alpha.1': dependencies: '@ag-ui/core': 0.0.42-alpha.1 '@ag-ui/proto': 0.0.42-alpha.1 - '@ag-ui/encoder@0.0.42-alpha.3': - dependencies: - '@ag-ui/core': 0.0.42-alpha.3 - '@ag-ui/proto': 0.0.42-alpha.3 - '@ag-ui/langgraph@0.0.11(@opentelemetry/api@1.9.0)(openai@4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.75))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ag-ui/client': 0.0.36 @@ -12698,9 +12698,9 @@ snapshots: - react - react-dom - '@ag-ui/mcp-apps-middleware@0.0.1(@ag-ui/client@0.0.42-alpha.3)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75)': + '@ag-ui/mcp-apps-middleware@0.0.1(@ag-ui/client@0.0.42)(@cfworker/json-schema@4.1.1)(rxjs@7.8.1)(zod@3.25.75)': dependencies: - '@ag-ui/client': 0.0.42-alpha.3 + '@ag-ui/client': 0.0.42 '@modelcontextprotocol/sdk': 1.24.3(@cfworker/json-schema@4.1.1)(zod@3.25.75) rxjs: 7.8.1 transitivePeerDependencies: @@ -12714,15 +12714,15 @@ snapshots: '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1 - '@ag-ui/proto@0.0.42-alpha.1': + '@ag-ui/proto@0.0.42': dependencies: - '@ag-ui/core': 0.0.42-alpha.1 + '@ag-ui/core': 0.0.42 '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1 - '@ag-ui/proto@0.0.42-alpha.3': + '@ag-ui/proto@0.0.42-alpha.1': dependencies: - '@ag-ui/core': 0.0.42-alpha.3 + '@ag-ui/core': 0.0.42-alpha.1 '@bufbuild/protobuf': 2.6.0 '@protobuf-ts/protoc': 2.11.1