Skip to content
111 changes: 104 additions & 7 deletions docs/draft/docs/ui/advanced/mcp-bridge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,17 @@ MCP Bridge is a runtime adapter that:
interface MCPBridge {
readonly provider: 'openai' | 'ext-apps' | 'claude' | 'unknown';

// Core methods
callTool(name: string, params: object): Promise<unknown>;
sendMessage(content: string): Promise<void>;
openLink(url: string): Promise<void>;

// ext-apps specific methods (SEP-1865)
updateModelContext(context: unknown, merge?: boolean): Promise<void>;
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: unknown): Promise<void>;
registerTool(name: string, description: string, inputSchema: object): Promise<void>;
unregisterTool(name: string): Promise<void>;

readonly toolInput: Record<string, unknown>;
readonly toolOutput: unknown;
readonly structuredContent: unknown;
Expand Down Expand Up @@ -187,6 +194,76 @@ const unsubscribe = window.mcpBridge.onToolResult((result) => {
});
```

## ext-apps Methods (SEP-1865)

The MCP Apps Extension protocol (SEP-1865) provides additional bidirectional communication methods for ext-apps widgets.

### Update Model Context

Share widget state with the AI model:

```typescript
// Update context (merged with existing by default)
await window.mcpBridge.updateModelContext({
selectedItem: 'item-123',
userPreference: 'detailed',
});

// Replace context entirely
await window.mcpBridge.updateModelContext(newContext, false);
```

This allows the model to reference widget state in subsequent interactions.

### Widget Logging

Send log messages to the host for debugging:

```typescript
await window.mcpBridge.log('info', 'Widget initialized');
await window.mcpBridge.log('debug', 'User clicked button', { buttonId: 'submit' });
await window.mcpBridge.log('warn', 'Connection slow');
await window.mcpBridge.log('error', 'Failed to fetch data', { error: err.message });
```

### Dynamic Tool Registration

Widgets can register tools dynamically:

```typescript
// Register a widget-defined tool
await window.mcpBridge.registerTool(
'widget_action',
'Performs an action from the widget',
{
type: 'object',
properties: {
action: { type: 'string', enum: ['approve', 'reject'] },
reason: { type: 'string' },
},
required: ['action'],
}
);

// Later, unregister when no longer needed
await window.mcpBridge.unregisterTool('widget_action');
```

### Check Host Capabilities

Before using ext-apps features, check if the host supports them:

```typescript
// The bridge falls back gracefully, but you can check explicitly
try {
await window.mcpBridge.updateModelContext({ key: 'value' });
} catch (err) {
if (err.message.includes('not supported')) {
console.log('Host does not support model context updates');
}
}
```

## Complete Interactive Widget

```typescript
Expand Down Expand Up @@ -288,13 +365,33 @@ ui: {

## Platform Compatibility

| Feature | OpenAI | Claude | Gemini |
| ----------------- | ------ | ------- | ------ |
| `callTool` | Yes | No | No |
| `sendMessage` | Yes | No | No |
| `openLink` | Yes | Yes | Yes |
| `setWidgetState` | Yes | No | No |
| `onContextChange` | Yes | Limited | No |
| Feature | OpenAI | ext-apps | Claude | Gemini |
| ---------------------- | ------ | -------- | ------- | ------ |
| `callTool` | Yes | Yes* | Yes* | No |
| `sendMessage` | Yes | Yes | Yes | No |
| `openLink` | Yes | Yes* | Yes* | Yes |
| `setWidgetState` | Yes | Yes | Yes | No |
| `onContextChange` | Yes | Yes | Yes | No |
| `updateModelContext` | No | Yes* | Yes* | No |
| `log` | No | Yes* | Yes* | No |
| `registerTool` | No | Yes* | Yes* | No |
| `unregisterTool` | No | Yes* | Yes* | No |
| `resourceUri` | Yes | Yes | Yes | No |

*Features depend on host capabilities negotiated during initialization.

<Note>
**Claude MCP Apps:** Claude supports the ext-apps protocol with full
bidirectional JSON-RPC communication via `window.__mcpAppsEnabled`.

See: [MCP Apps Announcement](https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/)
</Note>

<Note>
**Platform Detection:** The bridge uses explicit markers to detect each platform.
For ext-apps, set `window.__mcpPlatform = 'ext-apps'` before loading the bridge.
Generic iframes without markers will use the fallback generic adapter.
</Note>

Always check capabilities before using features:

Expand Down
114 changes: 114 additions & 0 deletions docs/draft/docs/ui/advanced/platforms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,120 @@ if (platform.scripts === 'inline') {
}
```

## ext-apps (SEP-1865)

ext-apps is the MCP Apps Extension protocol that standardizes bidirectional widget communication across AI hosts.

### Features

- Full network access
- JSON-RPC 2.0 over postMessage
- Bidirectional communication (widget ↔ host)
- Tool invocation from widgets
- Model context updates
- Dynamic tool registration
- Widget logging

### Configuration

```typescript
import { EXT_APPS_PLATFORM } from '@frontmcp/uipack/theme';

// Platform preset
console.log(EXT_APPS_PLATFORM);
// {
// id: 'ext-apps',
// network: 'open',
// scripts: 'external',
// capabilities: {
// callTool: true, // Depends on serverToolProxy
// sendMessage: true,
// openExternal: true, // Depends on openLink capability
// requestDisplayMode: true,
// widgetState: true,
// updateModelContext: true, // Depends on modelContextUpdate
// registerTool: true, // Depends on widgetTools
// },
// }
```

### Host Capabilities

ext-apps uses capability negotiation during initialization. The host advertises supported features:

```typescript
interface ExtAppsHostCapabilities {
serverToolProxy?: boolean; // Can proxy tool calls to MCP server
openLink?: boolean; // Can open external URLs
modelContextUpdate?: boolean; // Supports model context updates
widgetTools?: boolean; // Supports widget-defined tools
displayModes?: ('inline' | 'fullscreen' | 'pip')[];
logging?: boolean; // Supports widget logging
}
```

### JSON-RPC Communication

ext-apps widgets communicate via JSON-RPC 2.0 over postMessage:

```typescript
// Widget sends request
{
"jsonrpc": "2.0",
"id": 1,
"method": "ui/callServerTool",
"params": { "name": "get_weather", "arguments": { "city": "NYC" } }
}

// Host responds
{
"jsonrpc": "2.0",
"id": 1,
"result": { "temperature": 72, "conditions": "Sunny" }
}
```

### Tool Discovery Metadata

Tools with UI templates expose ext-apps metadata in `tools/list`:

```typescript
@Tool({
name: 'weather',
ui: {
template: WeatherWidget,
resourceUri: 'ui://my-app/weather.html', // Custom URI (optional)
widgetCapabilities: {
toolListChanged: false,
supportsPartialInput: true,
},
},
})
```

The discovery response includes:

```json
{
"ui/resourceUri": "ui://my-app/weather.html",
"ui/capabilities": {
"tools": { "listChanged": false },
"supportsPartialInput": true
}
}
```

### MIME Types

ext-apps uses specific MIME types:

```typescript
import { getExtAppsMimeType } from '@frontmcp/uipack/adapters';

getExtAppsMimeType('standard'); // 'text/html+mcp'
getExtAppsMimeType('profile'); // 'text/html;profile=mcp-app'
```

## OpenAI

OpenAI's Apps SDK provides full widget capabilities:
Expand Down
29 changes: 29 additions & 0 deletions docs/draft/docs/ui/api-reference/runtime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ interface MCPBridge {
/** Detected provider type */
readonly provider: ProviderType;

// Core methods
/** Call a tool on the MCP server */
callTool(name: string, params: Record<string, unknown>): Promise<unknown>;

Expand All @@ -183,6 +184,19 @@ interface MCPBridge {
/** Open an external link */
openLink(url: string): Promise<void>;

// ext-apps methods (SEP-1865)
/** Update model context with widget state (ext-apps only) */
updateModelContext(context: unknown, merge?: boolean): Promise<void>;

/** Send log message to host (ext-apps only) */
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: unknown): Promise<void>;

/** Register a widget-defined tool (ext-apps only) */
registerTool(name: string, description: string, inputSchema: Record<string, unknown>): Promise<void>;

/** Unregister a widget-defined tool (ext-apps only) */
unregisterTool(name: string): Promise<void>;

/** Get the tool input arguments */
readonly toolInput: Record<string, unknown>;

Expand Down Expand Up @@ -515,6 +529,21 @@ getToolUIMimeType('ext-apps'); // 'text/html+mcp'
getToolUIMimeType('generic'); // 'text/html'
```

### `getExtAppsMimeType(variant?)`

Gets the ext-apps MIME type with optional variant.

```typescript
import { getExtAppsMimeType, isExtAppsMimeType } from '@frontmcp/uipack/adapters';

getExtAppsMimeType(); // 'text/html+mcp' (standard)
getExtAppsMimeType('standard'); // 'text/html+mcp'
getExtAppsMimeType('profile'); // 'text/html;profile=mcp-app'

isExtAppsMimeType('text/html+mcp'); // true
isExtAppsMimeType('text/html'); // false
```

### OpenAIRuntime Interface

The `window.openai` interface exposed by OpenAI's environment.
Expand Down
29 changes: 29 additions & 0 deletions docs/draft/docs/ui/integration/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,35 @@ ui: {
}
```

### resourceUri (ext-apps)

Custom resource URI for the widget (ext-apps/MCP Apps):

```typescript
ui: {
template: WeatherWidget,
resourceUri: 'ui://my-app/weather.html', // Custom URI
}
```

If not specified, auto-generated as `ui://widget/{toolName}.html`.

### widgetCapabilities (ext-apps)

Widget capabilities for ext-apps initialization:

```typescript
ui: {
template: MyWidget,
widgetCapabilities: {
toolListChanged: false, // Widget cannot emit tool list changes
supportsPartialInput: true, // Widget handles streaming input
},
}
```

These capabilities are communicated to the host during tool discovery.

### wrapper

Custom wrapper for the rendered content:
Expand Down
Loading
Loading