🥞PancakeJS

Auto-Bridge Detection

PancakeJS automatically detects the host environment and selects the appropriate communication bridge. This means your widgets work in both MCP Apps and ChatGPT Apps without any code changes.

How It Works

When your widget loads, the SDK checks for environment markers:

function detectUiAdapter(): UiAdapter {
  // Check for ChatGPT environment
  if (typeof window !== 'undefined' && window.openai) {
    return createChatGptUiAdapter();
  }

  // Default to MCP Apps (postMessage)
  return createMcpAppsUiAdapter();
}

Bridge Comparison

MCP Apps Bridge

Uses postMessage with JSON-RPC 2.0 protocol:

Widget (iframe) <──postMessage──> Host App
AspectDetails
CommunicationWindow postMessage API
ProtocolJSON-RPC 2.0 with correlation IDs
SecurityOrigin validation, handshake tokens
ResourcesServed via resources.read MCP protocol

ChatGPT Apps Bridge

Uses the window.openai API:

Widget <──window.openai──> ChatGPT Runtime
AspectDetails
CommunicationDirect API calls
ProtocolPromise-based method calls
SecurityChatGPT's sandbox
ResourcesServed via output templates

Server-Side Adapters

Both adapters transform your universal tool definitions for their respective hosts:

MCP Apps Adapter

// Decorates widget metadata
{
  name: 'myWidget',
  meta: {
    'ui/resourceUri': 'ui://widget/myWidget'
  }
}

// Registers resource handlers
resources.list  → includes widget URIs
resources.read  → returns widget HTML

ChatGPT Apps Adapter

// Decorates widget metadata
{
  name: 'myWidget',
  meta: {
    'openai/outputTemplate': 'ui://widget/myWidget.html',
    'openai/intent': 'render_widget'
  }
}

Using the Provider

Wrap your widget with UniversalAppProvider:

import { UniversalAppProvider } from '@pancakeapps/react';

function App() {
  return (
    <UniversalAppProvider
      fallback={<Loading />}
      strict={false}  // Set to true for development
    >
      <MyWidget />
    </UniversalAppProvider>
  );
}

Provider Options

OptionTypeDescription
fallbackReactNodeShown while initializing
strictbooleanThrow errors for unsupported features
adapter() => UiAdapterOverride auto-detection
errorFallback(error) => ReactNodeCustom error display

Manual Adapter Selection

If needed, you can override auto-detection:

import { createMcpAppsUiAdapter } from '@pancakeapps/adapter-mcp-apps';

<UniversalAppProvider adapter={() => createMcpAppsUiAdapter()}>
  <MyWidget />
</UniversalAppProvider>

Checking the Active Bridge

Use useHost() to check which bridge is active:

function MyWidget() {
  const host = useHost();

  console.log('Running on:', host.id);  // 'mcp-apps' or 'chatgpt'
  console.log('Capabilities:', host.capabilities);

  return <div>Running on {host.id}</div>;
}

Testing Both Bridges

The dev inspector provides harnesses for both environments, letting you verify your widget works correctly in both without deploying.

  1. Start dev server: pnpm universal-apps dev
  2. Open: http://localhost:3000/_inspector
  3. Select a widget
  4. Switch between "MCP Harness" and "ChatGPT Harness" tabs

This lets you verify your widget works correctly in both environments without deploying.

Debug Mode

Enable debug mode for detailed logging:

// MCP Apps adapter
createMcpAppsUiAdapter({ debug: true });

// ChatGPT Apps adapter
createChatGptUiAdapter({ debug: true });

Next Steps

On this page