> ## Documentation Index
> Fetch the complete documentation index at: https://mcp-b-sync-npm-packages-docs-bf03420.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building MCP-UI + WebMCP Apps

> Complete guide to building interactive apps that AI agents can control. Scaffold bidirectional MCP-UI and WebMCP integration with create-webmcp-app CLI tool.

## What You're Building

Building a bidirectional MCP-UI app means the AI agent can invoke your code, which then provides UI to the user, which can then invoke the AI again. This creates interactive workflows where the interface evolves based on what the agent decides to do.

`npx create-webmcp-app` scaffolds a bidirectional system with three components:

1. **MCP Server** (Cloudflare Worker) - Exposes tools to AI agents
2. **Embedded Web App** (React or Vanilla JS) - Runs in an iframe, registers its own tools
3. **Communication Layer** - IframeParentTransport ↔ IframeChildTransport

Your embedded app registers tools that AI can call, creating bidirectional interaction.

## Quick Navigation

<CardGroup cols={3}>
  <Card title="Quick Start" icon="bolt" href="#quick-start">
    Get started in minutes with npx
  </Card>

  <Card title="Architecture" icon="diagram-project" href="/concepts/mcp-ui-integration">
    Understand how it works
  </Card>

  <Card title="MCP Server" icon="server" href="#part-1-the-mcp-server">
    Build the backend tools
  </Card>

  <Card title="Vanilla App" icon="code" href="#part-2-the-embedded-app-vanilla">
    HTML/CSS/JS implementation
  </Card>

  <Card title="React App" icon="react" href="#part-3-the-embedded-app-react">
    TypeScript + React implementation
  </Card>

  <Card title="Deployment" icon="rocket" href="#deployment">
    Deploy to production
  </Card>
</CardGroup>

## Quick Start

```bash theme={null}
npx create-webmcp-app
```

<Tabs>
  <Tab title="Vanilla Template" icon="code">
    HTML/CSS/JavaScript with no build step. Uses CDN for dependencies.

    ```bash theme={null}
    # Select "vanilla" when prompted
    cd your-project
    pnpm dev
    ```

    Runs at: `http://localhost:8889`
  </Tab>

  <Tab title="React Template" icon="react">
    React + TypeScript + Vite with hot module replacement.

    ```bash theme={null}
    # Select "react" when prompted
    cd your-project
    pnpm dev
    ```

    Runs at: `http://localhost:8888`
  </Tab>
</Tabs>

<Info>
  **How it works:** See [MCP-UI Architecture](/concepts/mcp-ui-integration) for detailed diagrams and communication flow between the AI agent, MCP server, host application, and your embedded app.
</Info>

## Part 1: The MCP Server

The MCP server (`worker/mcpServer.ts`) exposes tools that return UI resources.

### Example: Template MCP Server

```typescript theme={null}
import { createUIResource } from '@mcp-ui/server';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { McpAgent } from 'agents/mcp';

export class TemplateMCP extends McpAgent<Cloudflare.Env> {
  server = new McpServer({
    name: 'webmcp-template',
    version: '1.0.0',
  });

  async init() {
    /**
     * This tool tells the AI "here's an interactive web app you can use"
     * Returns a UIResource that the host application will render
     */
    this.server.tool(
      'showTemplateApp',
      `Display the template web application with WebMCP integration.

After calling this tool, the app will appear and register the following WebMCP tools:
- template_get_message: Get the current message from the app
- template_update_message: Update the message displayed in the app
- template_reset: Reset the message to default`,
      {},
      async () => {
        // Point to your embedded app
        const iframeUrl = `${this.env.APP_URL}/`;

        // Create UI resource with iframe URL
        const uiResource = createUIResource({
          uri: 'ui://template-app',
          content: {
            type: 'externalUrl',  // Tell host to load this in iframe
            iframeUrl: iframeUrl,
          },
          encoding: 'blob',
        });

        return {
          content: [
            {
              type: 'text',
              text: `# Template App Started

The template app is now displayed in the side panel.

**Available tools** (registered via WebMCP):
- \`template_get_message\` - View the current message
- \`template_update_message\` - Change the message
- \`template_reset\` - Reset to default

Try calling these tools to interact with the app!`,
            },
            uiResource,
          ],
        };
      }
    );
  }
}
```

<Warning>
  The tools mentioned in the description are registered by the iframe, not by this server.
</Warning>

## Part 2: The Embedded App (Vanilla)

The Vanilla template uses `@mcp-b/global` via CDN—no build step required.

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebMCP Template</title>
  <script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
</head>
<body>
  <div id="app">
    <h1>WebMCP Template</h1>
    <div id="status">Connecting...</div>
    <p id="message">Hello from WebMCP!</p>
    <button id="btn-update">Update</button>
    <button id="btn-reset">Reset</button>
  </div>

  <script>
    let message = 'Hello from WebMCP!';

    // Parent-child ready protocol
    window.addEventListener('message', (e) => {
      if (e.data?.type === 'parent_ready') {
        document.getElementById('status').textContent = 'Connected';
      }
    });
    window.parent.postMessage({ type: 'iframe_ready' }, '*');

    // Register WebMCP tools
    window.navigator.modelContext.provideContext({
      tools: [
        {
          name: 'template_update_message',
          description: 'Update the message displayed in the app',
          inputSchema: {
            type: 'object',
            properties: {
              newMessage: { type: 'string', description: 'New message' }
            },
            required: ['newMessage']
          },
          async execute({ newMessage }) {
            message = newMessage;
            document.getElementById('message').textContent = message;
            return { content: [{ type: 'text', text: `Updated: ${message}` }] };
          }
        }
      ]
    });
  </script>
</body>
</html>
```

<Note>
  Full template with styling and additional tools available via `npx create-webmcp-app` (select "vanilla"). Tool handlers and UI buttons should share the same state management logic.
</Note>

## Part 3: The Embedded App (React)

**src/main.tsx** - Initialize WebMCP before rendering:

```typescript theme={null}
import { initializeWebModelContext } from '@mcp-b/global';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';

initializeWebModelContext({
  transport: { tabServer: { allowedOrigins: ['*'] } }
});

createRoot(document.getElementById('root')!).render(<App />);
```

**src/App.tsx** - Register tools with `useWebMCP`:

```typescript theme={null}
import { useWebMCP } from '@mcp-b/react-webmcp';
import { useState, useEffect } from 'react';
import { z } from 'zod';

export default function App() {
  const [message, setMessage] = useState('Hello from WebMCP!');

  useEffect(() => {
    const handleMessage = (e: MessageEvent) => {
      if (e.data?.type === 'parent_ready') console.log('Connected');
    };
    window.addEventListener('message', handleMessage);
    window.parent.postMessage({ type: 'iframe_ready' }, '*');
    return () => window.removeEventListener('message', handleMessage);
  }, []);

  useWebMCP({
    name: 'template_update_message',
    description: 'Update the message displayed in the app',
    inputSchema: {
      newMessage: z.string().describe('The new message to display'),
    },
    handler: async ({ newMessage }) => {
      setMessage(newMessage);
      return `Message updated to: ${newMessage}`;
    },
  });

  return (
    <div>
      <h1>WebMCP Template</h1>
      <p>{message}</p>
      <button onClick={() => {
        const msg = prompt('Enter message:');
        if (msg) setMessage(msg);
      }}>Update</button>
    </div>
  );
}
```

<Note>
  Full template with Zod validation, TypeScript types, and additional tools available via `npx create-webmcp-app` (select "react").
</Note>

## Development Workflow

<Steps>
  <Step title="Start Development Server">
    Launch your local development environment:

    ```bash theme={null}
    pnpm dev
    ```

    This starts:

    * **MCP server**: `http://localhost:8888/mcp` (or 8889 for vanilla)
    * **Embedded app**: `http://localhost:8888/` (served by the worker)
    * **Hot reload**: Changes to your app update instantly
  </Step>

  <Step title="Connect a Test Client">
    <Tabs>
      <Tab title="Chat UI" icon="messages">
        Use the included chat-ui (in mcp-ui-webmcp repo):

        ```bash theme={null}
        # In a separate terminal
        cd ../chat-ui
        pnpm dev
        # Open http://localhost:5173
        ```
      </Tab>

      <Tab title="Claude Desktop" icon="desktop">
        Add to your Claude Desktop MCP config:

        ```json theme={null}
        {
          "mcpServers": {
            "my-app": {
              "command": "http",
              "args": ["http://localhost:8888/mcp"]
            }
          }
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Test the Interaction">
    1. AI calls `showTemplateApp` → iframe appears
    2. AI calls `template_get_message` → reads current state
    3. AI calls `template_update_message` → updates the UI
    4. UI buttons use the same logic as tool handlers
  </Step>
</Steps>

## Best Practices

<AccordionGroup>
  <Accordion title="Share logic between UI and tools">
    Tool handlers and UI buttons should call the same underlying functions—avoid duplicating state update logic.
  </Accordion>

  <Accordion title="Use tool annotations">
    Use `destructiveHint`, `idempotentHint`, and `readOnlyHint` to help AI understand tool behavior.
  </Accordion>

  <Accordion title="Validate inputs">
    Use JSON Schema (Vanilla) or Zod (React) for type-safe, validated tool inputs.
  </Accordion>

  <Accordion title="Handle parent-ready protocol">
    Wait for the `parent_ready` message before sending notifications to avoid race conditions.
  </Accordion>
</AccordionGroup>

## Deployment

```bash theme={null}
# Build and deploy
pnpm build
pnpm deploy
```

Update your MCP client to point to the production URL (e.g., `https://your-app.workers.dev/mcp`). See the [mcp-ui-webmcp repository](https://github.com/WebMCP-org/mcp-ui-webmcp) for detailed deployment instructions.

## Next Steps

<CardGroup cols={2}>
  <Card title="MCP-UI Architecture" icon="diagram-project" href="/concepts/mcp-ui-integration">
    Deep dive into architecture and communication flow
  </Card>

  <Card title="Examples" icon="code" href="/examples">
    Production examples including TicTacToe game
  </Card>

  <Card title="@mcp-b/react-webmcp" icon="react" href="/packages/react-webmcp">
    React hooks API reference
  </Card>

  <Card title="@mcp-b/transports" icon="arrows-left-right" href="/packages/transports">
    Transport layer documentation
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/troubleshooting">
    Common issues and solutions
  </Card>
</CardGroup>

## Resources

* **Source Code**: [mcp-ui-webmcp repository](https://github.com/WebMCP-org/mcp-ui-webmcp)
* **Live Demos**:
  * [TicTacToe Game](https://beattheclankers.com)
  * [Chat UI](https://mcp-ui.mcp-b.ai)
* **Documentation**:
  * [MCP-UI Docs](https://mcpui.dev)
  * [WebMCP Specification](https://github.com/webmachinelearning/webmcp)
* **Community**: [WebMCP Discord](https://discord.gg/ZnHG4csJRB)
