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| Aspect | Details |
|---|---|
| Communication | Window postMessage API |
| Protocol | JSON-RPC 2.0 with correlation IDs |
| Security | Origin validation, handshake tokens |
| Resources | Served via resources.read MCP protocol |
ChatGPT Apps Bridge
Uses the window.openai API:
Widget <──window.openai──> ChatGPT Runtime| Aspect | Details |
|---|---|
| Communication | Direct API calls |
| Protocol | Promise-based method calls |
| Security | ChatGPT's sandbox |
| Resources | Served 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 HTMLChatGPT 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
| Option | Type | Description |
|---|---|---|
fallback | ReactNode | Shown while initializing |
strict | boolean | Throw errors for unsupported features |
adapter | () => UiAdapter | Override auto-detection |
errorFallback | (error) => ReactNode | Custom 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.
- Start dev server:
pnpm universal-apps dev - Open:
http://localhost:3000/_inspector - Select a widget
- 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
- Capabilities — Handle feature differences between hosts
- Troubleshooting — Debug common issues