Skip to content

Commit 95679a2

Browse files
ochafikclaude
andcommitted
feat: experimental OpenAI Apps SDK compatibility
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]>
1 parent 894f793 commit 95679a2

File tree

5 files changed

+1243
-38
lines changed

5 files changed

+1243
-38
lines changed

src/app.ts

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
PingRequestSchema,
1717
} from "@modelcontextprotocol/sdk/types.js";
1818
import { AppNotification, AppRequest, AppResult } from "./types";
19-
import { PostMessageTransport } from "./message-transport";
2019
import {
2120
LATEST_PROTOCOL_VERSION,
2221
McpUiAppCapabilities,
@@ -47,8 +46,12 @@ import {
4746
McpUiRequestDisplayModeResultSchema,
4847
} from "./types";
4948
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
49+
import { PostMessageTransport } from "./message-transport";
50+
import { OpenAITransport, isOpenAIEnvironment } from "./openai/transport.js";
5051

5152
export { PostMessageTransport } from "./message-transport";
53+
export { OpenAITransport, isOpenAIEnvironment } from "./openai/transport";
54+
export * from "./openai/types";
5255
export * from "./types";
5356
export {
5457
applyHostStyleVariables,
@@ -100,7 +103,7 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
100103
*
101104
* @see ProtocolOptions from @modelcontextprotocol/sdk for inherited options
102105
*/
103-
type AppOptions = ProtocolOptions & {
106+
export type AppOptions = ProtocolOptions & {
104107
/**
105108
* Automatically report size changes to the host using ResizeObserver.
106109
*
@@ -111,6 +114,19 @@ type AppOptions = ProtocolOptions & {
111114
* @default true
112115
*/
113116
autoResize?: boolean;
117+
118+
/**
119+
* Enable experimental OpenAI compatibility.
120+
*
121+
* When enabled (default), the App will auto-detect the environment:
122+
* - If `window.openai` exists → use OpenAI Apps SDK
123+
* - Otherwise → use MCP Apps protocol via PostMessageTransport
124+
*
125+
* Set to `false` to force MCP-only mode.
126+
*
127+
* @default true
128+
*/
129+
experimentalOAICompatibility?: boolean;
114130
};
115131

116132
type RequestHandlerExtra = Parameters<
@@ -219,7 +235,10 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
219235
constructor(
220236
private _appInfo: Implementation,
221237
private _capabilities: McpUiAppCapabilities = {},
222-
private options: AppOptions = { autoResize: true },
238+
private options: AppOptions = {
239+
autoResize: true,
240+
experimentalOAICompatibility: true,
241+
},
223242
) {
224243
super(options);
225244

@@ -988,47 +1007,73 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
9881007
return () => resizeObserver.disconnect();
9891008
}
9901009

1010+
/**
1011+
* Create the default transport based on detected platform.
1012+
* @internal
1013+
*/
1014+
private createDefaultTransport(): Transport {
1015+
const experimentalOAI = this.options?.experimentalOAICompatibility ?? true;
1016+
if (experimentalOAI && isOpenAIEnvironment()) {
1017+
return new OpenAITransport();
1018+
}
1019+
return new PostMessageTransport(window.parent);
1020+
}
1021+
9911022
/**
9921023
* Establish connection with the host and perform initialization handshake.
9931024
*
9941025
* This method performs the following steps:
995-
* 1. Connects the transport layer
996-
* 2. Sends `ui/initialize` request with app info and capabilities
997-
* 3. Receives host capabilities and context in response
998-
* 4. Sends `ui/notifications/initialized` notification
999-
* 5. Sets up auto-resize using {@link setupSizeChangedNotifications} if enabled (default)
1026+
* 1. Auto-detects platform if no transport is provided
1027+
* 2. Connects the transport layer
1028+
* 3. Sends `ui/initialize` request with app info and capabilities
1029+
* 4. Receives host capabilities and context in response
1030+
* 5. Sends `ui/notifications/initialized` notification
1031+
* 6. Sets up auto-resize using {@link setupSizeChangedNotifications} if enabled (default)
1032+
* 7. For OpenAI mode: delivers initial tool input/result from window.openai
10001033
*
10011034
* If initialization fails, the connection is automatically closed and an error
10021035
* is thrown.
10031036
*
1004-
* @param transport - Transport layer (typically PostMessageTransport)
1037+
* @param transport - Optional transport layer. If not provided, auto-detects
1038+
* based on the `platform` option:
1039+
* - `'openai'` or `window.openai` exists → uses {@link OpenAITransport}
1040+
* - `'mcp'` or no `window.openai` → uses {@link PostMessageTransport}
10051041
* @param options - Request options for the initialize request
10061042
*
10071043
* @throws {Error} If initialization fails or connection is lost
10081044
*
1009-
* @example Connect with PostMessageTransport
1045+
* @example Auto-detect platform (recommended)
10101046
* ```typescript
10111047
* const app = new App(
10121048
* { name: "MyApp", version: "1.0.0" },
10131049
* {}
10141050
* );
10151051
*
1016-
* try {
1017-
* await app.connect(new PostMessageTransport(window.parent));
1018-
* console.log("Connected successfully!");
1019-
* } catch (error) {
1020-
* console.error("Failed to connect:", error);
1021-
* }
1052+
* // Auto-detects: OpenAI if window.openai exists, MCP otherwise
1053+
* await app.connect();
1054+
* ```
1055+
*
1056+
* @example Explicit MCP transport
1057+
* ```typescript
1058+
* await app.connect(new PostMessageTransport(window.parent));
1059+
* ```
1060+
*
1061+
* @example Explicit OpenAI transport
1062+
* ```typescript
1063+
* await app.connect(new OpenAITransport());
10221064
* ```
10231065
*
10241066
* @see {@link McpUiInitializeRequest} for the initialization request structure
10251067
* @see {@link McpUiInitializedNotification} for the initialized notification
1026-
* @see {@link PostMessageTransport} for the typical transport implementation
1068+
* @see {@link PostMessageTransport} for MCP-compatible hosts
1069+
* @see {@link OpenAITransport} for OpenAI/ChatGPT hosts
10271070
*/
10281071
override async connect(
1029-
transport: Transport = new PostMessageTransport(window.parent),
1072+
transport?: Transport,
10301073
options?: RequestOptions,
10311074
): Promise<void> {
1075+
transport ??= this.createDefaultTransport();
1076+
10321077
await super.connect(transport);
10331078

10341079
try {
@@ -1060,6 +1105,11 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
10601105
if (this.options?.autoResize) {
10611106
this.setupSizeChangedNotifications();
10621107
}
1108+
1109+
// For OpenAI mode: deliver initial state from window.openai
1110+
if (transport instanceof OpenAITransport) {
1111+
transport.deliverInitialState();
1112+
}
10631113
} catch (error) {
10641114
// Disconnect if initialization fails.
10651115
void this.close();

0 commit comments

Comments
 (0)