mcp-ui SDK

repository·main·Indexed 26 days ago

https://github.com/mcp-ui-org/mcp-ui

An SDK suite implementing the MCP Apps standard to deliver rich, interactive web-based UIs over the Model Context Protocol. It enables AI agents to present complex interfaces like buttons, forms, and widgets. The suite includes TypeScript SDKs (@mcp-ui/server and @mcp-ui/client), as well as server-side implementations for Python (mcp-ui-server) and Ruby (mcp_ui_server).

Tokens
46.1K
Snippets
134
Records
186
Agent score
85%

What's inside mcp-ui

  1. Overview of MCP-UI SDK packages

    main

    The @mcp-ui/* ecosystem provides tools for both servers and clients to implement the MCP Apps standard:

    • @mcp-ui/server (Server SDK): Provides createUIResource to build UI payloads (HTML or external URLs). It integrates with @modelcontextprotocol/ext-apps/server functions like registerAppTool and registerAppResource.
    • @mcp-ui/client (Client SDK): Provides AppRenderer (high-level component for fetching and rendering tool UIs) and AppFrame (low-level component for pre-fetched HTML).
    • mcp_ui_server (Ruby): Provides helper methods for creating UI resources in Ruby.
    • mcp-ui-server (Python): Provides helper methods for creating UI resources in Python.
  2. Overview of mcp-ui and MCP Apps support

    main

    mcp-ui is a Model Context Protocol (MCP) UI SDK designed to enable rich, dynamic, and interactive interfaces for AI tools. It implements the MCP Apps specification, allowing tools to return more than just plain text by embedding HTML resources and enabling bidirectional communication between UIs and hosts.

    Key capabilities include:

    • Embedding HTML resources in tool responses.
    • Secure sandboxed rendering of UIs.
    • Bidirectional communication between the UI and the MCP host.
  3. Overview of mcp-ui SDKs

    main

    mcp-ui is an SDK implementing the MCP Apps standard for delivering interactive UIs over the Model Context Protocol (MCP). It provides tools for both servers to create UI resources and clients to render them.

    Available SDKs:

    • @mcp-ui/server (TypeScript): Used to create UI resources via createUIResource. Integrates with @modelcontextprotocol/ext-apps/server functions like registerAppTool and registerAppResource.
    • @mcp-ui/client (TypeScript): Used by MCP Apps Hosts to render tool UIs using AppRenderer or legacy hosts using UIResourceRenderer.
    • mcp_ui_server (Ruby): Create UI resources in Ruby.
    • mcp-ui-server (Python): Create UI resources in Python.
  4. Understand the legacy MCP-UI postMessage protocol

    main

    The legacy MCP-UI protocol uses postMessage for communication between embedded iframes and their parent host window. This is used for UI over MCP implementations.

    Note: For new MCP Apps, it is recommended to use the MCP Apps JSON-RPC protocol with _meta.ui.resourceUri instead of this legacy protocol.

  5. Enhance HTML with communication capabilities

    main

    Use wrap_html_with_communication from mcp_ui_server.utils to wrap raw HTML strings, enabling them to communicate with the MCP client.

    from mcp_ui_server.utils import wrap_html_with_communication
    
    # Basic HTML
    html = "<div>My content</div>"
    
    # Enhanced with MCP UI communication
    enhanced_html = wrap_html_with_communication(html)
    
    # Use in resource
    resource = create_ui_resource({
        "uri": "ui://enhanced-html",
        "content": {"type": "rawHtml", "htmlString": enhanced_html},
        "encoding": "text"
    })
  6. Integrate OpenAI Apps SDK for ChatGPT

    main

    To run MCP-UI HTML widgets inside ChatGPT, you must use the @mcp-ui/server Apps SDK adapter. This requires a dual-resource approach:

    1. A Static Template: A resource registered via the MCP Resources API that has the Apps SDK adapter enabled (adapters.appsSdk.enabled: true). This template allows ChatGPT to inject the necessary bridge script and use the text/html+skybridge MIME type.
    2. An Embedded Resource: A standard MCP-UI resource returned in the tool response for MCP-native hosts. This resource must not have the Apps SDK adapter enabled.

    This pattern ensures compatibility with both ChatGPT (via the template) and standard MCP-UI hosts (via the embedded resource).

  7. Support MCP-UI Hosts with Embedded Resources

    main

    If you want your tool to work in legacy MCP-UI hosts (which expect embedded resources in tool responses) while still supporting MCP Apps hosts, you should return a createUIResource result within the tool's execution handler.

    Note: The embedded resource should not have the MCP Apps adapter enabled. MCP Apps hosts will ignore the embedded resource and use the _meta.ui.resourceUri instead.

    registerAppTool(
      server,
      'my_widget',
      {
        description: 'An interactive widget',
        inputSchema: { query: z.string().describe('User query') },
        _meta: {
          ui: { resourceUri: widgetUI.resource.uri }
        }
      },
      async ({ query }) => {
        // Create an embedded UI resource for MCP-UI hosts
        const embeddedResource = await createUIResource({
          uri: `ui://my-server/widget/${query}`,
          encoding: 'text',
          content: {
            type: 'rawHtml',
            htmlString: renderWidget(query),
          },
        });
    
        return {
          content: [
            { type: 'text', text: `Processing: ${query}` },
            embeddedResource // Include for MCP-UI hosts
          ],
        };
      }
    );
  8. Implement the MCP Apps Pattern

    main

    The recommended way to link tools to their UIs is the MCP Apps pattern. This involves:

    1. Creating a UI resource using createUIResource.
    2. Registering the resource with registerAppResource.
    3. Registering a tool with registerAppTool and linking it to the UI via the _meta.ui.resourceUri property.
    import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
    import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
    import { createUIResource } from '@mcp-ui/server';
    import { z } from 'zod';
    
    const server = new McpServer({ name: 'my-server', version: '1.0.0' });
    
    // 1. Create UI resource
    const widgetUI = await createUIResource({
      uri: 'ui://my-server/widget',
      content: {
        type: 'rawHtml',
        htmlString: `<html>...</html>`,
      },
      encoding: 'text',
    });
    
    // 2. Register resource handler
    registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
      contents: [widgetUI.resource]
    }));
    
    // 3. Register tool with _meta.ui.resourceUri
    registerAppTool(server, 'show_widget', {
      description: 'Show an interactive widget',
      inputSchema: {
        query: z.string().describe('User query'),
      },
      _meta: {
        ui: {
          resourceUri: widgetUI.resource.uri
        }
      }
    }, async ({ query }) => {
      return {
        content: [{ type: 'text', text: `Processing: ${query}` }]
      };
    });
  9. Expose an MCP Server via HTTP (WEBrick)

    main

    To make your MCP server accessible to web clients, you can wrap it in an HTTP server like WEBrick. You must implement a servlet that:

    1. Handles OPTIONS requests for CORS pre-flight (setting Access-Control-Allow-Origin: *).
    2. Handles POST requests by passing the request body to the MCP server's handle_json method.
    3. Returns the resulting JSON response.

    Example implementation using WEBrick::HTTPServlet::AbstractServlet:

    require 'webrick'
    
    # --- MCP Server Setup ---
    mcp_server = MCP::Server.new(tools: [ExternalUrlTool])
    
    # --- WEBrick HTTP Server Setup ---
    http_server = WEBrick::HTTPServer.new(Port: 8081)
    
    class MCPServlet < WEBrick::HTTPServlet::AbstractServlet
      def initialize(server, mcp_instance)
        super(server)
        @mcp = mcp_instance
      end
    
      def do_OPTIONS(_request, response)
        response.status = 200
        response['Access-Control-Allow-Origin'] = '*'
        response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
        response['Access-Control-Allow-Headers'] = 'Content-Type, Accept'
      end
    
      def do_POST(request, response)
        response['Access-control-Allow-Origin'] = '*'
        response.status = 200
        response['Content-Type'] = 'application/json'
        response.body = @mcp.handle_json(request.body)
      end
    end
    
    http_server.mount('/mcp', MCPServlet, mcp_server)
    
    trap('INT') { http_server.shutdown }
    http_server.start