Skip to content

Conversation

@ochafik
Copy link
Collaborator

@ochafik ochafik commented Dec 16, 2025

Summary

Enable MCP Apps to run transparently in ChatGPT by bridging the MCP Apps protocol to OpenAI's window.openai API.

Client-side: Same app code works in both environments:

const app = new App({ name: "MyApp", version: "1.0.0" }, {});
await app.connect(); // Auto-detects: OpenAI or MCP

Server-side: Single registration serves both platforms:

import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';

// Automatically adds OpenAI metadata (openai/outputTemplate)
registerAppTool(server, "weather", {
  _meta: { ui: { resourceUri: "ui://weather/widget.html" } }
}, handler);

// Registers both MCP and OpenAI (+skybridge) resource variants
registerAppResource(server, "Weather", "ui://weather/widget.html", config, readHtml);

Motivation

MCP Apps and OpenAI Apps SDK solve similar problems with different protocols. Rather than forcing developers to maintain two codebases, this PR allows a single MCP App to run in both:

  • MCP-compatible hosts (Claude, etc.) via PostMessageTransport
  • ChatGPT via the new OpenAITransport

This aligns with broader cross-platform goals discussed in #169 (A2UI interoperability) and #34 (iframe-embed reuse pattern).

Design Principles

  1. Bridge, don't extend: Only maps existing MCP Apps protocol messages to OpenAI equivalents. No new protocol additions.
  2. Auto-detection: experimentalOAICompatibility (default: true) enables transparent platform detection.
  3. Graceful degradation: Reports capabilities dynamically based on what window.openai actually supports.

Protocol Mapping

Fully Bridged (App → Host)

MCP Apps Protocol OpenAI Apps SDK Notes
ui/initialize Properties synthesis Theme, locale, displayMode, viewport, safeArea
tools/call callTool() Full support
ui/message sendFollowUpMessage() Text content extracted
ui/open-link openExternal() URL mapping
ui/request-display-mode requestDisplayMode() Mode passthrough
ui/notifications/size-changed notifyIntrinsicHeight() Height only (OpenAI limitation)

Fully Bridged (Host → App)

OpenAI Apps SDK MCP Apps Protocol Notes
toolInput ui/notifications/tool-input Delivered after connect
toolOutput ui/notifications/tool-result Delivered after connect
toolResponseMetadata _meta in tool-result Using existing spec field

Capability Detection

Capabilities are reported dynamically based on actual window.openai availability:

Capability Detection Reported In
serverTools !!openai.callTool hostCapabilities
openLinks !!openai.openExternal hostCapabilities
logging Always (→ console.log) hostCapabilities
availableDisplayModes !!openai.requestDisplayMode hostContext

This addresses part of #41 (display mode negotiation) by accurately reporting available modes.

Server-Side Dual Registration

The server helpers automatically handle platform differences:

What MCP Apps OpenAI Apps SDK
Tool metadata _meta.ui.resourceUri _meta["openai/outputTemplate"] (auto-added)
Resource URI ui://weather/widget.html ui://weather/widget.html+skybridge
Resource MIME text/html;profile=mcp-app text/html+skybridge

When you call registerAppResource(), it registers two resources:

  1. MCP resource at the base URI
  2. OpenAI resource at URI + +skybridge suffix

Custom MIME types in the callback are preserved; only the default MCP MIME type is converted to skybridge.

Not Bridged (Would Require Spec Additions)

OpenAI Feature Gap Related Issue/PR
widgetState / setWidgetState State persistence not in MCP spec #62, #125
view property Navigation context not in MCP spec #147
requestClose() Close request not in MCP spec
uploadFile() / getFileDownloadUrl() File ops not in MCP spec
requestModal() Modal API not in MCP spec

Test Coverage

  • 76 tests total
  • Environment detection (isOpenAIEnvironment)
  • Transport construction and lifecycle
  • All protocol message mappings
  • Dynamic capability detection
  • Initial state delivery (toolInput, toolOutput, _meta)
  • Server helpers: dual registration, OpenAI metadata, MIME type preservation

Breaking Changes

None. The experimentalOAICompatibility option defaults to true but falls back gracefully when window.openai is not present.


Related Issues: #169, #41, #62, #34, #147
Related PRs: #125

🤖 Generated with Claude Code

ochafik and others added 2 commits December 16, 2025 22:54
Add transparent support for OpenAI's Apps SDK environment alongside MCP.

## Changes

### New: `src/openai/` module
- `transport.ts` - OpenAITransport implementing MCP Transport interface
- `types.ts` - TypeScript types for OpenAI Apps SDK (`window.openai`)
- `transport.test.ts` - Comprehensive tests

### Updated: `src/app.ts`
- Add `experimentalOAICompatibility` option (default: `true`)
- Auto-detect platform: check for `window.openai` → use OpenAI, else MCP
- `connect()` creates appropriate transport automatically

### Updated: `src/react/useApp.tsx`
- Add `experimentalOAICompatibility` prop to `UseAppOptions`
- Pass through to App constructor

## Usage

Apps work transparently in both environments:

```typescript
// Works in both MCP hosts and ChatGPT
const app = new App(appInfo, capabilities);
await app.connect(); // Auto-detects platform

// Force MCP-only mode
const app = new App(appInfo, capabilities, {
  experimentalOAICompatibility: false
});
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Dynamic capability detection based on window.openai availability
- Report availableDisplayModes when requestDisplayMode is available
- Include toolResponseMetadata as _meta in tool-result notification
- registerAppTool adds openai/outputTemplate metadata automatically
- registerAppResource registers both MCP and OpenAI (+skybridge) variants
- Preserve custom MIME types in OpenAI resource callback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 16, 2025

Open in StackBlitz

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@172

commit: f869e0a

ochafik and others added 7 commits December 17, 2025 13:14
The learn_threejs tool was added in #173, which adds a second option
in the Tool dropdown. This updates the golden snapshot to match.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Change _meta: null to _meta: undefined in OpenAI transport
- Register default no-op handlers for all tool notifications in App constructor

The SDK's Protocol class throws 'Unknown message type' for unhandled
notifications. Now all tool-related notifications have default handlers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Test that null _meta is converted to undefined in OpenAI transport
- Test that default no-op handlers accept tool notifications without error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Check for both null and undefined before delivering tool-result
notification. Previously null passed through and was stringified.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Handle different shapes of toolOutput from ChatGPT:
- Array of content blocks: use directly
- Single content block {type, text}: wrap in array
- Object with just {text}: extract and wrap
- Other: stringify as fallback

This prevents double-stringification when ChatGPT passes content
in different formats.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
When toolOutput contains structuredContent, include it in the
tool-result notification. Also auto-extract structuredContent
from plain objects that aren't content arrays.

This allows apps to access structured data directly without
parsing JSON from text content.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants