feat: add server helpers, make connect() to default to parent post transport (#165)

* feat: add server helpers and optional connect() transport

Add `src/server/` with convenience functions for registering MCP App tools
and resources:

- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`

The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update src/server/index.ts

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* fix: refine server helper types

- Make RESOURCE_URI_META_KEY required in McpUiAppToolConfig (tools need a UI resource)
- Make _meta optional in McpUiAppResourceConfig (not all resources need custom metadata)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify RESOURCE_URI_META_KEY re-export

* refactor: consolidate export statements

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
This commit is contained in:
Olivier Chafik 2025-12-16 21:25:26 +00:00 • committed by GitHub
commit bea0ae27f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

147
src/server/index.ts Normal file
View file

@ -0,0 +1,147 @@
/**
* Server Helpers for MCP Apps.
*
* @module server-helpers
*/
import {
RESOURCE_URI_META_KEY,
RESOURCE_MIME_TYPE,
McpUiResourceMeta,
} from "../app.js";
import type {
McpServer,
ResourceMetadata,
ToolCallback,
ReadResourceCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
import type { ZodRawShape } from "zod";
// Re-exports for convenience
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE };
export type { ResourceMetadata, ToolCallback, ReadResourceCallback };
/**
* Tool configuration (same as McpServer.registerTool).
*/
export interface ToolConfig {
title?: string;
description?: string;
inputSchema?: ZodRawShape;
annotations?: ToolAnnotations;
_meta?: Record<string, unknown>;
}
/**
* MCP App Tool configuration for `registerAppTool`.
*/
export interface McpUiAppToolConfig extends ToolConfig {
_meta: {
// Soon: `ui: McpUiToolMeta;` (https://github.com/modelcontextprotocol/ext-apps/pull/131)
/**
* URI of the UI resource to display for this tool.
* This is converted to `_meta["ui/resourceUri"]`.
*
* @example "ui://weather/widget.html"
*/
[RESOURCE_URI_META_KEY]: string;
[key: string]: unknown;
};
}
/**
* MCP App Resource configuration for `registerAppResource`.
*/
export interface McpUiAppResourceConfig extends ResourceMetadata {
_meta?: {
ui?: McpUiResourceMeta;
[key: string]: unknown;
};
}
/**
* Register an app tool with the MCP server.
*
* This is a convenience wrapper around `server.registerTool` that will allow more backwards-compatibility.
*
* @param server - The MCP server instance
* @param name - Tool name/identifier
* @param config - Tool configuration with required `ui` field
* @param handler - Tool handler function
*
* @example
* ```typescript
* import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
* import { z } from 'zod';
*
* registerAppTool(server, "get-weather", {
* title: "Get Weather",
* description: "Get current weather for a location",
* inputSchema: { location: z.string() },
* _meta: {
* [RESOURCE_URI_META_KEY]: "ui://weather/widget.html",
* },
* }, async (args) => {
* const weather = await fetchWeather(args.location);
* return { content: [{ type: "text", text: JSON.stringify(weather) }] };
* });
* ```
*/
export function registerAppTool(
server: Pick<McpServer, "registerTool">,
name: string,
config: McpUiAppToolConfig,
handler: ToolCallback<ZodRawShape>,
): void {
server.registerTool(name, config, handler);
}
/**
* Register an app resource with the MCP server.
*
* This is a convenience wrapper around `server.registerResource` that:
* - Defaults the MIME type to "text/html;profile=mcp-app"
* - Provides a cleaner API matching the SDK's callback signature
*
* @param server - The MCP server instance
* @param name - Human-readable resource name
* @param uri - Resource URI (should match the `ui` field in tool config)
* @param config - Resource configuration
* @param readCallback - Callback that returns the resource contents
*
* @example
* ```typescript
* import { registerAppResource } from '@modelcontextprotocol/ext-apps/server';
*
* registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {
* description: "Interactive weather display",
* mimeType: RESOURCE_MIME_TYPE,
* }, async () => ({
* contents: [{
* uri: "ui://weather/widget.html",
* mimeType: RESOURCE_MIME_TYPE,
* text: await fs.readFile("dist/widget.html", "utf-8"),
* }],
* }));
* ```
*/
export function registerAppResource(
server: Pick<McpServer, "registerResource">,
name: string,
uri: string,
config: McpUiAppResourceConfig,
readCallback: ReadResourceCallback,
): void {
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,
);
}