From aa326fee8a3bdd5a71daf7de2c7fce955da4a32a Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Tue, 14 Apr 2026 14:21:10 +0000 Subject: [PATCH 1/3] =?UTF-8?q?WIP:=20port=20to=20concrete-Protocol=20appr?= =?UTF-8?q?oach=20(#1891)=20=E2=80=94=20mechanical=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - import paths sdk/* → @modelcontextprotocol/{client,server} - events.ts: ProtocolWithEvents extends Protocol (concrete), v1-schema-shim setRequestHandler - request/notification call-shape transforms (perl) - drop assert*Capability overrides (Protocol no longer abstract) - server/index.ts copied from #612 port - tsconfig: exclude *.examples.ts, docs/ (same as #612) NOT YET: *Schema imports → specTypeSchema(), request() visibility, ~225 tsc errors remain --- package-lock.json | 66 +++++- package.json | 8 +- src/app-bridge.examples.ts | 6 +- src/app-bridge.test.ts | 6 +- src/app-bridge.ts | 68 ++---- src/app.examples.ts | 4 +- src/app.ts | 37 +-- src/events.ts | 4 +- src/generated/schema.ts | 2 +- src/message-transport.ts | 4 +- src/react/useApp.tsx | 2 +- src/server/index.examples.ts | 2 +- src/server/index.test.ts | 2 +- src/server/index.ts | 431 ++++++++--------------------------- src/spec.types.ts | 2 +- src/types.ts | 2 +- tsconfig.json | 2 +- 17 files changed, 212 insertions(+), 436 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e689bcb9..e81a3aa69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,14 @@ "workspaces": [ "examples/*" ], + "dependencies": { + "@modelcontextprotocol/client": "file:../../../../../tmp/modelcontextprotocol-client-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/express": "file:../../../../../tmp/modelcontextprotocol-express-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/node": "file:../../../../../tmp/modelcontextprotocol-node-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/server": "file:../../../../../tmp/modelcontextprotocol-server-2.0.0-alpha.2.tgz" + }, "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/sdk": "^1.29.0", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -47,7 +52,6 @@ "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -2543,6 +2547,36 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.0.0-alpha.2", + "resolved": "file:../../../../../tmp/modelcontextprotocol-client-2.0.0-alpha.2.tgz", + "integrity": "sha512-ry39WpcAUsX7lwpIID9t4M8Tu0dTcmizLf7Av4f3QpDESayNr87RK/ZVzMu4cOkHY4Y/cG9AVYvJpN4bywOgVw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/express": { + "version": "2.0.0-alpha.2", + "resolved": "file:../../../../../tmp/modelcontextprotocol-express-2.0.0-alpha.2.tgz", + "integrity": "sha512-TlTUFnpUCH3dqegSnAZpCe+pn4fM43tow0KLRa3QTTWaC/jctsl51QQykqXHse5UhBpqGw+QF1rLfWOwEI9PPw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0-alpha.2", + "express": "^5.2.1" + } + }, "node_modules/@modelcontextprotocol/ext-apps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.0.tgz", @@ -2590,6 +2624,22 @@ "resolved": "examples/basic-host", "link": true }, + "node_modules/@modelcontextprotocol/node": { + "version": "2.0.0-alpha.2", + "resolved": "file:../../../../../tmp/modelcontextprotocol-node-2.0.0-alpha.2.tgz", + "integrity": "sha512-+fuoCMM5zpku7qJQNxyeLW9DNVHvaQivkFAqyVw5A410QiHPGtqoh6eDt/nwvnEBw2m21dyWxm22iNVc5CavbA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0-alpha.2", + "hono": "^4.11.4" + } + }, "node_modules/@modelcontextprotocol/quickstart": { "resolved": "examples/quickstart", "link": true @@ -2634,6 +2684,18 @@ } } }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0-alpha.2", + "resolved": "file:../../../../../tmp/modelcontextprotocol-server-2.0.0-alpha.2.tgz", + "integrity": "sha512-65RJVH41c9Ft48xEQzssGCtmIwgzsvymuO5b7L6GV+g2yymZJFAVn9uETuesVV1CBgxGyEFg1JLWJJkws75Oyw==", + "license": "MIT", + "dependencies": { + "zod": "^4.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@modelcontextprotocol/server-basic-preact": { "resolved": "examples/basic-server-preact", "link": true diff --git a/package.json b/package.json index 34b2c6a46..2fc0ab7d7 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "author": "Olivier Chafik", "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/sdk": "^1.29.0", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -107,7 +106,6 @@ "zod": "^4.1.13" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -125,5 +123,11 @@ "seroval-plugins": "1.4.2", "solid-js": "1.9.10", "@types/node": "20.19.27" + }, + "dependencies": { + "@modelcontextprotocol/client": "file:../../../../../tmp/modelcontextprotocol-client-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/express": "file:../../../../../tmp/modelcontextprotocol-express-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/node": "file:../../../../../tmp/modelcontextprotocol-node-2.0.0-alpha.2.tgz", + "@modelcontextprotocol/server": "file:../../../../../tmp/modelcontextprotocol-server-2.0.0-alpha.2.tgz" } } diff --git a/src/app-bridge.examples.ts b/src/app-bridge.examples.ts index b613451c1..caeeae92f 100644 --- a/src/app-bridge.examples.ts +++ b/src/app-bridge.examples.ts @@ -7,15 +7,15 @@ * @module */ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { Client } from "@modelcontextprotocol/client"; +import type { Transport } from "@modelcontextprotocol/client"; import { CallToolResult, CallToolResultSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListPromptsResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { AppBridge, PostMessageTransport } from "./app-bridge.js"; import type { McpUiDisplayMode } from "./types.js"; diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 4761d766a..76dc424a2 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import type { ServerCapabilities } from "@modelcontextprotocol/sdk/types.js"; +import type { Client } from "@modelcontextprotocol/client"; +import type { ServerCapabilities } from "@modelcontextprotocol/client"; import { EmptyResultSchema, ListPromptsResultSchema, @@ -11,7 +11,7 @@ import { ReadResourceResultSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { App } from "./app"; import { diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 77125872c..caf81a0d8 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -1,5 +1,5 @@ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { Client } from "@modelcontextprotocol/client"; +import { Transport } from "@modelcontextprotocol/client"; import { CallToolRequest, CallToolRequestSchema, @@ -37,11 +37,11 @@ import { Tool, ToolListChangedNotification, ToolListChangedNotificationSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { ProtocolOptions, RequestOptions, -} from "@modelcontextprotocol/sdk/shared/protocol.js"; +} from "@modelcontextprotocol/client"; import { ProtocolWithEvents } from "./events"; import { @@ -1033,10 +1033,7 @@ export class AppBridge extends ProtocolWithEvents< * @see `ToolListChangedNotification` from @modelcontextprotocol/sdk for the notification type */ sendToolListChanged(params: ToolListChangedNotification["params"] = {}) { - return this.notification({ - method: "notifications/tools/list_changed" as const, - params, - }); + return this.notification("notifications/tools/list_changed", params); } /** @@ -1232,10 +1229,7 @@ export class AppBridge extends ProtocolWithEvents< sendResourceListChanged( params: ResourceListChangedNotification["params"] = {}, ) { - return this.notification({ - method: "notifications/resources/list_changed" as const, - params, - }); + return this.notification("notifications/resources/list_changed", params); } /** @@ -1315,10 +1309,7 @@ export class AppBridge extends ProtocolWithEvents< * @see `PromptListChangedNotification` from @modelcontextprotocol/sdk for the notification type */ sendPromptListChanged(params: PromptListChangedNotification["params"] = {}) { - return this.notification({ - method: "notifications/prompts/list_changed" as const, - params, - }); + return this.notification("notifications/prompts/list_changed", params); } /** @@ -1462,10 +1453,7 @@ export class AppBridge extends ProtocolWithEvents< sendHostContextChange( params: McpUiHostContextChangedNotification["params"], ): Promise | void { - return this.notification({ - method: "ui/notifications/host-context-changed" as const, - params, - }); + return this.notification("ui/notifications/host-context-changed", params); } /** @@ -1491,10 +1479,7 @@ export class AppBridge extends ProtocolWithEvents< * @see {@link sendToolResult `sendToolResult`} for sending results after execution */ sendToolInput(params: McpUiToolInputNotification["params"]) { - return this.notification({ - method: "ui/notifications/tool-input" as const, - params, - }); + return this.notification("ui/notifications/tool-input", params); } /** @@ -1527,10 +1512,7 @@ export class AppBridge extends ProtocolWithEvents< * @see {@link sendToolInput `sendToolInput`} for sending complete arguments */ sendToolInputPartial(params: McpUiToolInputPartialNotification["params"]) { - return this.notification({ - method: "ui/notifications/tool-input-partial" as const, - params, - }); + return this.notification("ui/notifications/tool-input-partial", params); } /** @@ -1556,10 +1538,7 @@ export class AppBridge extends ProtocolWithEvents< * @see {@link sendToolInput `sendToolInput`} for sending tool arguments before results */ sendToolResult(params: McpUiToolResultNotification["params"]) { - return this.notification({ - method: "ui/notifications/tool-result" as const, - params, - }); + return this.notification("ui/notifications/tool-result", params); } /** @@ -1593,10 +1572,7 @@ export class AppBridge extends ProtocolWithEvents< * @see {@link sendToolInput `sendToolInput`} for sending tool arguments */ sendToolCancelled(params: McpUiToolCancelledNotification["params"]) { - return this.notification({ - method: "ui/notifications/tool-cancelled" as const, - params, - }); + return this.notification("ui/notifications/tool-cancelled", params); } /** @@ -1617,10 +1593,7 @@ export class AppBridge extends ProtocolWithEvents< sendSandboxResourceReady( params: McpUiSandboxResourceReadyNotification["params"], ) { - return this.notification({ - method: "ui/notifications/sandbox-resource-ready" as const, - params, - }); + return this.notification("ui/notifications/sandbox-resource-ready", params); } /** @@ -1651,12 +1624,7 @@ export class AppBridge extends ProtocolWithEvents< params: McpUiResourceTeardownRequest["params"], options?: RequestOptions, ) { - return this.request( - { - method: "ui/resource-teardown" as const, - params, - }, - McpUiResourceTeardownResultSchema, + return this.request("ui/resource-teardown", params, McpUiResourceTeardownResultSchema, options, ); } @@ -1674,9 +1642,7 @@ export class AppBridge extends ProtocolWithEvents< * @returns Promise resolving to the tool call result */ callTool(params: CallToolRequest["params"], options?: RequestOptions) { - return this.request( - { method: "tools/call", params }, - CallToolResultSchema, + return this.request("tools/call", params, CallToolResultSchema, options, ); } @@ -1691,9 +1657,7 @@ export class AppBridge extends ProtocolWithEvents< * @returns Promise resolving to the list of tools */ listTools(params: ListToolsRequest["params"], options?: RequestOptions) { - return this.request( - { method: "tools/list", params }, - ListToolsResultSchema, + return this.request("tools/list", params, ListToolsResultSchema, options, ); } diff --git a/src/app.examples.ts b/src/app.examples.ts index a0a582076..0c6f0bc98 100644 --- a/src/app.examples.ts +++ b/src/app.examples.ts @@ -7,11 +7,11 @@ * @module */ -import type { Tool } from "@modelcontextprotocol/sdk/types.js"; +import type { Tool } from "@modelcontextprotocol/client"; import type { McpServer, ToolCallback, -} from "@modelcontextprotocol/sdk/server/mcp.js"; +} from "@modelcontextprotocol/server"; import { App, PostMessageTransport, diff --git a/src/app.ts b/src/app.ts index 4f429e829..c99ffd682 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,7 @@ import { type RequestOptions, ProtocolOptions, -} from "@modelcontextprotocol/sdk/shared/protocol.js"; +} from "@modelcontextprotocol/client"; import { CallToolRequest, @@ -21,7 +21,7 @@ import { ReadResourceRequest, ReadResourceResult, ReadResourceResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { AppNotification, AppRequest, AppResult } from "./types"; import { ProtocolWithEvents } from "./events"; export { ProtocolWithEvents }; @@ -59,7 +59,7 @@ import { McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeResultSchema, } from "./types"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { Transport } from "@modelcontextprotocol/client"; export { PostMessageTransport } from "./message-transport"; export * from "./types"; @@ -857,9 +857,7 @@ export class App extends ProtocolWithEvents< `Did you mean: callServerTool({ name: "${params}", arguments: { ... } })?`, ); } - return await this.request( - { method: "tools/call", params }, - CallToolResultSchema, + return await this.request("tools/call", params, CallToolResultSchema, { // Hosts may interpose long-running or user-interactive steps before the // tool result arrives. Opting in here lets a host heartbeat keep the @@ -915,9 +913,7 @@ export class App extends ProtocolWithEvents< params: ReadResourceRequest["params"], options?: RequestOptions, ): Promise { - return await this.request( - { method: "resources/read", params }, - ReadResourceResultSchema, + return await this.request("resources/read", params, ReadResourceResultSchema, options, ); } @@ -962,9 +958,7 @@ export class App extends ProtocolWithEvents< params?: ListResourcesRequest["params"], options?: RequestOptions, ): Promise { - return await this.request( - { method: "resources/list", params }, - ListResourcesResultSchema, + return await this.request("resources/list", params, ListResourcesResultSchema, options, ); } @@ -1050,10 +1044,7 @@ export class App extends ProtocolWithEvents< * @returns Promise that resolves when the log notification is sent */ sendLog(params: LoggingMessageNotification["params"]) { - return this.notification({ - method: "notifications/message", - params, - }); + return this.notification("notifications/message", params); } /** @@ -1277,10 +1268,7 @@ export class App extends ProtocolWithEvents< * @see {@link onteardown `onteardown`} for the graceful termination handler */ requestTeardown(params: McpUiRequestTeardownNotification["params"] = {}) { - return this.notification({ - method: "ui/notifications/request-teardown", - params, - }); + return this.notification("ui/notifications/request-teardown", params); } /** @@ -1344,10 +1332,7 @@ export class App extends ProtocolWithEvents< * @see {@link McpUiSizeChangedNotification `McpUiSizeChangedNotification`} for notification structure */ sendSizeChanged(params: McpUiSizeChangedNotification["params"]) { - return this.notification({ - method: "ui/notifications/size-changed", - params, - }); + return this.notification("ui/notifications/size-changed", params); } /** @@ -1499,9 +1484,7 @@ export class App extends ProtocolWithEvents< this._hostInfo = result.hostInfo; this._hostContext = result.hostContext; - await this.notification({ - method: "ui/notifications/initialized", - }); + await this.notification("ui/notifications/initialized", undefined); if (this.options?.autoResize) { this.setupSizeChangedNotifications(); diff --git a/src/events.ts b/src/events.ts index 4d17ca4f2..60d24d948 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,9 +1,9 @@ -import { Protocol } from "@modelcontextprotocol/sdk/shared/protocol.js"; +import { Protocol } from "@modelcontextprotocol/client"; import { Request, Notification, Result, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { ZodLiteral, ZodObject } from "zod/v4"; type MethodSchema = ZodObject<{ method: ZodLiteral }>; diff --git a/src/generated/schema.ts b/src/generated/schema.ts index aed1f9651..71e425592 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -10,7 +10,7 @@ import { RequestIdSchema, ResourceLinkSchema, ToolSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; /** * @description Color theme preference for the host environment. diff --git a/src/message-transport.ts b/src/message-transport.ts index 9d195435a..52ab0c664 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -2,11 +2,11 @@ import { JSONRPCMessage, JSONRPCMessageSchema, MessageExtraInfo, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { Transport, TransportSendOptions, -} from "@modelcontextprotocol/sdk/shared/transport.js"; +} from "@modelcontextprotocol/client"; import { TOOL_INPUT_PARTIAL_METHOD } from "./spec.types"; /** diff --git a/src/react/useApp.tsx b/src/react/useApp.tsx index d6dcfbb79..044a787e1 100644 --- a/src/react/useApp.tsx +++ b/src/react/useApp.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Implementation } from "@modelcontextprotocol/sdk/types.js"; +import { Implementation } from "@modelcontextprotocol/client"; import { Client } from "@modelcontextprotocol/sdk/client"; import { App, McpUiAppCapabilities, PostMessageTransport } from "../app"; export * from "../app"; diff --git a/src/server/index.examples.ts b/src/server/index.examples.ts index d583ae85c..6a2929388 100644 --- a/src/server/index.examples.ts +++ b/src/server/index.examples.ts @@ -13,7 +13,7 @@ import type { McpServer, ToolCallback, ReadResourceCallback, -} from "@modelcontextprotocol/sdk/server/mcp.js"; +} from "@modelcontextprotocol/server"; import { z } from "zod"; import { registerAppTool, diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 1853cc05a..5de341f9e 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -7,7 +7,7 @@ import { getUiCapability, EXTENSION_ID, } from "./index"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { McpServer } from "@modelcontextprotocol/server"; describe("registerAppTool", () => { it("should pass through config to server.registerTool", () => { diff --git a/src/server/index.ts b/src/server/index.ts index 87209ef39..7d16f49ad 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -32,45 +32,42 @@ */ import { - RESOURCE_URI_META_KEY, - RESOURCE_MIME_TYPE, + McpUiClientCapabilities, McpUiResourceCsp, McpUiResourceMeta, McpUiToolMeta, - McpUiClientCapabilities, + RESOURCE_MIME_TYPE, + RESOURCE_URI_META_KEY, } from "../app.js"; import type { - BaseToolCallback, + ClientCapabilities, McpServer, + ReadResourceCallback, + ReadResourceResult, + RegisteredResource, RegisteredTool, ResourceMetadata, - ToolCallback, - ReadResourceCallback as _ReadResourceCallback, - RegisteredResource, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - AnySchema, - ZodRawShapeCompat, -} from "@modelcontextprotocol/sdk/server/zod-compat.js"; -import type { - ClientCapabilities, - ReadResourceResult, + StandardSchemaWithJSON, ToolAnnotations, -} from "@modelcontextprotocol/sdk/types.js"; + ToolCallback, +} from "@modelcontextprotocol/server"; // Re-exports for convenience -export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE }; +export { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY }; export type { ResourceMetadata, ToolCallback }; /** * Base tool configuration matching the standard MCP server tool options. * Extended by {@link McpUiAppToolConfig `McpUiAppToolConfig`} to add UI metadata requirements. */ -export interface ToolConfig { +export interface ToolConfig< + Input extends StandardSchemaWithJSON | undefined = undefined, + Output extends StandardSchemaWithJSON | undefined = undefined, +> { title?: string; description?: string; - inputSchema?: ZodRawShapeCompat | AnySchema; - outputSchema?: ZodRawShapeCompat | AnySchema; + inputSchema?: Input; + outputSchema?: Output; annotations?: ToolAnnotations; _meta?: Record; } @@ -85,370 +82,136 @@ export interface ToolConfig { * * @see {@link registerAppTool `registerAppTool`} for the recommended way to register app tools */ -export interface McpUiAppToolConfig extends ToolConfig { +export type McpUiAppToolConfig< + Input extends StandardSchemaWithJSON | undefined = undefined, + Output extends StandardSchemaWithJSON | undefined = undefined, +> = ToolConfig & { _meta: { [key: string]: unknown; } & ( - | { - ui: McpUiToolMeta; - } + | { ui: McpUiToolMeta } | { /** * URI of the UI resource to display for this tool. - * This is converted to `_meta["ui/resourceUri"]`. + * Converted to `_meta["ui/resourceUri"]` for wire compat. * * @example "ui://weather/view.html" - * * @deprecated Use `_meta.ui.resourceUri` instead. */ - [RESOURCE_URI_META_KEY]?: string; + [RESOURCE_URI_META_KEY]: string; } ); -} +}; -/** - * MCP App Resource configuration for {@link registerAppResource `registerAppResource`}. - * - * Extends the base MCP SDK `ResourceMetadata` with optional UI metadata - * for configuring security policies and rendering preferences. - * - * The `_meta.ui` field here is included in the `resources/list` response and serves as - * a static default for hosts to review at connection time. When the `resources/read` - * content item also includes `_meta.ui`, the content-item value takes precedence. - * - * @see {@link registerAppResource `registerAppResource`} for usage - */ -export interface McpUiAppResourceConfig extends ResourceMetadata { - /** - * Optional UI metadata for the resource. - * - * This appears on the resource entry in `resources/list` and acts as a listing-level - * fallback. Individual content items returned by `resources/read` may include their - * own `_meta.ui` which takes precedence over this value. - */ - _meta?: { - /** - * UI-specific metadata including CSP configuration and rendering preferences. - */ - ui?: McpUiResourceMeta; - // Allow additional metadata properties for extensibility. - [key: string]: unknown; - }; +function normalizeAppToolMeta( + meta: McpUiAppToolConfig["_meta"], +): Record { + const out: Record = { ...meta }; + const ui = { ...((meta as { ui?: McpUiToolMeta }).ui ?? {}) } as McpUiToolMeta; + const flat = out[RESOURCE_URI_META_KEY]; + // Sync both directions so hosts checking either format see the resource URI. + if (ui.resourceUri && !(RESOURCE_URI_META_KEY in out)) { + out[RESOURCE_URI_META_KEY] = ui.resourceUri; + } else if (typeof flat === "string" && !ui.resourceUri) { + ui.resourceUri = flat; + } + out.ui = ui; + return out; } /** - * Register an app tool with the MCP server. - * - * This is a convenience wrapper around `server.registerTool` that normalizes - * UI metadata: if `_meta.ui.resourceUri` is set, the legacy `_meta["ui/resourceUri"]` - * key is also populated (and vice versa) for compatibility with older hosts. - * - * @param server - The MCP server instance - * @param name - Tool name/identifier - * @param config - Tool configuration with `_meta` field containing UI metadata - * @param cb - Tool handler function - * - * @example Basic usage - * ```ts source="./index.examples.ts#registerAppTool_basicUsage" - * registerAppTool( - * server, - * "get-weather", - * { - * title: "Get Weather", - * description: "Get current weather for a location", - * inputSchema: { location: z.string() }, - * _meta: { - * ui: { resourceUri: "ui://weather/view.html" }, - * }, - * }, - * async (args) => { - * const weather = await fetchWeather(args.location); - * return { content: [{ type: "text", text: JSON.stringify(weather) }] }; - * }, - * ); - * ``` - * - * @example Tool visible to model but not callable by UI - * ```ts source="./index.examples.ts#registerAppTool_modelOnlyVisibility" - * registerAppTool( - * server, - * "show-cart", - * { - * description: "Display the user's shopping cart", - * _meta: { - * ui: { - * resourceUri: "ui://shop/cart.html", - * visibility: ["model"], - * }, - * }, - * }, - * async () => { - * const cart = await getCart(); - * return { content: [{ type: "text", text: JSON.stringify(cart) }] }; - * }, - * ); - * ``` - * - * @example Tool hidden from model, only callable by UI - * ```ts source="./index.examples.ts#registerAppTool_appOnlyVisibility" - * registerAppTool( - * server, - * "update-quantity", - * { - * description: "Update item quantity in cart", - * inputSchema: { itemId: z.string(), quantity: z.number() }, - * _meta: { - * ui: { - * resourceUri: "ui://shop/cart.html", - * visibility: ["app"], - * }, - * }, - * }, - * async ({ itemId, quantity }) => { - * const cart = await updateCartItem(itemId, quantity); - * return { content: [{ type: "text", text: JSON.stringify(cart) }] }; - * }, - * ); - * ``` + * Register a tool whose result is rendered as an interactive App UI. * - * @see {@link registerAppResource `registerAppResource`} to register the HTML resource referenced by the tool + * Thin wrapper over `McpServer.registerTool` that normalizes the + * `_meta.ui` block and ensures both nested and flat resource-URI keys are + * present for maximum host compatibility. */ export function registerAppTool< - OutputArgs extends ZodRawShapeCompat | AnySchema, - InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined, + Input extends StandardSchemaWithJSON | undefined = undefined, + Output extends StandardSchemaWithJSON | undefined = undefined, >( - server: Pick, + server: McpServer, name: string, - config: McpUiAppToolConfig & { - inputSchema?: InputArgs; - outputSchema?: OutputArgs; - }, - cb: ToolCallback, + config: McpUiAppToolConfig, + callback: ToolCallback, ): RegisteredTool { - // Normalize metadata for backward compatibility: - // - If _meta.ui.resourceUri is set, also set the legacy flat key - // - If the legacy flat key is set, also set _meta.ui.resourceUri - const meta = config._meta; - const uiMeta = meta.ui as McpUiToolMeta | undefined; - const legacyUri = meta[RESOURCE_URI_META_KEY] as string | undefined; - - let normalizedMeta = meta; - if (uiMeta?.resourceUri && !legacyUri) { - // New format -> also set legacy key - normalizedMeta = { ...meta, [RESOURCE_URI_META_KEY]: uiMeta.resourceUri }; - } else if (legacyUri && !uiMeta?.resourceUri) { - // Legacy format -> also set new format - normalizedMeta = { ...meta, ui: { ...uiMeta, resourceUri: legacyUri } }; - } - - return server.registerTool(name, { ...config, _meta: normalizedMeta }, cb); + return server.registerTool( + name, + { + ...config, + _meta: normalizeAppToolMeta(config._meta), + } as Parameters[1], + callback as Parameters[2], + ); } -export type McpUiReadResourceResult = ReadResourceResult & { +/** + * Metadata for an App UI resource. Adds the optional `_meta.ui` CSP/permissions + * block on top of {@link ResourceMetadata `ResourceMetadata`}. + */ +export type McpUiAppResourceMetadata = ResourceMetadata & { _meta?: { - ui?: McpUiResourceMeta; [key: string]: unknown; + ui?: McpUiResourceMeta; }; }; -export type McpUiReadResourceCallback = ( - uri: URL, - extra: Parameters<_ReadResourceCallback>[1], -) => McpUiReadResourceResult | Promise; -export type ReadResourceCallback = McpUiReadResourceCallback; /** - * Register an app resource with the MCP server. - * - * This is a convenience wrapper around `server.registerResource` that: - * - Defaults the MIME type to {@link RESOURCE_MIME_TYPE `RESOURCE_MIME_TYPE`} (`"text/html;profile=mcp-app"`) - * - Provides a cleaner API matching the SDK's callback signature + * Register an App UI resource (the `ui://` HTML the host renders in an iframe). * - * @param server - The MCP server instance - * @param name - Human-readable resource name - * @param uri - Resource URI (should match the `_meta.ui` field in tool config) - * @param config - Resource configuration - * @param readCallback - Callback that returns the resource contents - * - * @example Basic usage - * ```ts source="./index.examples.ts#registerAppResource_basicUsage" - * registerAppResource( - * server, - * "Weather View", - * "ui://weather/view.html", - * { - * description: "Interactive weather display", - * }, - * async () => ({ - * contents: [ - * { - * uri: "ui://weather/view.html", - * mimeType: RESOURCE_MIME_TYPE, - * text: await fs.readFile("dist/view.html", "utf-8"), - * }, - * ], - * }), - * ); - * ``` - * - * @example With CSP configuration for network access - * ```ts source="./index.examples.ts#registerAppResource_withCsp" - * registerAppResource( - * server, - * "Music Player", - * "ui://music/player.html", - * { - * description: "Audio player with external soundfonts", - * }, - * async () => ({ - * contents: [ - * { - * uri: "ui://music/player.html", - * mimeType: RESOURCE_MIME_TYPE, - * text: musicPlayerHtml, - * _meta: { - * ui: { - * csp: { - * resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images - * connectDomains: ["https://api.example.com"], // For fetch/WebSocket - * }, - * }, - * }, - * }, - * ], - * }), - * ); - * ``` - * - * @example With stable origin for external API CORS allowlists - * ```ts source="./index.examples.ts#registerAppResource_withDomain" - * // Computes a stable origin from an MCP server URL for hosting in Claude. - * function computeAppDomainForClaude(mcpServerUrl: string): string { - * const hash = crypto - * .createHash("sha256") - * .update(mcpServerUrl) - * .digest("hex") - * .slice(0, 32); - * return `${hash}.claudemcpcontent.com`; - * } - * - * const APP_DOMAIN = computeAppDomainForClaude("https://example.com/mcp"); - * - * registerAppResource( - * server, - * "Company Dashboard", - * "ui://dashboard/view.html", - * { - * description: "Internal dashboard with company data", - * }, - * async () => ({ - * contents: [ - * { - * uri: "ui://dashboard/view.html", - * mimeType: RESOURCE_MIME_TYPE, - * text: dashboardHtml, - * _meta: { - * ui: { - * // CSP: tell browser the app is allowed to make requests - * csp: { - * connectDomains: ["https://api.example.com"], - * }, - * // CORS: give app a stable origin for the API server to allowlist - * // - * // (Public APIs that use `Access-Control-Allow-Origin: *` or API - * // key auth don't need this.) - * domain: APP_DOMAIN, - * }, - * }, - * }, - * ], - * }), - * ); - * ``` - * - * @see {@link McpUiResourceMeta `McpUiResourceMeta`} for `_meta.ui` configuration options - * @see {@link McpUiResourceCsp `McpUiResourceCsp`} for CSP domain allowlist configuration - * @see {@link registerAppTool `registerAppTool`} to register tools that reference this resource + * Thin wrapper over `McpServer.registerResource` that defaults `mimeType` to + * {@link RESOURCE_MIME_TYPE `RESOURCE_MIME_TYPE`} and ensures resource contents + * carry that MIME type. */ export function registerAppResource( - server: Pick, + server: McpServer, name: string, uri: string, - config: McpUiAppResourceConfig, - readCallback: McpUiReadResourceCallback, + metadata: McpUiAppResourceMetadata, + readCallback: ReadResourceCallback, ): RegisteredResource { + const wrappedCallback: ReadResourceCallback = async (u, ctx) => { + const result = await readCallback(u, ctx); + return { + ...result, + contents: result.contents.map((c) => ({ + mimeType: RESOURCE_MIME_TYPE, + ...c, + })), + } as ReadResourceResult; + }; return server.registerResource( name, uri, - { - // Default MIME type for MCP App UI resources (can still be overridden by config below) - mimeType: RESOURCE_MIME_TYPE, - ...config, - }, - readCallback, + { mimeType: RESOURCE_MIME_TYPE, ...metadata }, + wrappedCallback, ); } /** - * Extension identifier for MCP Apps capability negotiation. - * - * Used as the key in `extensions` to advertise MCP Apps support. + * Type guard: returns true if `caps.extensions` declares MCP Apps support. */ +export function clientSupportsMcpApps( + caps: ClientCapabilities | undefined, +): caps is ClientCapabilities & { + extensions: { "io.modelcontextprotocol/ui": McpUiClientCapabilities }; +} { + return !!caps?.extensions?.["io.modelcontextprotocol/ui"]; +} + +export type { McpUiResourceCsp, McpUiResourceMeta, McpUiToolMeta }; + +/** SEP-2133 extension identifier for MCP Apps. Same value as MCP_APPS_EXTENSION_ID. */ export const EXTENSION_ID = "io.modelcontextprotocol/ui"; /** - * Get MCP Apps capability settings from client capabilities. - * - * This helper retrieves the capability object from the `extensions` field - * where MCP Apps advertises its support. - * - * Note: The `clientCapabilities` parameter extends the SDK's `ClientCapabilities` - * type with an `extensions` field (pending SEP-1724). Once `extensions` is added - * to the SDK, this can use `ClientCapabilities` directly. - * - * @param clientCapabilities - The client capabilities from the initialize response - * @returns The MCP Apps capability settings, or `undefined` if not supported - * - * @example Check for MCP Apps support in server initialization - * ```ts source="./index.examples.ts#getUiCapability_checkSupport" - * server.server.oninitialized = () => { - * const clientCapabilities = server.server.getClientCapabilities(); - * const uiCap = getUiCapability(clientCapabilities); - * - * if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) { - * // App-enhanced tool - * registerAppTool( - * server, - * "weather", - * { - * description: "Get weather information with interactive dashboard", - * _meta: { ui: { resourceUri: "ui://weather/dashboard" } }, - * }, - * weatherHandler, - * ); - * } else { - * // Text-only fallback - * server.registerTool( - * "weather", - * { - * description: "Get weather information", - * }, - * textWeatherHandler, - * ); - * } - * }; - * ``` + * Returns the MCP Apps capability blob from ClientCapabilities.extensions, or + * undefined if the client does not declare MCP Apps support. */ export function getUiCapability( - clientCapabilities: - | (ClientCapabilities & { extensions?: Record }) - | null - | undefined, + clientCapabilities: ClientCapabilities | undefined | null, ): McpUiClientCapabilities | undefined { - if (!clientCapabilities) { - return undefined; - } - - return clientCapabilities.extensions?.[EXTENSION_ID] as + return clientCapabilities?.extensions?.[EXTENSION_ID] as | McpUiClientCapabilities | undefined; } diff --git a/src/spec.types.ts b/src/spec.types.ts index 9f83fa972..22f6da675 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -18,7 +18,7 @@ import type { RequestId, ResourceLink, Tool, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; /** * Current protocol version supported by this SDK. diff --git a/src/types.ts b/src/types.ts index 24bffecee..b44237ad1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -156,7 +156,7 @@ import { ReadResourceResult, ResourceListChangedNotification, ToolListChangedNotification, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; /** * All request types in the MCP Apps protocol. diff --git a/tsconfig.json b/tsconfig.json index 08ff1103f..ab1dc405e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,5 @@ "resolveJsonModule": true }, "include": ["src/**/*.ts", "src/**/*.tsx", "docs/**/*.ts", "docs/**/*.tsx"], - "exclude": ["node_modules", "dist", "examples/**/*.ts"] + "exclude": ["src/**/*.examples.ts","src/**/*.examples.tsx","docs/**/*","node_modules", "dist", "examples/**/*.ts"] } From ab3c9c31c08d48fd6282d417d8f74db6346b3797 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Tue, 14 Apr 2026 14:38:08 +0000 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20port=20to=20v2=20concrete-Protocol?= =?UTF-8?q?=20(#1891)=20=E2=80=94=20events=20shim=20+=20sdk-schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - events.ts: ProtocolWithEvents extends Protocol (concrete); v1-compat envelope-schema overload extracts method+params from .shape and forwards to v2's 3-arg form. Typed via EnvelopeRequestHandler/ParamsOf. Event dispatcher writes _notificationHandlers directly to preserve the v1 sync-dispatch contract (v2's await parseSchema introduces a microtask). - sdk-schemas.ts (new): envelope/result stubs for SDK *Schema constants via specTypeSchema(); typed Env so ParamsOf infers concretely. - app.ts/app-bridge.ts: redirect *Schema imports to ./sdk-schemas; delete no-op assert*Capability overrides; convert this.request({method, params}, Schema) → this.request('method', params, Schema); autowire block uses client.callTool/listResources/etc + 2-arg setNotificationHandler. - generate-schemas.ts: emit z.custom(isSpecType('T', v)) for SDK external types instead of importing dropped *Schema constants. - message-transport.ts: JSONRPCMessageSchema → parseJSONRPCMessage. - server/index.test.ts: Pick → as McpServer. tsc 0, build 0, npm test 276/2/0. app-bridge.test.ts: 88/88 pass. --- package-lock.json | 6 +- scripts/generate-schemas.ts | 18 +- src/app-bridge.test.ts | 20 +- src/app-bridge.ts | 131 +-- src/app.ts | 102 +-- src/events.ts | 188 +++- src/generated/schema.json | 1680 +---------------------------------- src/generated/schema.ts | 41 +- src/message-transport.ts | 26 +- src/sdk-schemas.ts | 100 +++ src/server/index.test.ts | 16 +- 11 files changed, 399 insertions(+), 1929 deletions(-) create mode 100644 src/sdk-schemas.ts diff --git a/package-lock.json b/package-lock.json index e81a3aa69..46623ba81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2550,7 +2550,7 @@ "node_modules/@modelcontextprotocol/client": { "version": "2.0.0-alpha.2", "resolved": "file:../../../../../tmp/modelcontextprotocol-client-2.0.0-alpha.2.tgz", - "integrity": "sha512-ry39WpcAUsX7lwpIID9t4M8Tu0dTcmizLf7Av4f3QpDESayNr87RK/ZVzMu4cOkHY4Y/cG9AVYvJpN4bywOgVw==", + "integrity": "sha512-Wju3r1/k/2PFKsRUFpfO88dOs8Y/3dg34yvX3lvb2nNGFXLZcaC2Ttv9i0y6LSnTgPSpKqO2LkG1fROfQmVt2g==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.5", @@ -2627,7 +2627,7 @@ "node_modules/@modelcontextprotocol/node": { "version": "2.0.0-alpha.2", "resolved": "file:../../../../../tmp/modelcontextprotocol-node-2.0.0-alpha.2.tgz", - "integrity": "sha512-+fuoCMM5zpku7qJQNxyeLW9DNVHvaQivkFAqyVw5A410QiHPGtqoh6eDt/nwvnEBw2m21dyWxm22iNVc5CavbA==", + "integrity": "sha512-AF7NNQKJrc/lgbLHQAHvI4yWv+gifiyd+xrcsx8rCXjbno57blFbTTGdF+CDwGWkji71EbEaFVGP+cTxCrZ4DQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9" @@ -2687,7 +2687,7 @@ "node_modules/@modelcontextprotocol/server": { "version": "2.0.0-alpha.2", "resolved": "file:../../../../../tmp/modelcontextprotocol-server-2.0.0-alpha.2.tgz", - "integrity": "sha512-65RJVH41c9Ft48xEQzssGCtmIwgzsvymuO5b7L6GV+g2yymZJFAVn9uETuesVV1CBgxGyEFg1JLWJJkws75Oyw==", + "integrity": "sha512-GFZTSqJimDxVE8Qfc9m+FgxMfbigEL7nmRHp0azyvf/4+FbW78oHtArG1cUdEhnGI1X5Iw5icwcJajq/U3dmWg==", "license": "MIT", "dependencies": { "zod": "^4.0" diff --git a/scripts/generate-schemas.ts b/scripts/generate-schemas.ts index 109f35247..a0545806b 100644 --- a/scripts/generate-schemas.ts +++ b/scripts/generate-schemas.ts @@ -187,20 +187,24 @@ function postProcess(content: string): string { // 1. Rewrite to zod/v4 and add MCP SDK schema imports. // zod/v4 aligns with the SDK's own zod import — composing v3 and v4 // schema instances throws at parse time. See header comment for details. - const mcpImports = EXTERNAL_TYPE_SCHEMAS.join(",\n "); + const typeImports = EXTERNAL_TYPE_SCHEMAS.map((s) => + s.replace(/Schema$/, ""), + ).join(",\n "); content = content.replace( 'import { z } from "zod";', `import { z } from "zod/v4"; -import { - ${mcpImports}, -} from "@modelcontextprotocol/sdk/types.js";`, +import { isSpecType } from "@modelcontextprotocol/client"; +import type { + ${typeImports}, +} from "@modelcontextprotocol/client";`, ); - // 2. Remove z.any() placeholders for external types (now imported from MCP SDK) + // 2. Replace z.any() placeholders for external types with isSpecType-backed z.custom() for (const schema of EXTERNAL_TYPE_SCHEMAS) { + const typeName = schema.replace(/Schema$/, ""); content = content.replace( - new RegExp(`(?:export )?const ${schema} = z\\.any\\(\\);\\n?`, "g"), - "", + new RegExp(`((?:export )?const ${schema}) = z\\.any\\(\\);`, "g"), + `$1 = z.custom<${typeName}>((v) => isSpecType("${typeName}", v));`, ); } diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 76dc424a2..af6562b70 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -11,7 +11,7 @@ import { ReadResourceResultSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, -} from "@modelcontextprotocol/client"; +} from "./sdk-schemas"; import { App } from "./app"; import { @@ -681,7 +681,8 @@ describe("App <-> AppBridge integration", () => { // Bridge can send ping via the protocol's request method const result = await bridge.request( - { method: "ping", params: {} }, + "ping", + {}, EmptyResultSchema, ); @@ -795,7 +796,8 @@ describe("App <-> AppBridge integration", () => { // App sends resources/list request via the protocol's request method const result = await app.request( - { method: "resources/list", params: requestParams }, + "resources/list", + requestParams, ListResourcesResultSchema, ); @@ -839,7 +841,8 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); const result = await app.request( - { method: "resources/read", params: requestParams }, + "resources/read", + requestParams, ReadResourceResultSchema, ); @@ -890,7 +893,8 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); const result = await app.request( - { method: "resources/templates/list", params: requestParams }, + "resources/templates/list", + requestParams, ListResourceTemplatesResultSchema, ); @@ -913,7 +917,8 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); const result = await app.request( - { method: "prompts/list", params: requestParams }, + "prompts/list", + requestParams, ListPromptsResultSchema, ); @@ -1370,13 +1375,11 @@ describe("isToolVisibilityAppOnly", () => { testHostCapabilities, ); bridge2.setRequestHandler( - // @ts-expect-error — exercising throw path with raw schema { shape: { method: { value: "test/method" } } }, () => ({}), ); expect(() => { bridge2.setRequestHandler( - // @ts-expect-error — exercising throw path with raw schema { shape: { method: { value: "test/method" } } }, () => ({}), ); @@ -1388,7 +1391,6 @@ describe("isToolVisibilityAppOnly", () => { app2.addEventListener("toolinput", () => {}); expect(() => { app2.setNotificationHandler( - // @ts-expect-error — exercising throw path with raw schema { shape: { method: { value: "ui/notifications/tool-input" } }, }, diff --git a/src/app-bridge.ts b/src/app-bridge.ts index caf81a0d8..509e61ae0 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -2,42 +2,44 @@ import { Client } from "@modelcontextprotocol/client"; import { Transport } from "@modelcontextprotocol/client"; import { CallToolRequest, - CallToolRequestSchema, CallToolResult, - CallToolResultSchema, EmptyResult, Implementation, ListPromptsRequest, - ListPromptsRequestSchema, ListPromptsResult, - ListPromptsResultSchema, ListResourcesRequest, - ListResourcesRequestSchema, ListResourcesResult, - ListResourcesResultSchema, ListResourceTemplatesRequest, - ListResourceTemplatesRequestSchema, ListResourceTemplatesResult, - ListResourceTemplatesResultSchema, ListToolsRequest, - ListToolsRequestSchema, - ListToolsResultSchema, LoggingMessageNotification, - LoggingMessageNotificationSchema, PingRequest, - PingRequestSchema, PromptListChangedNotification, - PromptListChangedNotificationSchema, ReadResourceRequest, - ReadResourceRequestSchema, ReadResourceResult, - ReadResourceResultSchema, ResourceListChangedNotification, - ResourceListChangedNotificationSchema, Tool, ToolListChangedNotification, - ToolListChangedNotificationSchema, } from "@modelcontextprotocol/client"; +import { + CallToolRequestSchema, + CallToolResultSchema, + ListPromptsRequestSchema, + ListPromptsResultSchema, + ListResourcesRequestSchema, + ListResourcesResultSchema, + ListResourceTemplatesRequestSchema, + ListResourceTemplatesResultSchema, + ListToolsRequestSchema, + ListToolsResultSchema, + LoggingMessageNotificationSchema, + PingRequestSchema, + PromptListChangedNotificationSchema, + ReadResourceRequestSchema, + ReadResourceResultSchema, + ResourceListChangedNotificationSchema, + ToolListChangedNotificationSchema, +} from "./sdk-schemas"; import { ProtocolOptions, RequestOptions, @@ -360,7 +362,7 @@ export class AppBridge extends ProtocolWithEvents< this._hostContext = options?.hostContext || {}; this.setRequestHandler(McpUiInitializeRequestSchema, (request) => - this._oninitialize(request), + this._oninitialize(request as McpUiInitializeRequest), ); this.setRequestHandler(PingRequestSchema, (request, extra) => { @@ -1312,46 +1314,6 @@ export class AppBridge extends ProtocolWithEvents< return this.notification("notifications/prompts/list_changed", params); } - /** - * Verify that the guest supports the capability required for the given request method. - * @internal - */ - assertCapabilityForMethod(method: AppRequest["method"]): void { - // TODO - } - - /** - * Verify that a request handler is registered and supported for the given method. - * @internal - */ - assertRequestHandlerCapability(method: AppRequest["method"]): void { - // TODO - } - - /** - * Verify that the host supports the capability required for the given notification method. - * @internal - */ - assertNotificationCapability(method: AppNotification["method"]): void { - // TODO - } - - /** - * Verify that task creation is supported for the given request method. - * @internal - */ - protected assertTaskCapability(_method: string): void { - throw new Error("Tasks are not supported in MCP Apps"); - } - - /** - * Verify that task handler is supported for the given method. - * @internal - */ - protected assertTaskHandlerCapability(_method: string): void { - throw new Error("Task handlers are not supported in MCP Apps"); - } - /** * Get the host capabilities passed to the constructor. * @@ -1731,60 +1693,37 @@ export class AppBridge extends ProtocolWithEvents< } if (serverCapabilities.tools) { - this.oncalltool = async (params, extra) => { - return this._client!.request( - { method: "tools/call", params }, - CallToolResultSchema, - { signal: extra.signal }, - ); - }; + this.oncalltool = async (params, extra) => + this._client!.callTool(params, { signal: extra.signal }); if (serverCapabilities.tools.listChanged) { this._client.setNotificationHandler( - ToolListChangedNotificationSchema, + "notifications/tools/list_changed", (n) => this.sendToolListChanged(n.params), ); } } if (serverCapabilities.resources) { - this.onlistresources = async (params, extra) => { - return this._client!.request( - { method: "resources/list", params }, - ListResourcesResultSchema, - { signal: extra.signal }, - ); - }; - this.onlistresourcetemplates = async (params, extra) => { - return this._client!.request( - { method: "resources/templates/list", params }, - ListResourceTemplatesResultSchema, - { signal: extra.signal }, - ); - }; - this.onreadresource = async (params, extra) => { - return this._client!.request( - { method: "resources/read", params }, - ReadResourceResultSchema, - { signal: extra.signal }, - ); - }; + this.onlistresources = async (params, extra) => + this._client!.listResources(params, { signal: extra.signal }); + this.onlistresourcetemplates = async (params, extra) => + this._client!.listResourceTemplates(params, { + signal: extra.signal, + }); + this.onreadresource = async (params, extra) => + this._client!.readResource(params, { signal: extra.signal }); if (serverCapabilities.resources.listChanged) { this._client.setNotificationHandler( - ResourceListChangedNotificationSchema, + "notifications/resources/list_changed", (n) => this.sendResourceListChanged(n.params), ); } } if (serverCapabilities.prompts) { - this.onlistprompts = async (params, extra) => { - return this._client!.request( - { method: "prompts/list", params }, - ListPromptsResultSchema, - { signal: extra.signal }, - ); - }; + this.onlistprompts = async (params, extra) => + this._client!.listPrompts(params, { signal: extra.signal }); if (serverCapabilities.prompts.listChanged) { this._client.setNotificationHandler( - PromptListChangedNotificationSchema, + "notifications/prompts/list_changed", (n) => this.sendPromptListChanged(n.params), ); } diff --git a/src/app.ts b/src/app.ts index c99ffd682..8b39b4672 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,23 +5,25 @@ import { import { CallToolRequest, - CallToolRequestSchema, CallToolResult, - CallToolResultSchema, - EmptyResultSchema, Implementation, ListResourcesRequest, ListResourcesResult, - ListResourcesResultSchema, ListToolsRequest, - ListToolsRequestSchema, ListToolsResult, LoggingMessageNotification, - PingRequestSchema, ReadResourceRequest, ReadResourceResult, - ReadResourceResultSchema, } from "@modelcontextprotocol/client"; +import { + CallToolRequestSchema, + CallToolResultSchema, + EmptyResultSchema, + ListResourcesResultSchema, + ListToolsRequestSchema, + PingRequestSchema, + ReadResourceResultSchema, +} from "./sdk-schemas"; import { AppNotification, AppRequest, AppResult } from "./types"; import { ProtocolWithEvents } from "./events"; export { ProtocolWithEvents }; @@ -36,6 +38,7 @@ import { McpUiHostContextChangedNotificationSchema, McpUiInitializedNotification, McpUiInitializeRequest, + McpUiInitializeResult, McpUiInitializeResultSchema, McpUiMessageRequest, McpUiMessageResultSchema, @@ -758,19 +761,11 @@ export class App extends ProtocolWithEvents< }); } - /** - * Verify that the host supports the capability required for the given request method. - * @internal - */ - assertCapabilityForMethod(method: AppRequest["method"]): void { - // TODO - } - /** * Verify that the app declared the capability required for the given request method. * @internal */ - assertRequestHandlerCapability(method: AppRequest["method"]): void { + protected override assertRequestHandlerCapability(method: string): void { switch (method) { case "tools/call": case "tools/list": @@ -780,38 +775,11 @@ export class App extends ProtocolWithEvents< ); } return; - case "ping": - case "ui/resource-teardown": - return; default: - throw new Error(`No handler for method ${method} registered`); + return; } } - /** - * Verify that the app supports the capability required for the given notification method. - * @internal - */ - assertNotificationCapability(method: AppNotification["method"]): void { - // TODO - } - - /** - * Verify that task creation is supported for the given request method. - * @internal - */ - protected assertTaskCapability(_method: string): void { - throw new Error("Tasks are not supported in MCP Apps"); - } - - /** - * Verify that task handler is supported for the given method. - * @internal - */ - protected assertTaskHandlerCapability(_method: string): void { - throw new Error("Task handlers are not supported in MCP Apps"); - } - /** * Call a tool on the originating MCP server (proxied through the host). * @@ -1015,10 +983,8 @@ export class App extends ProtocolWithEvents< */ sendMessage(params: McpUiMessageRequest["params"], options?: RequestOptions) { return this.request( - { - method: "ui/message", - params, - }, + "ui/message", + params, McpUiMessageResultSchema, options, ); @@ -1105,10 +1071,8 @@ export class App extends ProtocolWithEvents< options?: RequestOptions, ) { return this.request( - { - method: "ui/update-model-context", - params, - }, + "ui/update-model-context", + params, EmptyResultSchema, options, ); @@ -1141,10 +1105,8 @@ export class App extends ProtocolWithEvents< */ openLink(params: McpUiOpenLinkRequest["params"], options?: RequestOptions) { return this.request( - { - method: "ui/open-link", - params, - }, + "ui/open-link", + params, McpUiOpenLinkResultSchema, options, ); @@ -1221,10 +1183,8 @@ export class App extends ProtocolWithEvents< options?: RequestOptions, ) { return this.request( - { - method: "ui/download-file", - params, - }, + "ui/download-file", + params, McpUiDownloadFileResultSchema, options, ); @@ -1302,10 +1262,8 @@ export class App extends ProtocolWithEvents< options?: RequestOptions, ) { return this.request( - { - method: "ui/request-display-mode", - params, - }, + "ui/request-display-mode", + params, McpUiRequestDisplayModeResultSchema, options, ); @@ -1463,18 +1421,16 @@ export class App extends ProtocolWithEvents< await super.connect(transport); try { - const result = await this.request( - { - method: "ui/initialize", - params: { - appCapabilities: this._capabilities, - appInfo: this._appInfo, - protocolVersion: LATEST_PROTOCOL_VERSION, - }, + const result = (await this.request( + "ui/initialize", + { + appCapabilities: this._capabilities, + appInfo: this._appInfo, + protocolVersion: LATEST_PROTOCOL_VERSION, }, McpUiInitializeResultSchema, options, - ); + )) as McpUiInitializeResult; if (result === undefined) { throw new Error(`Server sent invalid initialize result: ${result}`); diff --git a/src/events.ts b/src/events.ts index 60d24d948..cc71dbb4d 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,12 +1,79 @@ -import { Protocol } from "@modelcontextprotocol/client"; import { - Request, - Notification, - Result, + Protocol, + type BaseContext, + type Result, + type StandardSchemaV1, } from "@modelcontextprotocol/client"; -import { ZodLiteral, ZodObject } from "zod/v4"; +import type { Request, Notification } from "@modelcontextprotocol/client"; -type MethodSchema = ZodObject<{ method: ZodLiteral }>; +/** + * v1-compat envelope schema shape: a Zod-like object with + * `.shape.method.value` (the method string) and `.shape.params` (the params + * schema). Real Zod objects from `generated/schema.ts` satisfy this, as do + * the lightweight stubs in `sdk-schemas.ts`. + */ +export interface EnvelopeSchema { + shape: { + method: { value: string }; + params?: StandardSchemaV1 | undefined; + }; +} + +const passthrough: StandardSchemaV1 = { + "~standard": { + version: 1, + vendor: "ext-apps-passthrough", + validate: (v) => ({ value: v }), + }, +}; + +/** + * v1-style handler `extra`. v2's `BaseContext` nests these under `mcpReq`; + * this adapter flattens the few fields ext-apps actually uses. + */ +export type RequestHandlerExtra = { + signal: AbortSignal; + sessionId?: string; + requestId?: string | number; + sendNotification: (notification: Notification) => Promise; + sendRequest: ( + request: Request, + resultSchema: StandardSchemaV1, + options?: unknown, + ) => Promise; +}; + +function toExtra(ctx: BaseContext): RequestHandlerExtra { + return { + signal: ctx.mcpReq.signal, + sessionId: ctx.sessionId, + requestId: ctx.mcpReq.id, + sendNotification: (n) => ctx.mcpReq.notify(n), + sendRequest: (r, _schema, options) => + ctx.mcpReq.send(r as never, options as never) as never, + }; +} + +type ParamsOf = + T["shape"]["params"] extends StandardSchemaV1 ? O : unknown; + +/** v1-compat handler signature for {@link ProtocolWithEvents.setRequestHandler}. */ +export type EnvelopeRequestHandler = ( + schema: T, + handler: ( + request: { method: T["shape"]["method"]["value"]; params: ParamsOf }, + extra: RequestHandlerExtra, + ) => Result | Promise, +) => void; + +/** v1-compat handler signature for {@link ProtocolWithEvents.setNotificationHandler}. */ +export type EnvelopeNotificationHandler = ( + schema: T, + handler: (notification: { + method: T["shape"]["method"]["value"]; + params: ParamsOf; + }) => void | Promise, +) => void; /** * Per-event state: a singular `on*` handler (replace semantics) plus a @@ -54,24 +121,39 @@ interface EventSlot { * for the same method has already been registered (through any path), so * accidental overwrites surface as errors instead of silent bugs. * + * ### v1-compat shim + * + * The v1 SDK's `setRequestHandler(envelopeSchema, handler)` signature is + * preserved here as a thin overload that extracts the method string and + * params schema from `envelopeSchema.shape` and forwards to the v2 + * three-argument form. The handler receives `(request, extra)` shaped like + * v1, so existing call sites in `app.ts` / `app-bridge.ts` are unchanged. + * + * @typeParam SendRequestT - Ignored; retained for v1 source compatibility. + * @typeParam SendNotificationT - Ignored; retained for v1 source compatibility. + * @typeParam SendResultT - Ignored; retained for v1 source compatibility. * @typeParam EventMap - Maps event names to the listener's `params` type. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- SendRequestT/SendNotificationT/SendResultT retained for v1 source compatibility export abstract class ProtocolWithEvents< + // eslint-disable-next-line @typescript-eslint/no-unused-vars SendRequestT extends Request, + // eslint-disable-next-line @typescript-eslint/no-unused-vars SendNotificationT extends Notification, + // eslint-disable-next-line @typescript-eslint/no-unused-vars SendResultT extends Result, EventMap extends Record, -> extends Protocol { +> extends Protocol { private _registeredMethods = new Set(); private _eventSlots = new Map(); /** - * Event name → notification schema. Subclasses populate this so that - * the event system can lazily register a dispatcher with the correct + * Event name → notification envelope schema. Subclasses populate this so + * that the event system can lazily register a dispatcher with the correct * schema on first use. */ protected abstract readonly eventSchemas: { - [K in keyof EventMap]: MethodSchema; + [K in keyof EventMap]: EnvelopeSchema; }; /** @@ -110,14 +192,27 @@ export abstract class ProtocolWithEvents< this._registeredMethods.add(method); const s = slot; // stable reference for the closure - super.setNotificationHandler(schema, (n) => { - const params = (n as { params: EventMap[K] }).params; - this.onEventDispatch(event, params); + // Register directly on the handler map rather than via the 3-arg + // `super.setNotificationHandler(method, paramsSchema, handler)` overload. + // The v2 SDK's 3-arg path does `await parseSchema(...)` before invoking + // the handler, which introduces a microtask hop and breaks the v1 + // contract that `await bridge.sendX(...)` resolves *after* the receiving + // `onX` handler has run synchronously over `InMemoryTransport`. + ( + this as unknown as { + _notificationHandlers: Map< + string, + (n: { params?: unknown }) => unknown + >; + } + )._notificationHandlers.set(method, (n) => { + const p = n.params as EventMap[K]; + this.onEventDispatch(event, p); // 1. Singular on* handler - s.onHandler?.(params); + s.onHandler?.(p); // 2. addEventListener listeners — snapshot to tolerate removal during // dispatch (e.g., a listener that calls removeEventListener on itself) - for (const l of [...s.listeners]) l(params); + for (const l of [...s.listeners]) l(p); }); } return slot; @@ -193,7 +288,7 @@ export abstract class ProtocolWithEvents< if (idx !== -1) slot.listeners.splice(idx, 1); } - // ── Handler registration with double-set protection ───────────────── + // ── v1-compat envelope-schema overload ────────────────────────────── // The two overrides below are arrow-function class fields rather than // prototype methods so that Protocol's constructor — which registers its @@ -207,15 +302,22 @@ export abstract class ProtocolWithEvents< * has already been registered — use the `on*` setter (replace semantics) * or `addEventListener` (multi-listener) for notification events. * + * Accepts the v1 envelope-schema form `(schema, handler)` where `schema` + * has `.shape.method.value` and `.shape.params`; the handler receives + * `({method, params}, extra)` like v1. + * * @throws {Error} if a handler for this method is already registered. */ - override setRequestHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setRequestHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setRequestHandler"); - super.setRequestHandler(schema, handler); + // @ts-expect-error — narrowing the inherited overload set to the v1-compat envelope form + override setRequestHandler: EnvelopeRequestHandler = (schema, handler) => { + const method = schema.shape.method.value; + this._assertMethodNotRegistered(method, "setRequestHandler"); + super.setRequestHandler( + method, + schema.shape.params ?? passthrough, + (params, ctx) => + handler({ method, params } as never, toExtra(ctx)) as Result, + ); }; /** @@ -225,13 +327,18 @@ export abstract class ProtocolWithEvents< * * @throws {Error} if a handler for this method is already registered. */ - override setNotificationHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setNotificationHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setNotificationHandler"); - super.setNotificationHandler(schema, handler); + // @ts-expect-error — narrowing the inherited overload set to the v1-compat envelope form + override setNotificationHandler: EnvelopeNotificationHandler = ( + schema, + handler, + ) => { + const method = schema.shape.method.value; + this._assertMethodNotRegistered(method, "setNotificationHandler"); + super.setNotificationHandler( + method, + schema.shape.params ?? passthrough, + (params) => handler({ method, params } as never), + ); }; /** @@ -255,18 +362,21 @@ export abstract class ProtocolWithEvents< * Replace a request handler, bypassing double-set protection. Used by * `on*` request-handler setters that need replace semantics. */ - protected replaceRequestHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setRequestHandler"] = (schema, handler) => { - const method = (schema as MethodSchema).shape.method.value; + protected replaceRequestHandler: EnvelopeRequestHandler = ( + schema, + handler, + ) => { + const method = schema.shape.method.value; this._registeredMethods.add(method); - super.setRequestHandler(schema, handler); + super.setRequestHandler( + method, + schema.shape.params ?? passthrough, + (params, ctx) => + handler({ method, params } as never, toExtra(ctx)) as Result, + ); }; - private _assertMethodNotRegistered(schema: unknown, via: string): void { - const method = (schema as MethodSchema).shape.method.value; + private _assertMethodNotRegistered(method: string, via: string): void { if (this._registeredMethods.has(method)) { throw new Error( `Handler for "${method}" already registered (via ${via}). ` + diff --git a/src/generated/schema.json b/src/generated/schema.json index d66e55760..7213b1da6 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -95,182 +95,7 @@ "contents": { "type": "array", "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "resource" - }, - "resource": { - "anyOf": [ - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "text": { - "type": "string" - } - }, - "required": ["uri", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "blob": { - "type": "string" - } - }, - "required": ["uri", "blob"], - "additionalProperties": false - } - ] - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "resource"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "uri": { - "type": "string" - }, - "description": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "size": { - "type": "number" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "properties": {}, - "additionalProperties": {} - }, - "type": { - "type": "string", - "const": "resource_link" - } - }, - "required": ["name", "uri", "type"], - "additionalProperties": false - } - ] + "anyOf": [{}, {}] }, "description": "Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types." } @@ -521,145 +346,9 @@ "type": "object", "properties": { "id": { - "description": "JSON-RPC id of the tools/call request.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - ] + "description": "JSON-RPC id of the tools/call request." }, "tool": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "description": { - "type": "string" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "annotations": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "readOnlyHint": { - "type": "boolean" - }, - "destructiveHint": { - "type": "boolean" - }, - "idempotentHint": { - "type": "boolean" - }, - "openWorldHint": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "execution": { - "type": "object", - "properties": { - "taskSupport": { - "type": "string", - "enum": ["required", "optional", "forbidden"] - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["name", "inputSchema"], - "additionalProperties": false, "description": "Tool definition including name, inputSchema, etc." } }, @@ -1284,145 +973,9 @@ "type": "object", "properties": { "id": { - "description": "JSON-RPC id of the tools/call request.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - ] + "description": "JSON-RPC id of the tools/call request." }, "tool": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "description": { - "type": "string" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "annotations": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "readOnlyHint": { - "type": "boolean" - }, - "destructiveHint": { - "type": "boolean" - }, - "idempotentHint": { - "type": "boolean" - }, - "openWorldHint": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "execution": { - "type": "object", - "properties": { - "taskSupport": { - "type": "string", - "enum": ["required", "optional", "forbidden"] - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["name", "inputSchema"], - "additionalProperties": false, "description": "Tool definition including name, inputSchema, etc." } }, @@ -2472,52 +2025,6 @@ "type": "object", "properties": { "appInfo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "version": { - "type": "string" - }, - "websiteUrl": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": ["name", "version"], - "additionalProperties": false, "description": "App identification (name and version)." }, "appCapabilities": { @@ -2586,52 +2093,6 @@ "description": "Negotiated protocol version string (e.g., \"2025-11-21\")." }, "hostInfo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "version": { - "type": "string" - }, - "websiteUrl": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": ["name", "version"], - "additionalProperties": false, "description": "Host application identification and version." }, "hostCapabilities": { @@ -2854,145 +2315,9 @@ "type": "object", "properties": { "id": { - "description": "JSON-RPC id of the tools/call request.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - ] + "description": "JSON-RPC id of the tools/call request." }, "tool": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "description": { - "type": "string" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["type"], - "additionalProperties": {} - }, - "annotations": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "readOnlyHint": { - "type": "boolean" - }, - "destructiveHint": { - "type": "boolean" - }, - "idempotentHint": { - "type": "boolean" - }, - "openWorldHint": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "execution": { - "type": "object", - "properties": { - "taskSupport": { - "type": "string", - "enum": ["required", "optional", "forbidden"] - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["name", "inputSchema"], - "additionalProperties": false, "description": "Tool definition including name, inputSchema, etc." } }, @@ -3648,322 +2973,7 @@ }, "content": { "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "text" - }, - "text": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "image" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "audio" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "uri": { - "type": "string" - }, - "description": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "size": { - "type": "number" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "properties": {}, - "additionalProperties": {} - }, - "type": { - "type": "string", - "const": "resource_link" - } - }, - "required": ["name", "uri", "type"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "resource" - }, - "resource": { - "anyOf": [ - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "text": { - "type": "string" - } - }, - "required": ["uri", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "blob": { - "type": "string" - } - }, - "required": ["uri", "blob"], - "additionalProperties": false - } - ] - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "resource"], - "additionalProperties": false - } - ] - }, + "items": {}, "description": "Message content blocks (text, image, etc.)." } }, @@ -5287,369 +4297,6 @@ "const": "ui/notifications/tool-result" }, "params": { - "type": "object", - "properties": { - "_meta": { - "type": "object", - "properties": { - "progressToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - ] - }, - "io.modelcontextprotocol/related-task": { - "type": "object", - "properties": { - "taskId": { - "type": "string" - } - }, - "required": ["taskId"], - "additionalProperties": false - } - }, - "additionalProperties": {} - }, - "content": { - "default": [], - "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "text" - }, - "text": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "image" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "audio" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "uri": { - "type": "string" - }, - "description": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "size": { - "type": "number" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "properties": {}, - "additionalProperties": {} - }, - "type": { - "type": "string", - "const": "resource_link" - } - }, - "required": ["name", "uri", "type"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "resource" - }, - "resource": { - "anyOf": [ - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "text": { - "type": "string" - } - }, - "required": ["uri", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "blob": { - "type": "string" - } - }, - "required": ["uri", "blob"], - "additionalProperties": false - } - ] - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "resource"], - "additionalProperties": false - } - ] - } - }, - "structuredContent": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "isError": { - "type": "boolean" - } - }, - "required": ["content"], - "additionalProperties": {}, "description": "Standard MCP tool execution result." } }, @@ -5684,322 +4331,7 @@ "content": { "description": "Context content blocks (text, image, etc.).", "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "text" - }, - "text": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "image" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "audio" - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "data", "mimeType"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "uri": { - "type": "string" - }, - "description": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "size": { - "type": "number" - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "properties": {}, - "additionalProperties": {} - }, - "type": { - "type": "string", - "const": "resource_link" - } - }, - "required": ["name", "uri", "type"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "resource" - }, - "resource": { - "anyOf": [ - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "text": { - "type": "string" - } - }, - "required": ["uri", "text"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "uri": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "blob": { - "type": "string" - } - }, - "required": ["uri", "blob"], - "additionalProperties": false - } - ] - }, - "annotations": { - "type": "object", - "properties": { - "audience": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "assistant"] - } - }, - "priority": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lastModified": { - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" - } - }, - "additionalProperties": false - }, - "_meta": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["type", "resource"], - "additionalProperties": false - } - ] - } + "items": {} }, "structuredContent": { "description": "Structured content for machine-readable context data.", diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 71e425592..9d3e5eb40 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -2,14 +2,15 @@ // Post-processed for Zod v3/v4 compatibility and MCP SDK integration // Run: npm run generate:schemas import { z } from "zod/v4"; -import { - ContentBlockSchema, - CallToolResultSchema, - EmbeddedResourceSchema, - ImplementationSchema, - RequestIdSchema, - ResourceLinkSchema, - ToolSchema, +import { isSpecType } from "@modelcontextprotocol/client"; +import type { + ContentBlock, + CallToolResult, + EmbeddedResource, + Implementation, + RequestId, + ResourceLink, + Tool, } from "@modelcontextprotocol/client"; /** @@ -734,6 +735,30 @@ export const McpUiClientCapabilitiesSchema = z.object({ ), }); +const EmbeddedResourceSchema = z.custom((v) => + isSpecType("EmbeddedResource", v), +); + +const ResourceLinkSchema = z.custom((v) => + isSpecType("ResourceLink", v), +); + +const ContentBlockSchema = z.custom((v) => + isSpecType("ContentBlock", v), +); + +const CallToolResultSchema = z.custom((v) => + isSpecType("CallToolResult", v), +); + +const RequestIdSchema = z.custom((v) => isSpecType("RequestId", v)); + +const ToolSchema = z.custom((v) => isSpecType("Tool", v)); + +const ImplementationSchema = z.custom((v) => + isSpecType("Implementation", v), +); + /** * @description Request to download a file through the host. * diff --git a/src/message-transport.ts b/src/message-transport.ts index 52ab0c664..fade621c6 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -1,6 +1,6 @@ import { JSONRPCMessage, - JSONRPCMessageSchema, + parseJSONRPCMessage, MessageExtraInfo, } from "@modelcontextprotocol/client"; import { @@ -79,25 +79,27 @@ export class PostMessageTransport implements Transport { console.debug("Ignoring message from unknown source", event); return; } - const parsed = JSONRPCMessageSchema.safeParse(event.data); - if (parsed.success) { - console.debug("Parsed message", parsed.data); - this.onmessage?.(parsed.data); + let parsed: JSONRPCMessage | undefined; + let parseError: unknown; + try { + parsed = parseJSONRPCMessage(event.data); + } catch (e) { + parseError = e; + } + if (parsed) { + console.debug("Parsed message", parsed); + this.onmessage?.(parsed); } else if (event.data?.jsonrpc !== "2.0") { // Not a JSON-RPC message at all (e.g. internal frames injected by // the host environment). Ignore silently so the transport stays alive. - console.debug( - "Ignoring non-JSON-RPC message", - parsed.error.message, - event, - ); + console.debug("Ignoring non-JSON-RPC message", parseError, event); } else { // Has jsonrpc: "2.0" but is otherwise malformed — surface as a real // protocol error. - console.error("Failed to parse message", parsed.error.message, event); + console.error("Failed to parse message", parseError, event); this.onerror?.( new Error( - "Invalid JSON-RPC message received: " + parsed.error.message, + "Invalid JSON-RPC message received: " + String(parseError), ), ); } diff --git a/src/sdk-schemas.ts b/src/sdk-schemas.ts new file mode 100644 index 000000000..dd069bea1 --- /dev/null +++ b/src/sdk-schemas.ts @@ -0,0 +1,100 @@ +/** + * v1-compat envelope/result schema stubs for SDK-defined types. + * + * The v2 SDK no longer exports Zod schema constants (`CallToolRequestSchema`, + * `ListResourcesResultSchema`, …). This module reconstructs the few that + * `app.ts` / `app-bridge.ts` reference, using `specTypeSchema()` for runtime + * validation while presenting the v1 `.shape.method.value` / `.shape.params` + * surface that {@link ./events.ProtocolWithEvents} expects. + * + * Result schemas are plain `StandardSchemaV1` (passed to `request()` directly). + */ +import { + specTypeSchema, + type CallToolRequestParams, + type ListToolsRequest, + type LoggingMessageNotificationParams, + type PaginatedRequestParams, + type PingRequest, + type ReadResourceRequestParams, + type StandardSchemaV1, +} from "@modelcontextprotocol/client"; + +type Env = { + shape: { + method: { value: M }; + params: StandardSchemaV1

; + }; +}; +const env = ( + method: M, + params: StandardSchemaV1

, +): Env => ({ shape: { method: { value: method }, params } }); + +const passthrough = (): StandardSchemaV1 => ({ + "~standard": { + version: 1, + vendor: "ext-apps-passthrough", + validate: (v) => ({ value: v as T }), + }, +}); + +// Request envelope stubs (passed to setRequestHandler / replaceRequestHandler) +export const PingRequestSchema = env("ping", passthrough()); +export const CallToolRequestSchema = env( + "tools/call", + specTypeSchema("CallToolRequestParams") as StandardSchemaV1, +); +export const ListToolsRequestSchema = env( + "tools/list", + passthrough(), +); +export const ListResourcesRequestSchema = env( + "resources/list", + passthrough(), +); +export const ListResourceTemplatesRequestSchema = env( + "resources/templates/list", + passthrough(), +); +export const ReadResourceRequestSchema = env( + "resources/read", + specTypeSchema( + "ReadResourceRequestParams", + ) as StandardSchemaV1, +); +export const ListPromptsRequestSchema = env( + "prompts/list", + passthrough(), +); + +// Notification envelope stubs (passed to setNotificationHandler / eventSchemas) +export const LoggingMessageNotificationSchema = env( + "notifications/message", + specTypeSchema( + "LoggingMessageNotificationParams", + ) as StandardSchemaV1, +); +export const ToolListChangedNotificationSchema = env( + "notifications/tools/list_changed", + passthrough | undefined>(), +); +export const ResourceListChangedNotificationSchema = env( + "notifications/resources/list_changed", + passthrough | undefined>(), +); +export const PromptListChangedNotificationSchema = env( + "notifications/prompts/list_changed", + passthrough | undefined>(), +); + +// Result schemas (passed to `request()` as the third argument) +export const CallToolResultSchema = specTypeSchema("CallToolResult"); +export const ListToolsResultSchema = specTypeSchema("ListToolsResult"); +export const ListResourcesResultSchema = specTypeSchema("ListResourcesResult"); +export const ListResourceTemplatesResultSchema = specTypeSchema( + "ListResourceTemplatesResult", +); +export const ReadResourceResultSchema = specTypeSchema("ReadResourceResult"); +export const ListPromptsResultSchema = specTypeSchema("ListPromptsResult"); +export const EmptyResultSchema = passthrough>(); diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 5de341f9e..47fd9a3b0 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -31,7 +31,7 @@ describe("registerAppTool", () => { }); registerAppTool( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "my-tool", { title: "My Tool", @@ -72,7 +72,7 @@ describe("registerAppTool", () => { }; registerAppTool( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "my-tool", { _meta: { @@ -107,7 +107,7 @@ describe("registerAppTool", () => { }; registerAppTool( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "my-tool", { _meta: { @@ -142,7 +142,7 @@ describe("registerAppTool", () => { }; registerAppTool( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "my-tool", { _meta: { @@ -176,7 +176,7 @@ describe("registerAppTool", () => { }; registerAppTool( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "my-tool", { _meta: { @@ -225,7 +225,7 @@ describe("registerAppResource", () => { }); registerAppResource( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "My Resource", "ui://test/view.html", { @@ -255,7 +255,7 @@ describe("registerAppResource", () => { }; registerAppResource( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "My Resource", "ui://test/view.html", { @@ -306,7 +306,7 @@ describe("registerAppResource", () => { const callback = mock(async () => expectedResult); registerAppResource( - mockServer as unknown as Pick, + mockServer as unknown as McpServer, "My Resource", "ui://test/view.html", { _meta: { ui: {} } }, From fee6d58ecbc2d9a35058d99d39e25b2aa67a4b23 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Wed, 15 Apr 2026 11:15:21 +0000 Subject: [PATCH 3/3] chore: consolidate imports, remove unused (code-quality bot) --- src/app-bridge.test.ts | 14 ++++++-------- src/app-bridge.ts | 33 +++++++++++---------------------- src/app.ts | 37 +++++++++++++++++++------------------ 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index af6562b70..2e8d72750 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -1,7 +1,9 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import type { Client } from "@modelcontextprotocol/client"; -import type { ServerCapabilities } from "@modelcontextprotocol/client"; +import { + InMemoryTransport, + type Client, + type ServerCapabilities, +} from "@modelcontextprotocol/client"; import { EmptyResultSchema, ListPromptsResultSchema, @@ -680,11 +682,7 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); // Bridge can send ping via the protocol's request method - const result = await bridge.request( - "ping", - {}, - EmptyResultSchema, - ); + const result = await bridge.request("ping", {}, EmptyResultSchema); expect(result).toEqual({}); }); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 509e61ae0..b0a109776 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -1,8 +1,7 @@ -import { Client } from "@modelcontextprotocol/client"; -import { Transport } from "@modelcontextprotocol/client"; import { CallToolRequest, CallToolResult, + Client, EmptyResult, Implementation, ListPromptsRequest, @@ -15,35 +14,26 @@ import { LoggingMessageNotification, PingRequest, PromptListChangedNotification, + ProtocolOptions, ReadResourceRequest, ReadResourceResult, + RequestOptions, ResourceListChangedNotification, Tool, ToolListChangedNotification, + Transport, } from "@modelcontextprotocol/client"; import { CallToolRequestSchema, CallToolResultSchema, ListPromptsRequestSchema, - ListPromptsResultSchema, ListResourcesRequestSchema, - ListResourcesResultSchema, ListResourceTemplatesRequestSchema, - ListResourceTemplatesResultSchema, - ListToolsRequestSchema, ListToolsResultSchema, LoggingMessageNotificationSchema, PingRequestSchema, - PromptListChangedNotificationSchema, ReadResourceRequestSchema, - ReadResourceResultSchema, - ResourceListChangedNotificationSchema, - ToolListChangedNotificationSchema, } from "./sdk-schemas"; -import { - ProtocolOptions, - RequestOptions, -} from "@modelcontextprotocol/client"; import { ProtocolWithEvents } from "./events"; import { @@ -374,7 +364,7 @@ export class AppBridge extends ProtocolWithEvents< // Hosts can override this by setting bridge.onrequestdisplaymode = ... this.replaceRequestHandler( McpUiRequestDisplayModeRequestSchema, - (request) => { + (_request) => { const currentMode = this._hostContext.displayMode ?? "inline"; return { mode: currentMode }; }, @@ -1586,7 +1576,10 @@ export class AppBridge extends ProtocolWithEvents< params: McpUiResourceTeardownRequest["params"], options?: RequestOptions, ) { - return this.request("ui/resource-teardown", params, McpUiResourceTeardownResultSchema, + return this.request( + "ui/resource-teardown", + params, + McpUiResourceTeardownResultSchema, options, ); } @@ -1604,9 +1597,7 @@ export class AppBridge extends ProtocolWithEvents< * @returns Promise resolving to the tool call result */ callTool(params: CallToolRequest["params"], options?: RequestOptions) { - return this.request("tools/call", params, CallToolResultSchema, - options, - ); + return this.request("tools/call", params, CallToolResultSchema, options); } /** @@ -1619,9 +1610,7 @@ export class AppBridge extends ProtocolWithEvents< * @returns Promise resolving to the list of tools */ listTools(params: ListToolsRequest["params"], options?: RequestOptions) { - return this.request("tools/list", params, ListToolsResultSchema, - options, - ); + return this.request("tools/list", params, ListToolsResultSchema, options); } /** diff --git a/src/app.ts b/src/app.ts index 8b39b4672..d8ade8891 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,3 @@ -import { - type RequestOptions, - ProtocolOptions, -} from "@modelcontextprotocol/client"; - import { CallToolRequest, CallToolResult, @@ -12,8 +7,11 @@ import { ListToolsRequest, ListToolsResult, LoggingMessageNotification, + ProtocolOptions, ReadResourceRequest, ReadResourceResult, + type RequestOptions, + Transport, } from "@modelcontextprotocol/client"; import { CallToolRequestSchema, @@ -62,7 +60,6 @@ import { McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeResultSchema, } from "./types"; -import { Transport } from "@modelcontextprotocol/client"; export { PostMessageTransport } from "./message-transport"; export * from "./types"; @@ -825,16 +822,14 @@ export class App extends ProtocolWithEvents< `Did you mean: callServerTool({ name: "${params}", arguments: { ... } })?`, ); } - return await this.request("tools/call", params, CallToolResultSchema, - { - // Hosts may interpose long-running or user-interactive steps before the - // tool result arrives. Opting in here lets a host heartbeat keep the - // request alive past the default timeout; callers can still override. - onprogress: () => {}, - resetTimeoutOnProgress: true, - ...options, - }, - ); + return await this.request("tools/call", params, CallToolResultSchema, { + // Hosts may interpose long-running or user-interactive steps before the + // tool result arrives. Opting in here lets a host heartbeat keep the + // request alive past the default timeout; callers can still override. + onprogress: () => {}, + resetTimeoutOnProgress: true, + ...options, + }); } /** @@ -881,7 +876,10 @@ export class App extends ProtocolWithEvents< params: ReadResourceRequest["params"], options?: RequestOptions, ): Promise { - return await this.request("resources/read", params, ReadResourceResultSchema, + return await this.request( + "resources/read", + params, + ReadResourceResultSchema, options, ); } @@ -926,7 +924,10 @@ export class App extends ProtocolWithEvents< params?: ListResourcesRequest["params"], options?: RequestOptions, ): Promise { - return await this.request("resources/list", params, ListResourcesResultSchema, + return await this.request( + "resources/list", + params, + ListResourcesResultSchema, options, ); }