Migrate to MCP SDK 2.0 with official Protocol and isolated role peers (#720)
App and AppBridge extend the public Protocol from @modelcontextprotocol/client; the iframe wire protocol (ui/initialize only) is unchanged. Peers move to the split SDK 2.0 packages with client and server as optional role peers. Fixups (lockfile, peer ranges, restored handler guards, docs, wire-compat tests) follow in a separate PR. Co-authored-by: Andrew Khadder <andrew@manufact.com> Co-authored-by: Felix Weinberger <fweinberger@anthropic.com> Closes #702
This commit is contained in:
commit
1a713173cc
2 changed files with 2415 additions and 1867 deletions
|
|
@ -23,10 +23,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "^1.7.0",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"@modelcontextprotocol/node": "2.0.0",
|
||||
"@modelcontextprotocol/server": "2.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"jose": "^6.0.0"
|
||||
"jose": "^6.0.0",
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ import {
|
|||
registerAppTool,
|
||||
RESOURCE_MIME_TYPE,
|
||||
} from "@modelcontextprotocol/ext-apps/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { UrlElicitationRequiredError } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
CallToolResult,
|
||||
ReadResourceResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
||||
import {
|
||||
McpServer,
|
||||
UrlElicitationRequiredError,
|
||||
type CallToolResult,
|
||||
type ReadResourceResult,
|
||||
} from "@modelcontextprotocol/server";
|
||||
import cors from "cors";
|
||||
import express, { type Express, type Request, type Response } from "express";
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
|
|
@ -35,6 +35,7 @@ import crypto from "node:crypto";
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { z } from "zod";
|
||||
|
||||
// Works both from source (server.ts) and compiled (dist/server.js). Derived
|
||||
// from import.meta.url rather than import.meta.filename/dirname, which are
|
||||
|
|
@ -621,7 +622,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
title: "Show Auth Button",
|
||||
description:
|
||||
"Public tool: shows a button that triggers the protected get_secret tool.",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
_meta: { ui: { resourceUri: buttonUri } },
|
||||
},
|
||||
async (): Promise<CallToolResult> => ({
|
||||
|
|
@ -654,7 +655,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
title: "Get Secret",
|
||||
description:
|
||||
"Protected tool: returns secret data. Requires authentication.",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
_meta: { ui: { resourceUri: secretUri } },
|
||||
},
|
||||
async (): Promise<CallToolResult> => {
|
||||
|
|
@ -698,7 +699,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
title: "Revoke Auth Token",
|
||||
description:
|
||||
"Protected tool: revokes the caller's entire auth session (access + refresh token).",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async (): Promise<CallToolResult> => {
|
||||
if (!authInfo) {
|
||||
|
|
@ -736,7 +737,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
title: "Elicit URL (session-callback)",
|
||||
description:
|
||||
"URL elicitation via elicitInput. Blocks until the client accepts; sends ElicitCompleteNotification before returning.",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async (_args, extra): Promise<CallToolResult> => {
|
||||
const eid = crypto.randomUUID();
|
||||
|
|
@ -751,7 +752,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
message: "Please open this URL to continue.",
|
||||
elicitationId: eid,
|
||||
},
|
||||
{ relatedRequestId: extra.requestId, timeout: 300_000 },
|
||||
{ relatedRequestId: extra.mcpReq.id, timeout: 300_000 },
|
||||
);
|
||||
// Send completion notification before returning — lands on the same SSE
|
||||
// stream as this tool response. For Streamable HTTP this is the ONLY
|
||||
|
|
@ -783,7 +784,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer {
|
|||
title: "Elicit by -32042 Error",
|
||||
description:
|
||||
"Throws UrlElicitationRequiredError (-32042). Client must open the URL, then retry. On retry, if the last-issued elicitationId was completed, sends ElicitCompleteNotification and succeeds.",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
},
|
||||
async (): Promise<CallToolResult> => {
|
||||
// Retry path: check if the user completed the elicitation we last issued.
|
||||
|
|
@ -1047,7 +1048,7 @@ export function createApp(): Express {
|
|||
}
|
||||
|
||||
const server = createServer(authInfo, req);
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
const transport = new NodeStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
res.on("close", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue