update build

This commit is contained in:
Olivier Chafik 2025-11-21 13:13:46 +00:00
commit a0039ef150
No known key found for this signature in database
8 changed files with 50 additions and 490 deletions

View file

@ -25,45 +25,45 @@ async function buildJs(
}
await Promise.all([
buildJs("src/index.ts", { outdir: "dist/src" }),
buildJs("src/react/useApp.tsx", { outdir: "dist/src/react" }),
buildJs("examples/example-ui-react.tsx", { outdir: "dist/examples" }),
buildJs("examples/example-ui.ts", { outdir: "dist/examples" }),
buildJs("examples/example-server.ts", {
target: "node",
outdir: "dist/examples",
}),
buildJs("src/app.ts"),
buildJs("src/react/index.tsx", { outdir: "dist/react" }),
// buildJs("examples/example-ui-react.tsx", { outdir: "dist/examples" }),
// buildJs("examples/example-ui.ts", { outdir: "dist/examples" }),
// buildJs("examples/example-server.ts", {
// target: "node",
// outdir: "dist/examples",
// }),
]);
async function inlineHtml(htmlFilePath: string, outputFilePath: string) {
let html = await readFile(htmlFilePath, "utf-8");
// Find all src sources, then fetch them, then replace them
const files = html.matchAll(
/<script src="((?:\.\/)?dist\/.*?\.js)"><\/script>/g,
);
const fileContent = Object.fromEntries(
await Promise.all(
Array.from(files, async ([, filename]) => {
const scriptContent = await readFile(filename, "utf-8");
return [filename, scriptContent];
}),
),
);
html = html.replace(
/<script src="((?:\.\/)?dist\/.*?\.js)"><\/script>/g,
(_, filename) => {
let scriptContent = fileContent[filename];
// Escape </script> to prevent premature script tag closing
// This is the standard way to inline scripts safely
scriptContent = scriptContent.replace(/<\/script>/gi, "<\\/script>");
return `<script>${scriptContent}</script>`;
},
);
await writeFile(outputFilePath, html);
}
// async function inlineHtml(htmlFilePath: string, outputFilePath: string) {
// let html = await readFile(htmlFilePath, "utf-8");
// // Find all src sources, then fetch them, then replace them
// const files = html.matchAll(
// /<script src="((?:\.\/)?dist\/.*?\.js)"><\/script>/g,
// );
// const fileContent = Object.fromEntries(
// await Promise.all(
// Array.from(files, async ([, filename]) => {
// const scriptContent = await readFile(filename, "utf-8");
// return [filename, scriptContent];
// }),
// ),
// );
// html = html.replace(
// /<script src="((?:\.\/)?dist\/.*?\.js)"><\/script>/g,
// (_, filename) => {
// let scriptContent = fileContent[filename];
// // Escape </script> to prevent premature script tag closing
// // This is the standard way to inline scripts safely
// scriptContent = scriptContent.replace(/<\/script>/gi, "<\\/script>");
// return `<script>${scriptContent}</script>`;
// },
// );
// await writeFile(outputFilePath, html);
// }
await inlineHtml("examples/example-ui.html", "dist/examples/example-ui.html");
await inlineHtml(
"examples/example-ui-react.html",
"dist/examples/example-ui-react.html",
);
// await inlineHtml("examples/example-ui.html", "dist/examples/example-ui.html");
// await inlineHtml(
// "examples/example-ui-react.html",
// "dist/examples/example-ui-react.html",
// );

View file

@ -1,3 +0,0 @@
export { App } from "./app";
export { PostMessageTransport } from "./message-transport";
export * from "./types";

View file

@ -4,11 +4,16 @@
"version": "1.0.0",
"description": "Hey",
"type": "module",
"main": "./src/index.ts",
"main": "./dist/app.js",
"exports": {
".": "./src/index.ts",
"./types": "./src/types.ts",
"./react/useApp": "./src/react/useApp.tsx"
".": {
"types": "./dist/app.d.ts",
"default": "./dist/app.js"
},
"./react": {
"types": "./dist/react/index.d.ts",
"default": "./dist/react/index.js"
}
},
"scripts": {
"start:example-server0": "tsx examples/example-server.ts",

View file

@ -1,70 +0,0 @@
import { useEffect, useState } from "react";
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
import { Client } from "@modelcontextprotocol/sdk/client";
import { App, McpUiAppCapabilities, PostMessageTransport } from "../app";
export * from "../app";
export interface UseAppOptions {
appInfo: Implementation;
capabilities: McpUiAppCapabilities;
/**
* Called after client is created but before connection.
* Use this to register request/notification handlers via
* client.setRequestHandler() and client.setNotificationHandler().
*/
onAppCreated?: (app: App) => void;
}
export interface AppState {
app: App | null;
isConnected: boolean;
error: Error | null;
}
export function useApp({
appInfo,
capabilities,
onAppCreated,
}: UseAppOptions): AppState {
const [app, setApp] = useState<App | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let mounted = true;
async function connect() {
try {
const transport = new PostMessageTransport(window.parent);
const app = new App(appInfo, capabilities);
// Register handlers BEFORE connecting
onAppCreated?.(app);
await app.connect(transport);
if (mounted) {
setApp(app);
setIsConnected(true);
setError(null);
}
} catch (error) {
if (mounted) {
setApp(null);
setIsConnected(false);
setError(
error instanceof Error ? error : new Error("Failed to connect"),
);
}
}
}
connect();
return () => {
mounted = false;
};
}, []); // Intentionally not including options to avoid reconnection
return { app, isConnected, error };
}

View file

@ -1,20 +0,0 @@
import { useEffect, RefObject } from "react";
import { App } from "../app";
/**
* Custom hook that automatically reports size changes to the parent window.
*
* @param client - MCP UI client for sending size notifications
*/
export function useAutoResize(
app: App | null,
elementRef?: RefObject<HTMLElement | null>,
) {
useEffect(() => {
if (!app) {
return;
}
return app.setupSizeChangeNotifications();
}, [app, elementRef]);
}

View file

@ -1,3 +0,0 @@
export { App } from "./app";
export { PostMessageTransport } from "./message-transport";
export * from "./types";

View file

@ -1,351 +0,0 @@
// import {
// z,
// ZodRawShape,
// ZodLiteral,
// ZodObject,
// ZodString,
// ZodTypeAny,
// ZodType,
// ZodTypeDef,
// ZodOptional,
// } from "zod";
// import { Client } from "@modelcontextprotocol/sdk/client/index.js";
// import {
// // ToolCallback,
// RegisteredTool,
// } from "@modelcontextprotocol/sdk/server/mcp.js";
// import { Server } from "@modelcontextprotocol/sdk/server/index.js";
// import {
// Protocol,
// type RequestOptions,
// type RequestHandlerExtra,
// } from "@modelcontextprotocol/sdk/shared/protocol.js";
// import {
// CallToolRequestSchema,
// CallToolResult,
// CallToolResultSchema,
// CancelledNotificationSchema,
// ClientCapabilities,
// ClientCapabilitiesSchema,
// Request,
// Implementation,
// JSONRPCError,
// JSONRPCMessage,
// JSONRPCMessageSchema,
// JSONRPCNotification,
// JSONRPCResponse,
// ListPromptsRequestSchema,
// ListPromptsResultSchema,
// ListResourcesRequestSchema,
// ListResourcesResultSchema,
// ListResourceTemplatesRequestSchema,
// ListResourceTemplatesResultSchema,
// LoggingMessageNotification,
// LoggingMessageNotificationSchema,
// MessageExtraInfo,
// Notification,
// PromptListChangedNotificationSchema,
// RequestId,
// ResourceListChangedNotificationSchema,
// Result,
// ServerCapabilities,
// ServerCapabilitiesSchema,
// ServerNotification,
// ServerRequest,
// ToolAnnotations,
// ToolListChangedNotificationSchema,
// } from "@modelcontextprotocol/sdk/types.js";
// import {
// Transport,
// TransportSendOptions,
// } from "@modelcontextprotocol/sdk/shared/transport.js";
// import {
// McpUiToolInputNotification,
// McpUiToolInputNotificationSchema,
// McpUiToolResultNotification,
// McpUiToolResultNotificationSchema,
// McpUiHostCapabilities,
// McpUiHostInfo,
// McpUiInitializedNotification,
// McpUiInitializeRequest,
// McpUiInitializeResultSchema,
// McpUiIntentRequest,
// McpUiIntentRequestSchema,
// McpUiIntentResultSchema,
// McpUiRenderDataRequest,
// McpUiRenderDataRequestSchema,
// McpUiMessageRequest,
// McpUiMessageRequestSchema,
// McpUiMessageResult,
// McpUiMessageResultSchema,
// McpUiOpenLinkRequest,
// McpUiOpenLinkRequestSchema,
// McpUiOpenLinkResultSchema,
// McpUiRenderDataResultSchema,
// McpUiSizeChangeNotification,
// McpUiSizeChangeNotificationSchema,
// } from "./types";
// export { PostMessageTransport } from "./message-transport.js";
// if (false) {
// let server: Server;
// let client: Client;
// }
// const McpUiInitializeRequestSchema = z.object({
// method: z.literal("ui/initialize"),
// params: z.object({
// name: z.string(),
// version: z.string(),
// capabilities: ServerCapabilitiesSchema.extend(
// ClientCapabilitiesSchema.shape,
// ),
// }),
// });
// /**
// * Converts a provided Zod schema to a Zod object if it is a ZodRawShape,
// * otherwise returns the schema as is.
// */
// function getZodSchemaObject(
// schema: ZodRawShape | ZodType<object> | undefined,
// ): ZodType<object> | undefined {
// if (!schema) {
// return undefined;
// }
// if (isZodRawShape(schema)) {
// return z.object(schema);
// }
// return schema;
// }
// export class McpUiGuest extends Protocol<Request, Notification, Result> {
// private _registeredHostTools: Record<string, RegisteredTool> = {};
// assertCapabilityForMethod(method: Request["method"]): void {}
// assertRequestHandlerCapability(method: Request["method"]): void {}
// assertNotificationCapability(method: Notification["method"]): void {}
// async callServerTool(
// name: string,
// argsSchema: ZodTypeAny,
// args: unknown,
// options?: RequestOptions,
// ) {
// //: Promise<CallToolResult> {
// }
// async addUiToChat(
// resourceUri: string,
// state: Record<string, unknown>,
// ): Promise<void> {}
// // In future, could expose callHostTool
// registerHostTool<
// InputArgs extends ZodRawShape | ZodType<object>,
// OutputArgs extends ZodRawShape | ZodType<object>,
// >(
// name: string,
// config: {
// title?: string;
// description?: string;
// inputSchema?: InputArgs;
// outputSchema?: OutputArgs;
// annotations?: ToolAnnotations;
// _meta?: Record<string, unknown>;
// },
// cb: ToolCallback<InputArgs>,
// ): RegisteredTool {
// if (this._registeredHostTools[name]) {
// throw new Error(`Tool ${name} is already registered on host`);
// }
// const {
// title,
// description,
// inputSchema,
// outputSchema,
// annotations,
// _meta,
// } = config;
// return this._createRegisteredTool(
// name,
// title,
// description,
// inputSchema,
// outputSchema,
// annotations,
// _meta,
// cb as ToolCallback<ZodRawShape | undefined>,
// );
// }
// private _createRegisteredTool(
// name: string,
// title: string | undefined,
// description: string | undefined,
// inputSchema: ZodRawShape | ZodType<object> | undefined,
// outputSchema: ZodRawShape | ZodType<object> | undefined,
// annotations: ToolAnnotations | undefined,
// _meta: Record<string, unknown> | undefined,
// callback: ToolCallback<ZodRawShape | undefined>,
// ): RegisteredTool {
// const registeredTool: RegisteredTool = {
// title,
// description,
// inputSchema: getZodSchemaObject(inputSchema) as any,
// outputSchema: getZodSchemaObject(outputSchema) as any,
// annotations,
// _meta,
// callback,
// enabled: true,
// disable: () => registeredTool.update({ enabled: false }),
// enable: () => registeredTool.update({ enabled: true }),
// remove: () => registeredTool.update({ name: null }),
// update: (updates) => {
// if (typeof updates.name !== "undefined" && updates.name !== name) {
// delete this._registeredHostTools[name];
// if (updates.name)
// this._registeredHostTools[updates.name] = registeredTool;
// }
// if (typeof updates.title !== "undefined")
// registeredTool.title = updates.title;
// if (typeof updates.description !== "undefined")
// registeredTool.description = updates.description;
// if (typeof updates.paramsSchema !== "undefined")
// registeredTool.inputSchema = z.object(updates.paramsSchema);
// if (typeof updates.callback !== "undefined")
// registeredTool.callback = updates.callback;
// if (typeof updates.annotations !== "undefined")
// registeredTool.annotations = updates.annotations;
// if (typeof updates._meta !== "undefined")
// registeredTool._meta = updates._meta;
// if (typeof updates.enabled !== "undefined")
// registeredTool.enabled = updates.enabled;
// // this.sendToolListChanged();
// },
// };
// this._registeredHostTools[name] = registeredTool;
// return registeredTool;
// }
// sendMessage(params: McpUiMessageRequest["params"], options?: RequestOptions) {
// return this.request(
// <McpUiMessageRequest>{
// method: "ui/message",
// params,
// },
// McpUiMessageResultSchema,
// options,
// );
// }
// sendLog(params: LoggingMessageNotification["params"]) {
// return this.notification(<LoggingMessageNotification>{
// method: "notifications/message",
// params,
// });
// }
// sendOpenLink(
// params: McpUiOpenLinkRequest["params"],
// options?: RequestOptions,
// ) {
// return this.request(
// <McpUiOpenLinkRequest>{
// method: "ui/open-link",
// params,
// },
// McpUiOpenLinkResultSchema,
// options,
// );
// }
// sendSizeChange(params: McpUiSizeChangeNotification["params"]) {
// return this.notification(<McpUiSizeChangeNotification>{
// method: "ui/size-change",
// params,
// });
// }
// private _hostCapabilities?: McpUiHostCapabilities;
// private _hostInfo?: McpUiHostInfo;
// override async connect(
// transport: Transport,
// options?: RequestOptions,
// ): Promise<void> {
// await super.connect(transport);
// try {
// const result = await this.request(
// <McpUiInitializeRequest>{
// method: "ui/initialize",
// params: {
// // TODO: protocolVersion: LATEST_PROTOCOL_VERSION,
// },
// },
// McpUiInitializeResultSchema,
// options,
// );
// if (result === undefined) {
// throw new Error(`Server sent invalid initialize result: ${result}`);
// }
// this._hostCapabilities = result.hostCapabilities;
// this._hostInfo = result.hostInfo;
// await this.notification(<McpUiInitializedNotification>{
// method: "ui/notifications/initialized",
// });
// } catch (error) {
// // Disconnect if initialization fails.
// void this.close();
// throw error;
// }
// }
// }
// export type ToolCallback<
// Args extends undefined | ZodRawShape | ZodType<object> = undefined,
// > = Args extends ZodRawShape
// ? (
// args: z.objectOutputType<Args, ZodTypeAny>,
// extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
// ) => CallToolResult | Promise<CallToolResult>
// : Args extends ZodType<infer T>
// ? (
// args: T,
// extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
// ) => CallToolResult | Promise<CallToolResult>
// : (
// extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
// ) => CallToolResult | Promise<CallToolResult>;
// // Helper to check if an object is a Zod schema (ZodRawShape)
// function isZodRawShape(obj: unknown): obj is ZodRawShape {
// if (typeof obj !== "object" || obj === null) return false;
// const isEmptyObject = Object.keys(obj).length === 0;
// // Check if object is empty or at least one property is a ZodType instance
// // Note: use heuristic check to avoid instanceof failure across different Zod versions
// return isEmptyObject || Object.values(obj as object).some(isZodTypeLike);
// }
// function isZodTypeLike(value: unknown): value is ZodType {
// return (
// value !== null &&
// typeof value === "object" &&
// "parse" in value &&
// typeof value.parse === "function" &&
// "safeParse" in value &&
// typeof value.safeParse === "function"
// );
// }

2
src/react/index.tsx Normal file
View file

@ -0,0 +1,2 @@
export * from "./useApp";
export * from "./useAutoResize";