refactor: finish MCP SDK v2 migration

This commit is contained in:
Andrew Khadder 2026-07-14 17:07:30 -07:00
commit f94cb38b0b
No known key found for this signature in database
9 changed files with 684 additions and 540 deletions

View file

@ -7,17 +7,11 @@
* @module
*/
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import {
CallToolResult,
CallToolResultSchema,
import { Client, type Transport } from "@modelcontextprotocol/client";
import type {
CreateMessageRequest,
CreateMessageResult,
ListResourcesResultSchema,
ReadResourceResultSchema,
ListPromptsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
} from "@modelcontextprotocol/server";
import { AppBridge, PostMessageTransport } from "./app-bridge.js";
import type { McpUiDisplayMode } from "./types.js";
@ -223,8 +217,7 @@ function AppBridge_oncalltool_forwardToServer(
bridge.oncalltool = async (params, extra) => {
return mcpClient.request(
{ method: "tools/call", params },
CallToolResultSchema,
{ signal: extra.signal },
{ signal: extra.mcpReq.signal },
);
};
//#endregion AppBridge_oncalltool_forwardToServer
@ -245,7 +238,9 @@ function AppBridge_oncreatesamplingmessage_forwardToLlm(
//#region AppBridge_oncreatesamplingmessage_forwardToLlm
bridge.oncreatesamplingmessage = async (params, extra) => {
// Apply rate limiting, user approval, cost controls here
return await myLlmProvider.complete(params, { signal: extra.signal });
return await myLlmProvider.complete(params, {
signal: extra.mcpReq.signal,
});
};
//#endregion AppBridge_oncreatesamplingmessage_forwardToLlm
}
@ -261,8 +256,7 @@ function AppBridge_onlistresources_returnResources(
bridge.onlistresources = async (params, extra) => {
return mcpClient.request(
{ method: "resources/list", params },
ListResourcesResultSchema,
{ signal: extra.signal },
{ signal: extra.mcpReq.signal },
);
};
//#endregion AppBridge_onlistresources_returnResources
@ -279,8 +273,7 @@ function AppBridge_onreadresource_returnResource(
bridge.onreadresource = async (params, extra) => {
return mcpClient.request(
{ method: "resources/read", params },
ReadResourceResultSchema,
{ signal: extra.signal },
{ signal: extra.mcpReq.signal },
);
};
//#endregion AppBridge_onreadresource_returnResource
@ -297,8 +290,7 @@ function AppBridge_onlistprompts_returnPrompts(
bridge.onlistprompts = async (params, extra) => {
return mcpClient.request(
{ method: "prompts/list", params },
ListPromptsResultSchema,
{ signal: extra.signal },
{ signal: extra.mcpReq.signal },
);
};
//#endregion AppBridge_onlistprompts_returnPrompts
@ -444,10 +436,10 @@ async function AppBridge_sendToolResult_afterExecution(
args: Record<string, unknown>,
) {
//#region AppBridge_sendToolResult_afterExecution
const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema,
);
const result = await mcpClient.request({
method: "tools/call",
params: { name: "get_weather", arguments: args },
});
bridge.sendToolResult(result);
//#endregion AppBridge_sendToolResult_afterExecution
}

View file

@ -261,7 +261,7 @@ export type AppBridgeEventMap = {
/**
* Host-side bridge for communicating with a single View ({@link app!App `App`}).
*
* `AppBridge` extends the MCP SDK's `Protocol` class and acts as a proxy between
* `AppBridge` extends the base MCP SDK's public `Server` class and acts as a proxy between
* the host application and a view running in an iframe. When an MCP client
* is provided to the constructor, it automatically forwards MCP server capabilities
* (tools, resources, prompts) to the view. It also handles the initialization
@ -667,8 +667,7 @@ export class AppBridge extends Server {
* ```typescript
* bridge.onsandboxready = async () => {
* const resource = await mcpClient.request(
* { method: "resources/read", params: { uri: "ui://my-app" } },
* ReadResourceResultSchema
* { method: "resources/read", params: { uri: "ui://my-app" } }
* );
*
* bridge.sendSandboxResourceReady({
@ -1154,14 +1153,13 @@ export class AppBridge extends Server {
* bridge.oncalltool = async (params, extra) => {
* return mcpClient.request(
* { method: "tools/call", params },
* CallToolResultSchema,
* { signal: extra.signal },
* { signal: extra.mcpReq.signal },
* );
* };
* ```
*
* @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type
* @see `CallToolResult` from @modelcontextprotocol/sdk for the result type
* @see `CallToolRequest` from @modelcontextprotocol/server for the request type
* @see `CallToolResult` from @modelcontextprotocol/server for the result type
*/
private _oncalltool?: (
params: CallToolRequest["params"],
@ -1209,12 +1207,14 @@ export class AppBridge extends Server {
* ```ts source="./app-bridge.examples.ts#AppBridge_oncreatesamplingmessage_forwardToLlm"
* bridge.oncreatesamplingmessage = async (params, extra) => {
* // Apply rate limiting, user approval, cost controls here
* return await myLlmProvider.complete(params, { signal: extra.signal });
* return await myLlmProvider.complete(params, {
* signal: extra.mcpReq.signal,
* });
* };
* ```
*
* @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type
* @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types
* @see `CreateMessageRequest` from @modelcontextprotocol/server for the request type
* @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/server for result types
*/
set oncreatesamplingmessage(
callback: (
@ -1244,7 +1244,7 @@ export class AppBridge extends Server {
* });
* ```
*
* @see `ToolListChangedNotification` from @modelcontextprotocol/sdk for the notification type
* @see `ToolListChangedNotification` from @modelcontextprotocol/server for the notification type
*/
sendToolListChanged(params: ToolListChangedNotification["params"] = {}) {
return this.notification({
@ -1270,14 +1270,13 @@ export class AppBridge extends Server {
* bridge.onlistresources = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/list", params },
* ListResourcesResultSchema,
* { signal: extra.signal },
* { signal: extra.mcpReq.signal },
* );
* };
* ```
*
* @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type
* @see `ListResourcesRequest` from @modelcontextprotocol/server for the request type
* @see `ListResourcesResult` from @modelcontextprotocol/server for the result type
*/
private _onlistresources?: (
params: ListResourcesRequest["params"],
@ -1325,14 +1324,13 @@ export class AppBridge extends Server {
* bridge.onlistresourcetemplates = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/templates/list", params },
* ListResourceTemplatesResultSchema,
* { signal: extra.signal }
* { signal: extra.mcpReq.signal }
* );
* };
* ```
*
* @see `ListResourceTemplatesRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListResourceTemplatesResult` from @modelcontextprotocol/sdk for the result type
* @see `ListResourceTemplatesRequest` from @modelcontextprotocol/server for the request type
* @see `ListResourceTemplatesResult` from @modelcontextprotocol/server for the result type
*/
private _onlistresourcetemplates?: (
params: ListResourceTemplatesRequest["params"],
@ -1383,14 +1381,13 @@ export class AppBridge extends Server {
* bridge.onreadresource = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/read", params },
* ReadResourceResultSchema,
* { signal: extra.signal },
* { signal: extra.mcpReq.signal },
* );
* };
* ```
*
* @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type
* @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type
* @see `ReadResourceRequest` from @modelcontextprotocol/server for the request type
* @see `ReadResourceResult` from @modelcontextprotocol/server for the result type
*/
private _onreadresource?: (
params: ReadResourceRequest["params"],
@ -1438,7 +1435,7 @@ export class AppBridge extends Server {
* });
* ```
*
* @see `ResourceListChangedNotification` from @modelcontextprotocol/sdk for the notification type
* @see `ResourceListChangedNotification` from @modelcontextprotocol/server for the notification type
*/
sendResourceListChanged(
params: ResourceListChangedNotification["params"] = {},
@ -1466,14 +1463,13 @@ export class AppBridge extends Server {
* bridge.onlistprompts = async (params, extra) => {
* return mcpClient.request(
* { method: "prompts/list", params },
* ListPromptsResultSchema,
* { signal: extra.signal },
* { signal: extra.mcpReq.signal },
* );
* };
* ```
*
* @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type
* @see `ListPromptsRequest` from @modelcontextprotocol/server for the request type
* @see `ListPromptsResult` from @modelcontextprotocol/server for the result type
*/
private _onlistprompts?: (
params: ListPromptsRequest["params"],
@ -1520,7 +1516,7 @@ export class AppBridge extends Server {
* });
* ```
*
* @see `PromptListChangedNotification` from @modelcontextprotocol/sdk for the notification type
* @see `PromptListChangedNotification` from @modelcontextprotocol/server for the notification type
*/
sendPromptListChanged(params: PromptListChangedNotification["params"] = {}) {
return this.notification({
@ -1722,10 +1718,10 @@ export class AppBridge extends Server {
*
* @example
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution"
* const result = await mcpClient.request(
* { method: "tools/call", params: { name: "get_weather", arguments: args } },
* CallToolResultSchema,
* );
* const result = await mcpClient.request({
* method: "tools/call",
* params: { name: "get_weather", arguments: args },
* });
* bridge.sendToolResult(result);
* ```
*

View file

@ -7,11 +7,8 @@
* @module
*/
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import type {
McpServer,
ToolCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import type { SamplingMessage, Tool } from "@modelcontextprotocol/client";
import type { McpServer, ToolCallback } from "@modelcontextprotocol/server";
import {
App,
PostMessageTransport,
@ -369,7 +366,7 @@ async function App_createSamplingMessage_simple(app: App) {
*/
async function App_createSamplingMessage_withTools(
app: App,
messages: import("@modelcontextprotocol/sdk/types.js").SamplingMessage[],
messages: SamplingMessage[],
) {
//#region App_createSamplingMessage_withTools
if (!app.getHostCapabilities()?.sampling?.tools) return;

View file

@ -174,9 +174,9 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
/**
* Options for configuring {@link App `App`} behavior.
*
* Extends `ProtocolOptions` from the MCP SDK with `App`-specific configuration.
* Extends `ClientOptions` from the base MCP SDK with `App`-specific configuration.
*
* @see `ProtocolOptions` from @modelcontextprotocol/sdk for inherited options
* @see `ClientOptions` from @modelcontextprotocol/client for inherited options
*/
export type AppOptions = Omit<
ClientOptions,
@ -243,7 +243,7 @@ export type AppToolResult<
* validated/parsed input; when `In` is `undefined`, the callback receives only
* `extra`. When `Out` is provided, the return's `structuredContent` is typed.
*
* Mirrors `ToolCallback` from `@modelcontextprotocol/sdk/server/mcp.js` but is
* Mirrors `ToolCallback` from `@modelcontextprotocol/server` but is
* parameterized over {@link StandardSchemaV1} instead of zod, so any
* Standard-Schema-compatible library (Zod, ArkType, Valibot, …) can be used.
*/
@ -261,7 +261,7 @@ export type AppToolCallback<
/**
* Handle returned by {@link App.registerTool}. Mirrors `RegisteredTool` from
* `@modelcontextprotocol/sdk/server/mcp.js` but stores
* `@modelcontextprotocol/server` but stores
* {@link StandardSchemaV1} schemas.
*/
export type RegisteredAppTool = {
@ -302,9 +302,8 @@ export type AppEventMap = {
* Main class for MCP Apps to communicate with their host.
*
* The `App` class provides a framework-agnostic way to build interactive MCP Apps
* that run inside host applications. It extends the MCP SDK's `Protocol` class and
* handles the connection lifecycle, initialization handshake, and bidirectional
* communication with the host.
* that run inside host applications. It extends the base MCP SDK's public
* `Client` class and adds the Apps handshake, lifecycle, and high-level APIs.
*
* ## Architecture
*
@ -321,7 +320,7 @@ export type AppEventMap = {
*
* ## Protocol and event methods
*
* As a subclass of the MCP SDK's `Protocol`, `App` provides:
* As a subclass of the base MCP SDK's `Client`, `App` provides:
* - `setRequestHandler()` - Register handlers for requests from host
* - `setNotificationHandler()` - Register handlers for notifications from host
* - `addEventListener()` - Append a listener for a notification event (multi-listener)
@ -1458,8 +1457,8 @@ export class App extends Client {
* }
* ```
*
* @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type
* @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types
* @see `CreateMessageRequest` from @modelcontextprotocol/client for the request type
* @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/client for result types
*/
async createSamplingMessage(
params: CreateMessageRequest["params"] & { tools?: undefined },

View file

@ -38,9 +38,8 @@ export class EventDispatcher<EventMap extends Record<string, unknown>> {
getHandler<K extends keyof EventMap>(
event: K,
): ((params: EventMap[K]) => void) | undefined {
return (this._eventSlots.get(event) as
| EventSlot<EventMap[K]>
| undefined)?.onHandler;
return (this._eventSlots.get(event) as EventSlot<EventMap[K]> | undefined)
?.onHandler;
}
/** Add a listener. Listeners run in insertion order after the handler. */

View file

@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
import type { JSONRPCMessage } from "@modelcontextprotocol/client";
import { PostMessageTransport } from "./message-transport";

View file

@ -9,17 +9,14 @@
import * as crypto from "node:crypto";
import * as fs from "node:fs/promises";
import type {
McpServer,
ToolCallback,
ReadResourceCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import type { McpServer, ToolCallback } from "@modelcontextprotocol/server";
import { z } from "zod";
import {
registerAppTool,
registerAppResource,
getUiCapability,
RESOURCE_MIME_TYPE,
type ReadResourceCallback,
} from "./index.js";
// Stubs for external functions used in examples
@ -74,7 +71,7 @@ function registerAppTool_basicUsage(server: McpServer) {
{
title: "Get Weather",
description: "Get current weather for a location",
inputSchema: { location: z.string() },
inputSchema: z.object({ location: z.string() }),
_meta: {
ui: { resourceUri: "ui://weather/view.html" },
},
@ -122,7 +119,7 @@ function registerAppTool_appOnlyVisibility(server: McpServer) {
"update-quantity",
{
description: "Update item quantity in cart",
inputSchema: { itemId: z.string(), quantity: z.number() },
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",

View file

@ -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", () => {

View file

@ -150,7 +150,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata {
* {
* title: "Get Weather",
* description: "Get current weather for a location",
* inputSchema: { location: z.string() },
* inputSchema: z.object({ location: z.string() }),
* _meta: {
* ui: { resourceUri: "ui://weather/view.html" },
* },
@ -190,7 +190,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata {
* "update-quantity",
* {
* description: "Update item quantity in cart",
* inputSchema: { itemId: z.string(), quantity: z.number() },
* inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
* _meta: {
* ui: {
* resourceUri: "ui://shop/cart.html",