MCP Apps Kit - v0.5.0
    Preparing search index...

    Quickstart

    Build interactive MCP applications with rich UIs that work on both MCP Apps and ChatGPT from a single codebase.

    # Create a new project
    npx @mcp-apps-kit/create-app my-app
    cd my-app
    pnpm install

    # Or add to an existing project
    pnpm add @mcp-apps-kit/core @mcp-apps-kit/ui-react zod

    Create an MCP server with tools that return structured data:

    import { createApp, defineTool, defineUI } from "@mcp-apps-kit/core";
    import { z } from "zod";

    // Define a UI resource for the tool
    const greetingWidget = defineUI({
    name: "Greeting Widget",
    html: "./ui/dist/greeting.html",
    prefersBorder: true,
    });

    // Create the app
    const app = createApp({
    name: "my-app",
    version: "1.0.0",
    tools: {
    greet: defineTool({
    title: "Greet User",
    description: "Generate a personalized greeting",
    input: z.object({
    name: z.string().describe("Name to greet"),
    }),
    output: z.object({
    message: z.string(),
    timestamp: z.string(),
    }),
    ui: greetingWidget,
    handler: async ({ name }) => ({
    message: `Hello, ${name}!`,
    timestamp: new Date().toISOString(),
    }),
    }),
    },
    });

    // Start the server
    await app.start({ port: 3000 });

    Create a React component that displays the tool result. Wrap your app with AppsProvider:

    // ui/src/App.tsx
    import { AppsProvider } from "@mcp-apps-kit/ui-react";
    import { GreetingWidget } from "./GreetingWidget";

    export function App() {
    return (
    <AppsProvider>
    <GreetingWidget />
    </AppsProvider>
    );
    }
    // ui/src/GreetingWidget.tsx
    import { useToolResult, useHostContext } from "@mcp-apps-kit/ui-react";

    interface GreetingResult {
    message: string;
    timestamp: string;
    }

    export function GreetingWidget() {
    const result = useToolResult<GreetingResult>();
    const { theme } = useHostContext();

    if (!result) {
    return <div>Loading...</div>;
    }

    return (
    <div style={{
    padding: "1rem",
    background: theme === "dark" ? "#1a1a1a" : "#fff"
    }}>
    <h2>{result.message}</h2>
    <p>Generated at: {new Date(result.timestamp).toLocaleString()}</p>
    </div>
    );
    }

    Export types from your server to get fully typed tool results in UI code:

    // server/index.ts
    import { createApp, defineTool, type ClientToolsFromCore } from "@mcp-apps-kit/core";
    import { z } from "zod";

    const app = createApp({
    name: "my-app",
    version: "1.0.0",
    tools: {
    greet: defineTool({
    input: z.object({ name: z.string() }),
    output: z.object({ message: z.string(), timestamp: z.string() }),
    handler: async (input) => ({
    message: `Hello, ${input.name}!`,
    timestamp: new Date().toISOString(),
    }),
    }),
    },
    });

    // Export types for UI code
    export type AppTools = typeof app.tools;
    export type AppClientTools = ClientToolsFromCore<AppTools>;

    Then use the types in your UI:

    // ui/Widget.tsx
    import { useToolResult, useAppsClient } from "@mcp-apps-kit/ui-react";
    import type { AppClientTools } from "../server";

    function Widget() {
    const client = useAppsClient<AppClientTools>();
    const result = useToolResult<AppClientTools>();

    // TypeScript enforces correct tool name and input shape
    const handleGreet = () => client.callTool("greet", { name: "Alice" });

    // result?.greet?.message is typed as string | undefined
    if (result?.greet) {
    return <p>{result.greet.message}</p>;
    }

    return <button onClick={handleGreet}>Greet</button>;
    }
    Hook Purpose
    useAppsClient() Client instance for tool calls
    useToolResult<T>() Current tool result data
    useToolInput<T>() Tool input parameters
    useHostContext() Host info (theme, viewport, locale)
    useWidgetState() Persist state across reloads
    useDisplayMode() Toggle fullscreen mode
    import { useHostCapabilities, useHostVersion } from "@mcp-apps-kit/ui-react";

    function Widget() {
    const capabilities = useHostCapabilities();
    const version = useHostVersion();

    const themes = capabilities?.theming?.themes; // ["light", "dark"]
    const modes = capabilities?.displayModes?.modes; // ["inline", "fullscreen"]
    const hasFileUpload = !!capabilities?.fileUpload; // ChatGPT only

    return <div>Host: {version?.name}</div>;
    }
    Hook Purpose
    useFileUpload() Upload files to host
    useFileDownload() Get file download URLs
    useModal() Show modal dialogs
    Hook Purpose
    useHostStyleVariables() Apply host CSS variables
    useDocumentTheme() Sync document theme
    useSafeAreaInsets() Safe area insets (ChatGPT)

    Add cross-cutting concerns with Koa-style middleware:

    app.use(async (context, next) => {
    console.log(`Tool called: ${context.toolName}`);
    const start = Date.now();

    await next();

    console.log(`Completed in ${Date.now() - start}ms`);
    });

    Extend functionality with lifecycle hooks:

    import { loggingPlugin } from "@mcp-apps-kit/core";

    const app = createApp({
    name: "my-app",
    version: "1.0.0",
    plugins: [loggingPlugin],
    tools: { /* ... */ },
    });

    Create custom plugins:

    const myPlugin = {
    name: "my-plugin",
    onInit: async () => console.log("App initializing..."),
    onStart: async () => console.log("App started"),
    beforeToolCall: async (context) => {
    console.log(`Tool called: ${context.toolName}`);
    },
    afterToolCall: async (context) => {
    console.log(`Tool completed: ${context.toolName}`);
    },
    };

    Subscribe to app lifecycle events:

    app.on("tool:called", ({ toolName }) => {
    analytics.track("tool_called", { tool: toolName });
    });

    Enable debug logging to receive logs from client UIs:

    const app = createApp({
    name: "my-app",
    version: "1.0.0",
    tools: { /* ... */ },
    config: {
    debug: {
    logTool: true,
    level: "debug",
    },
    },
    });

    Use the server-side logger:

    import { debugLogger } from "@mcp-apps-kit/core";

    debugLogger.info("User action", { userId: "456" });

    Add bearer token validation:

    const app = createApp({
    name: "my-app",
    version: "1.0.0",
    tools: { /* ... */ },
    config: {
    oauth: {
    protectedResource: "http://localhost:3000",
    authorizationServer: "https://auth.example.com",
    scopes: ["mcp:read", "mcp:write"],
    },
    },
    });

    Access auth context in handlers:

    tools: {
    get_user_data: defineTool({
    input: z.object({}),
    handler: async (_input, context) => {
    const subject = context.subject; // Authenticated user ID
    return { userId: subject };
    },
    }),
    }

    MCP Apps Kit automatically handles protocol differences:

    Feature MCP Apps ChatGPT
    Tool execution MCP protocol OpenAI Apps SDK
    UI rendering iframe Widget runtime
    Theme support Auto-detected Auto-detected
    File upload Supported Supported
    Modals Supported Supported
    Partial input Supported -
    Size notifications Supported -