Skip to content

Commit b0dc592

Browse files
ochafikclaude
andcommitted
feat: add server helpers and optional connect() transport
## Server Helpers (`src/server/`) Add convenience functions for registering MCP App tools and resources: - `registerAppTool(server, name, config, handler)` - registers a tool with `_meta[RESOURCE_URI_META_KEY]` for the UI resource URI - `registerAppResource(server, name, uri, config, callback)` - registers a resource with default MIME type `text/html;profile=mcp-app` Types use SDK directly: `Pick<McpServer, "registerTool">`, `ToolCallback`, etc. ## Optional Transport in App.connect() `App.connect()` now accepts an optional transport parameter: - Defaults to `PostMessageTransport(window.parent)` when not provided - Simplifies app initialization: `await app.connect()` just works ## Example Updates Updated basic-server-react and basic-server-vanillajs examples to use the new helpers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 31e05d3 commit b0dc592

File tree

8 files changed

+391
-51
lines changed

8 files changed

+391
-51
lines changed

examples/basic-server-react/server.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
3+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "node:fs/promises";
55
import path from "node:path";
6-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import {
7+
registerAppTool,
8+
registerAppResource,
9+
RESOURCE_MIME_TYPE,
10+
RESOURCE_URI_META_KEY,
11+
} from "../../src/server";
712
import { startServer } from "../shared/server-utils.js";
813

914
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -20,14 +25,14 @@ function createServer(): McpServer {
2025
});
2126

2227
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
23-
// resource (the UI it renders). The `_meta` field on the tool links to the
24-
// resource URI, telling hosts which UI to display when the tool executes.
25-
server.registerTool(
28+
// resource (the UI it renders). Use registerAppTool and registerAppResource
29+
// for a cleaner API that automatically sets up the required metadata.
30+
registerAppTool(
31+
server,
2632
"get-time",
2733
{
2834
title: "Get Time",
2935
description: "Returns the current server time as an ISO 8601 string.",
30-
inputSchema: {},
3136
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
3237
},
3338
async (): Promise<CallToolResult> => {
@@ -38,19 +43,18 @@ function createServer(): McpServer {
3843
},
3944
);
4045

41-
server.registerResource(
46+
registerAppResource(
47+
server,
48+
"Get Time App",
4249
RESOURCE_URI,
43-
RESOURCE_URI,
44-
{ mimeType: RESOURCE_MIME_TYPE },
45-
async (): Promise<ReadResourceResult> => {
46-
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
47-
50+
{ description: "Interactive time display", mimeType: RESOURCE_MIME_TYPE, _meta: { ui: {} } },
51+
async () => {
52+
const html = await fs.readFile(
53+
path.join(DIST_DIR, "mcp-app.html"),
54+
"utf-8",
55+
);
4856
return {
49-
contents: [
50-
// Per the MCP App specification, "text/html;profile=mcp-app" signals
51-
// to the Host that this resource is indeed for an MCP App UI.
52-
{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html },
53-
],
57+
contents: [{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html }],
5458
};
5559
},
5660
);

examples/basic-server-vanillajs/server.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
3+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "node:fs/promises";
55
import path from "node:path";
6-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import {
7+
registerAppTool,
8+
registerAppResource,
9+
RESOURCE_MIME_TYPE,
10+
RESOURCE_URI_META_KEY,
11+
} from "../../src/server";
712
import { startServer } from "../shared/server-utils.js";
813

914
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -20,14 +25,14 @@ function createServer(): McpServer {
2025
});
2126

2227
// MCP Apps require two-part registration: a tool (what the LLM calls) and a
23-
// resource (the UI it renders). The `_meta` field on the tool links to the
24-
// resource URI, telling hosts which UI to display when the tool executes.
25-
server.registerTool(
28+
// resource (the UI it renders). Use registerAppTool and registerAppResource
29+
// for a cleaner API that automatically sets up the required metadata.
30+
registerAppTool(
31+
server,
2632
"get-time",
2733
{
2834
title: "Get Time",
2935
description: "Returns the current server time as an ISO 8601 string.",
30-
inputSchema: {},
3136
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
3237
},
3338
async (): Promise<CallToolResult> => {
@@ -38,19 +43,18 @@ function createServer(): McpServer {
3843
},
3944
);
4045

41-
server.registerResource(
46+
registerAppResource(
47+
server,
48+
"Get Time App",
4249
RESOURCE_URI,
43-
RESOURCE_URI,
44-
{ mimeType: RESOURCE_MIME_TYPE },
45-
async (): Promise<ReadResourceResult> => {
46-
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
47-
50+
{ description: "Interactive time display", mimeType: RESOURCE_MIME_TYPE, _meta: { ui: {} } },
51+
async () => {
52+
const html = await fs.readFile(
53+
path.join(DIST_DIR, "mcp-app.html"),
54+
"utf-8",
55+
);
4856
return {
49-
contents: [
50-
// Per the MCP App specification, "text/html;profile=mcp-app" signals
51-
// to the Host that this resource is indeed for an MCP App UI.
52-
{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html },
53-
],
57+
contents: [{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html }],
5458
};
5559
},
5660
);

examples/basic-server-vanillajs/src/mcp-app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @file App that demonstrates a few features using MCP Apps SDK with vanilla JS.
33
*/
4-
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
4+
import { App } from "@modelcontextprotocol/ext-apps";
55
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
66
import "./global.css";
77
import "./mcp-app.css";
@@ -97,5 +97,5 @@ openLinkBtn.addEventListener("click", async () => {
9797
});
9898

9999

100-
// Connect to host
101-
app.connect(new PostMessageTransport(window.parent));
100+
// Connect to host (uses PostMessageTransport by default)
101+
app.connect();

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
"types": "./dist/src/app-bridge.d.ts",
2424
"default": "./dist/src/app-bridge.js"
2525
},
26+
"./server": {
27+
"types": "./dist/src/server/index.d.ts",
28+
"default": "./dist/src/server/index.js"
29+
},
2630
"./schema.json": "./dist/src/generated/schema.json"
2731
},
2832
"files": [

src/app.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
PingRequestSchema,
1717
} from "@modelcontextprotocol/sdk/types.js";
1818
import { AppNotification, AppRequest, AppResult } from "./types";
19+
import { PostMessageTransport } from "./message-transport";
1920
import {
2021
LATEST_PROTOCOL_VERSION,
2122
McpUiAppCapabilities,
@@ -991,7 +992,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
991992
* Establish connection with the host and perform initialization handshake.
992993
*
993994
* This method performs the following steps:
994-
* 1. Connects the transport layer
995+
* 1. Connects the transport layer (defaults to PostMessageTransport if not provided)
995996
* 2. Sends `ui/initialize` request with app info and capabilities
996997
* 3. Receives host capabilities and context in response
997998
* 4. Sends `ui/notifications/initialized` notification
@@ -1000,32 +1001,33 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
10001001
* If initialization fails, the connection is automatically closed and an error
10011002
* is thrown.
10021003
*
1003-
* @param transport - Transport layer (typically PostMessageTransport)
1004+
* @param transport - Optional transport layer. Defaults to `PostMessageTransport(window.parent)`.
10041005
* @param options - Request options for the initialize request
10051006
*
10061007
* @throws {Error} If initialization fails or connection is lost
10071008
*
1008-
* @example Connect with PostMessageTransport
1009+
* @example Connect with default transport (recommended)
10091010
* ```typescript
10101011
* const app = new App(
10111012
* { name: "MyApp", version: "1.0.0" },
10121013
* {}
10131014
* );
10141015
*
1015-
* try {
1016-
* await app.connect(new PostMessageTransport(window.parent));
1017-
* console.log("Connected successfully!");
1018-
* } catch (error) {
1019-
* console.error("Failed to connect:", error);
1020-
* }
1016+
* // Uses PostMessageTransport(window.parent) by default
1017+
* await app.connect();
1018+
* ```
1019+
*
1020+
* @example Connect with explicit transport
1021+
* ```typescript
1022+
* await app.connect(new PostMessageTransport(window.parent));
10211023
* ```
10221024
*
10231025
* @see {@link McpUiInitializeRequest} for the initialization request structure
10241026
* @see {@link McpUiInitializedNotification} for the initialized notification
10251027
* @see {@link PostMessageTransport} for the typical transport implementation
10261028
*/
10271029
override async connect(
1028-
transport: Transport,
1030+
transport: Transport = new PostMessageTransport(window.parent),
10291031
options?: RequestOptions,
10301032
): Promise<void> {
10311033
await super.connect(transport);

src/react/useApp.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useEffect, useState } from "react";
22
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
3-
import { Client } from "@modelcontextprotocol/sdk/client";
4-
import { App, McpUiAppCapabilities, PostMessageTransport } from "../app";
3+
import { App, McpUiAppCapabilities } from "../app";
54
export * from "../app";
65

76
/**
@@ -117,13 +116,13 @@ export function useApp({
117116

118117
async function connect() {
119118
try {
120-
const transport = new PostMessageTransport(window.parent);
121119
const app = new App(appInfo, capabilities);
122120

123121
// Register handlers BEFORE connecting
124122
onAppCreated?.(app);
125123

126-
await app.connect(transport);
124+
// connect() defaults to PostMessageTransport(window.parent)
125+
await app.connect();
127126

128127
if (mounted) {
129128
setApp(app);

0 commit comments

Comments
 (0)