mcp-ui-ext-apps-openai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -0
- package/dist/chunk-MX7VCLPI.mjs +261 -0
- package/dist/chunk-MX7VCLPI.mjs.map +1 -0
- package/dist/index.d.mts +225 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +292 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +81 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +484 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +198 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# mcp-ui-ext-apps-openai
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/mcp-ui-ext-apps-openai)
|
|
4
|
+
[](https://github.com/lkm1developer/mcp-ui-ext-apps-openai/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Unified utility for building apps that work on both **OpenAI ChatGPT** and **MCP Apps** platforms.
|
|
7
|
+
|
|
8
|
+
**Author:** [lkm1developer](https://github.com/lkm1developer)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install mcp-ui-ext-apps-openai
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Peer Dependencies
|
|
17
|
+
|
|
18
|
+
This package requires the following peer dependencies:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @modelcontextprotocol/ext-apps react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### React Hook (Recommended)
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { useUnifiedApp } from "mcp-ui-ext-apps-openai/react";
|
|
30
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
31
|
+
|
|
32
|
+
// Define your structured data type
|
|
33
|
+
interface CounterData {
|
|
34
|
+
status: boolean;
|
|
35
|
+
value?: number;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function CounterApp() {
|
|
40
|
+
const {
|
|
41
|
+
app,
|
|
42
|
+
isConnected,
|
|
43
|
+
platform,
|
|
44
|
+
hostContext,
|
|
45
|
+
initialProps,
|
|
46
|
+
widgetState,
|
|
47
|
+
setWidgetState,
|
|
48
|
+
} = useUnifiedApp({
|
|
49
|
+
appInfo: { name: "Counter App", version: "1.0.0" },
|
|
50
|
+
capabilities: {},
|
|
51
|
+
onError: (e) => console.error("[CounterApp]", e),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Get counter value from widget state
|
|
55
|
+
const counter = (widgetState as { value?: number } | null)?.value ?? 0;
|
|
56
|
+
|
|
57
|
+
// Fetch counter from server
|
|
58
|
+
const fetchCounter = async () => {
|
|
59
|
+
if (!app) return;
|
|
60
|
+
|
|
61
|
+
const result = await app.callServerTool({ name: "get-counter", arguments: {} });
|
|
62
|
+
const data = (result as CallToolResult).structuredContent as unknown as CounterData;
|
|
63
|
+
|
|
64
|
+
if (data.status && data.value !== undefined) {
|
|
65
|
+
setWidgetState({ value: data.value });
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Initialize from initialProps (OpenAI) or fetch from server (MCP)
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!app || !isConnected) return;
|
|
72
|
+
|
|
73
|
+
if (initialProps !== undefined) {
|
|
74
|
+
const data = (initialProps as CallToolResult).structuredContent as unknown as CounterData;
|
|
75
|
+
if (data?.status && data.value !== undefined) {
|
|
76
|
+
setWidgetState({ value: data.value });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fetchCounter();
|
|
82
|
+
}, [app, isConnected, initialProps]);
|
|
83
|
+
|
|
84
|
+
// Increment counter
|
|
85
|
+
const handleIncrement = async () => {
|
|
86
|
+
if (!app) return;
|
|
87
|
+
|
|
88
|
+
const newValue = counter + 1;
|
|
89
|
+
setWidgetState({ value: newValue }); // Optimistic update
|
|
90
|
+
|
|
91
|
+
const result = await app.callServerTool({
|
|
92
|
+
name: "set-counter",
|
|
93
|
+
arguments: { value: newValue }
|
|
94
|
+
});
|
|
95
|
+
const data = (result as CallToolResult).structuredContent as unknown as CounterData;
|
|
96
|
+
|
|
97
|
+
if (!data.status) {
|
|
98
|
+
setWidgetState({ value: counter }); // Revert on error
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<p>Platform: {platform}</p>
|
|
105
|
+
<p>Theme: {hostContext?.theme}</p>
|
|
106
|
+
<p>Counter: {counter}</p>
|
|
107
|
+
<button onClick={handleIncrement}>+</button>
|
|
108
|
+
<button onClick={fetchCounter}>Refresh</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Core Utilities (No React)
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { detectPlatform, isOpenAI, isMCP, createUnifiedApp } from "mcp-ui-ext-apps-openai";
|
|
118
|
+
|
|
119
|
+
// Check platform
|
|
120
|
+
const platform = detectPlatform(); // "openai" | "mcp" | "unknown"
|
|
121
|
+
|
|
122
|
+
if (isOpenAI()) {
|
|
123
|
+
console.log("Running on OpenAI ChatGPT");
|
|
124
|
+
} else if (isMCP()) {
|
|
125
|
+
console.log("Running on MCP Apps");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Create unified app (for OpenAI only - use React hook for MCP)
|
|
129
|
+
const { app, isConnected, error } = createUnifiedApp({
|
|
130
|
+
appInfo: { name: "My App", version: "1.0.0" },
|
|
131
|
+
onError: console.error,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (app && isConnected) {
|
|
135
|
+
const result = await app.callServerTool({ name: "my-tool", arguments: {} });
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## API Reference
|
|
140
|
+
|
|
141
|
+
### `useUnifiedApp(options)`
|
|
142
|
+
|
|
143
|
+
React hook that provides a unified app interface for both platforms.
|
|
144
|
+
|
|
145
|
+
#### Options
|
|
146
|
+
|
|
147
|
+
| Option | Type | Description |
|
|
148
|
+
|--------|------|-------------|
|
|
149
|
+
| `appInfo` | `{ name: string; version: string }` | App information |
|
|
150
|
+
| `capabilities` | `McpUiAppCapabilities` | Optional capabilities |
|
|
151
|
+
| `onToolInput` | `(input: unknown) => void` | Called when tool input is received |
|
|
152
|
+
| `onToolResult` | `(result: UnifiedToolResult) => void` | Called when tool result is received |
|
|
153
|
+
| `onHostContextChanged` | `(context: UnifiedHostContext) => void` | Called when host context changes |
|
|
154
|
+
| `onTeardown` | `() => void` | Called when app is being torn down |
|
|
155
|
+
| `onError` | `(error: Error) => void` | Called on errors (also logs to console) |
|
|
156
|
+
|
|
157
|
+
#### Returns
|
|
158
|
+
|
|
159
|
+
| Property | Type | Description |
|
|
160
|
+
|----------|------|-------------|
|
|
161
|
+
| `app` | `UnifiedApp \| null` | The unified app instance |
|
|
162
|
+
| `isConnected` | `boolean` | Whether connected to host |
|
|
163
|
+
| `error` | `Error \| null` | Any connection error |
|
|
164
|
+
| `platform` | `"openai" \| "mcp" \| "unknown"` | Detected platform |
|
|
165
|
+
| `hostContext` | `UnifiedHostContext` | Current host context (theme, locale, etc.) |
|
|
166
|
+
| `initialProps` | `unknown` | Initial props from toolOutput (OpenAI only) |
|
|
167
|
+
| `widgetProps` | `Record<string, unknown>` | Widget props (OpenAI only) |
|
|
168
|
+
| `widgetState` | `unknown` | Widget state (works on both platforms) |
|
|
169
|
+
| `setWidgetState` | `(state: T) => void` | Set widget state |
|
|
170
|
+
| `updateWidgetState` | `(state: Partial<T>) => void` | Partially update widget state |
|
|
171
|
+
|
|
172
|
+
### `UnifiedApp` Interface
|
|
173
|
+
|
|
174
|
+
| Method | Description |
|
|
175
|
+
|--------|-------------|
|
|
176
|
+
| `callServerTool({ name, arguments })` | Call a server tool |
|
|
177
|
+
| `sendMessage(message, options?)` | Send a message to the host |
|
|
178
|
+
| `sendLog({ level, data })` | Send a log message |
|
|
179
|
+
| `openLink({ url })` | Open an external link |
|
|
180
|
+
| `getHostContext()` | Get current host context |
|
|
181
|
+
| `setWidgetState(state)` | Set widget state (OpenAI only) |
|
|
182
|
+
| `updateWidgetState(state)` | Update widget state partially (OpenAI only) |
|
|
183
|
+
| `requestDisplayMode(mode)` | Request display mode (OpenAI only) |
|
|
184
|
+
| `requestClose()` | Request to close widget (OpenAI only) |
|
|
185
|
+
| `uploadFile(file)` | Upload a file (OpenAI only) |
|
|
186
|
+
| `getFileDownloadUrl({ fileId })` | Get file download URL (OpenAI only) |
|
|
187
|
+
|
|
188
|
+
### Platform-Specific Behavior
|
|
189
|
+
|
|
190
|
+
| Feature | OpenAI | MCP |
|
|
191
|
+
|---------|--------|-----|
|
|
192
|
+
| `widgetState` | Syncs to OpenAI + React state | React state only |
|
|
193
|
+
| `initialProps` | From `toolOutput` | `undefined` |
|
|
194
|
+
| `widgetProps` | From `widget.props` | `{}` |
|
|
195
|
+
| `uploadFile` | Works | Throws error |
|
|
196
|
+
| `requestDisplayMode` | Works | No-op |
|
|
197
|
+
| `callCompletion` | Works | Throws error |
|
|
198
|
+
|
|
199
|
+
## Structured Tool Data
|
|
200
|
+
|
|
201
|
+
Tools should return structured data in `structuredContent`:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// Server tool response format
|
|
205
|
+
{
|
|
206
|
+
status: true,
|
|
207
|
+
value: 42,
|
|
208
|
+
// or on error:
|
|
209
|
+
// status: false,
|
|
210
|
+
// error: "Something went wrong"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Access in client
|
|
214
|
+
const result = await app.callServerTool({ name: "get-counter", arguments: {} });
|
|
215
|
+
const data = (result as CallToolResult).structuredContent as unknown as CounterData;
|
|
216
|
+
|
|
217
|
+
if (data.status) {
|
|
218
|
+
console.log("Value:", data.value);
|
|
219
|
+
} else {
|
|
220
|
+
console.error("Error:", data.error);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/unified-app.ts
|
|
2
|
+
function detectPlatform() {
|
|
3
|
+
if (typeof window !== "undefined" && window.openai) {
|
|
4
|
+
return "openai";
|
|
5
|
+
}
|
|
6
|
+
return "unknown";
|
|
7
|
+
}
|
|
8
|
+
function isOpenAI() {
|
|
9
|
+
return detectPlatform() === "openai";
|
|
10
|
+
}
|
|
11
|
+
function isMCP() {
|
|
12
|
+
return detectPlatform() === "mcp" || detectPlatform() === "unknown";
|
|
13
|
+
}
|
|
14
|
+
function createOpenAIAdapter(options) {
|
|
15
|
+
const openai = window.openai;
|
|
16
|
+
const handleGlobalsChange = (_event) => {
|
|
17
|
+
if (options.onHostContextChanged) {
|
|
18
|
+
options.onHostContextChanged({
|
|
19
|
+
theme: openai.theme,
|
|
20
|
+
displayMode: openai.displayMode,
|
|
21
|
+
locale: openai.locale,
|
|
22
|
+
safeAreaInsets: openai.safeArea?.insets,
|
|
23
|
+
maxWidth: openai.maxWidth,
|
|
24
|
+
maxHeight: openai.maxHeight
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
window.addEventListener("openai:set_globals", handleGlobalsChange);
|
|
29
|
+
if (options.onHostContextChanged) {
|
|
30
|
+
options.onHostContextChanged({
|
|
31
|
+
theme: openai.theme,
|
|
32
|
+
displayMode: openai.displayMode,
|
|
33
|
+
locale: openai.locale,
|
|
34
|
+
safeAreaInsets: openai.safeArea?.insets,
|
|
35
|
+
maxWidth: openai.maxWidth,
|
|
36
|
+
maxHeight: openai.maxHeight
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (options.onToolResult && openai.toolOutput) {
|
|
40
|
+
const result = convertOpenAIToolOutput(openai.toolOutput);
|
|
41
|
+
options.onToolResult(result);
|
|
42
|
+
}
|
|
43
|
+
if (options.onToolInput && openai.toolInput) {
|
|
44
|
+
options.onToolInput(openai.toolInput);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
platform: "openai",
|
|
48
|
+
callServerTool: async ({ name, arguments: args }) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = await openai.callTool(name, args);
|
|
51
|
+
return convertOpenAIToolOutput(result);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (options.onError) {
|
|
54
|
+
options.onError(error);
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
sendMessage: async (message, _opts) => {
|
|
60
|
+
try {
|
|
61
|
+
const textContent = message.content.find((c) => c.type === "text");
|
|
62
|
+
if (textContent?.text) {
|
|
63
|
+
await openai.sendFollowUpMessage({ prompt: textContent.text });
|
|
64
|
+
}
|
|
65
|
+
return { isError: false };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (options.onError) {
|
|
68
|
+
options.onError(error);
|
|
69
|
+
}
|
|
70
|
+
return { isError: true };
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
sendLog: async ({ level, data }) => {
|
|
74
|
+
const consoleLevel = level === "warning" ? "warn" : level;
|
|
75
|
+
console[consoleLevel](`[${options.appInfo.name}]`, data);
|
|
76
|
+
},
|
|
77
|
+
openLink: async ({ url }) => {
|
|
78
|
+
try {
|
|
79
|
+
await openai.openExternal({ href: url });
|
|
80
|
+
return { isError: false };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (options.onError) {
|
|
83
|
+
options.onError(error);
|
|
84
|
+
}
|
|
85
|
+
return { isError: true };
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
getHostContext: () => ({
|
|
89
|
+
theme: openai.theme,
|
|
90
|
+
displayMode: openai.displayMode,
|
|
91
|
+
locale: openai.locale,
|
|
92
|
+
safeAreaInsets: openai.safeArea?.insets,
|
|
93
|
+
maxWidth: openai.maxWidth,
|
|
94
|
+
maxHeight: openai.maxHeight
|
|
95
|
+
}),
|
|
96
|
+
getToolInput: () => openai.toolInput || {},
|
|
97
|
+
getToolOutput: () => openai.toolOutput,
|
|
98
|
+
getToolResponseMetadata: () => openai.toolResponseMetadata || {},
|
|
99
|
+
getWidgetState: () => openai.widgetState,
|
|
100
|
+
getWidgetProps: () => openai.widget?.props || {},
|
|
101
|
+
setWidgetState: (state) => {
|
|
102
|
+
openai.setWidgetState(state);
|
|
103
|
+
},
|
|
104
|
+
updateWidgetState: (state) => {
|
|
105
|
+
openai.updateWidgetState(state);
|
|
106
|
+
},
|
|
107
|
+
requestDisplayMode: async (mode) => {
|
|
108
|
+
await openai.requestDisplayMode(mode);
|
|
109
|
+
},
|
|
110
|
+
requestClose: () => {
|
|
111
|
+
openai.requestClose();
|
|
112
|
+
},
|
|
113
|
+
notifyIntrinsicHeight: (height) => {
|
|
114
|
+
openai.notifyIntrinsicHeight(height);
|
|
115
|
+
},
|
|
116
|
+
uploadFile: async (file) => {
|
|
117
|
+
return await openai.uploadFile(file);
|
|
118
|
+
},
|
|
119
|
+
getFileDownloadUrl: async ({ fileId }) => {
|
|
120
|
+
return await openai.getFileDownloadUrl({ fileId });
|
|
121
|
+
},
|
|
122
|
+
setOpenInAppUrl: ({ href }) => {
|
|
123
|
+
openai.setOpenInAppUrl({ href });
|
|
124
|
+
},
|
|
125
|
+
share: async (params) => {
|
|
126
|
+
await openai.share(params);
|
|
127
|
+
},
|
|
128
|
+
callCompletion: async (params) => {
|
|
129
|
+
return await openai.callCompletion(params);
|
|
130
|
+
},
|
|
131
|
+
streamCompletion: (params) => {
|
|
132
|
+
return openai.streamCompletion(params);
|
|
133
|
+
},
|
|
134
|
+
_raw: openai
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function convertOpenAIToolOutput(output) {
|
|
138
|
+
if (typeof output === "string") {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: output }]
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (output && typeof output === "object") {
|
|
144
|
+
if ("content" in output && Array.isArray(output.content)) {
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
147
|
+
if ("text" in output) {
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: String(output.text) }]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
content: [{ type: "text", text: JSON.stringify(output) }]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: "text", text: String(output) }]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function createMCPAdapter(app, _options) {
|
|
161
|
+
return {
|
|
162
|
+
platform: "mcp",
|
|
163
|
+
callServerTool: async ({ name, arguments: args }) => {
|
|
164
|
+
const result = await app.callServerTool({ name, arguments: args });
|
|
165
|
+
return result;
|
|
166
|
+
},
|
|
167
|
+
sendMessage: async (message, opts) => {
|
|
168
|
+
const result = await app.sendMessage(message, opts);
|
|
169
|
+
return { isError: result.isError ?? false };
|
|
170
|
+
},
|
|
171
|
+
sendLog: async ({ level, data }) => {
|
|
172
|
+
await app.sendLog({ level, data });
|
|
173
|
+
},
|
|
174
|
+
openLink: async ({ url }) => {
|
|
175
|
+
const result = await app.openLink({ url });
|
|
176
|
+
return { isError: result.isError ?? false };
|
|
177
|
+
},
|
|
178
|
+
getHostContext: () => {
|
|
179
|
+
const ctx = app.getHostContext();
|
|
180
|
+
return {
|
|
181
|
+
theme: ctx?.theme,
|
|
182
|
+
displayMode: ctx?.displayMode,
|
|
183
|
+
locale: ctx?.locale,
|
|
184
|
+
safeAreaInsets: ctx?.safeAreaInsets
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
getToolInput: () => ({}),
|
|
188
|
+
// MCP handles this via ontoolinput callback
|
|
189
|
+
getToolOutput: () => null,
|
|
190
|
+
// MCP handles this via ontoolresult callback
|
|
191
|
+
// OpenAI-specific methods - no-op or stub implementations for MCP
|
|
192
|
+
getToolResponseMetadata: () => ({}),
|
|
193
|
+
getWidgetState: () => null,
|
|
194
|
+
getWidgetProps: () => ({}),
|
|
195
|
+
setWidgetState: (_state) => {
|
|
196
|
+
},
|
|
197
|
+
updateWidgetState: (_state) => {
|
|
198
|
+
},
|
|
199
|
+
requestDisplayMode: async (_mode) => {
|
|
200
|
+
},
|
|
201
|
+
requestClose: () => {
|
|
202
|
+
},
|
|
203
|
+
notifyIntrinsicHeight: (_height) => {
|
|
204
|
+
},
|
|
205
|
+
uploadFile: async (_file) => {
|
|
206
|
+
throw new Error("uploadFile is not supported on MCP platform");
|
|
207
|
+
},
|
|
208
|
+
getFileDownloadUrl: async (_params) => {
|
|
209
|
+
throw new Error("getFileDownloadUrl is not supported on MCP platform");
|
|
210
|
+
},
|
|
211
|
+
setOpenInAppUrl: (_params) => {
|
|
212
|
+
},
|
|
213
|
+
share: async (_params) => {
|
|
214
|
+
throw new Error("share is not supported on MCP platform");
|
|
215
|
+
},
|
|
216
|
+
callCompletion: async (_params) => {
|
|
217
|
+
throw new Error("callCompletion is not supported on MCP platform");
|
|
218
|
+
},
|
|
219
|
+
streamCompletion: (_params) => {
|
|
220
|
+
throw new Error("streamCompletion is not supported on MCP platform");
|
|
221
|
+
},
|
|
222
|
+
_raw: app
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function createUnifiedApp(options) {
|
|
226
|
+
const platform = detectPlatform();
|
|
227
|
+
if (platform === "openai") {
|
|
228
|
+
try {
|
|
229
|
+
const app = createOpenAIAdapter(options);
|
|
230
|
+
return {
|
|
231
|
+
app,
|
|
232
|
+
isConnected: true,
|
|
233
|
+
error: null,
|
|
234
|
+
platform: "openai"
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
app: null,
|
|
239
|
+
isConnected: false,
|
|
240
|
+
error,
|
|
241
|
+
platform: "openai"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
app: null,
|
|
247
|
+
isConnected: false,
|
|
248
|
+
error: null,
|
|
249
|
+
platform: "unknown"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
detectPlatform,
|
|
255
|
+
isOpenAI,
|
|
256
|
+
isMCP,
|
|
257
|
+
createOpenAIAdapter,
|
|
258
|
+
createMCPAdapter,
|
|
259
|
+
createUnifiedApp
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=chunk-MX7VCLPI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/unified-app.ts"],"sourcesContent":["/**\n * @file Unified App Utility - Works with both OpenAI ChatGPT Apps and MCP Apps\n *\n * This utility provides a unified interface that abstracts away the differences\n * between OpenAI's window.openai API and MCP Apps SDK.\n */\n\nimport type { App, McpUiAppCapabilities } from \"@modelcontextprotocol/ext-apps\";\n\n// ============================================================================\n// Types & Interfaces\n// ============================================================================\n\nexport type Platform = \"openai\" | \"mcp\" | \"unknown\";\n\nexport interface UnifiedToolResult {\n content: Array<{ type: string; text?: string; [key: string]: unknown }>;\n structuredContent?: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport interface UnifiedMessage {\n role: \"user\" | \"assistant\";\n content: Array<{ type: string; text?: string; [key: string]: unknown }>;\n}\n\nexport interface UnifiedHostContext {\n theme?: \"light\" | \"dark\";\n displayMode?: \"inline\" | \"pip\" | \"fullscreen\";\n locale?: string;\n safeAreaInsets?: {\n top?: number;\n right?: number;\n bottom?: number;\n left?: number;\n };\n maxWidth?: number;\n maxHeight?: number;\n}\n\nexport interface UnifiedAppOptions {\n appInfo: { name: string; version: string };\n capabilities?: McpUiAppCapabilities;\n onToolInput?: (input: unknown) => void | Promise<void>;\n onToolResult?: (result: UnifiedToolResult) => void | Promise<void>;\n onHostContextChanged?: (context: UnifiedHostContext) => void;\n onTeardown?: () => void | Promise<void>;\n onError?: (error: Error) => void;\n}\n\nexport interface UnifiedApp {\n /** The underlying platform */\n platform: Platform;\n\n /** Call a server tool by name with arguments */\n callServerTool: (params: { name: string; arguments: Record<string, unknown> }) => Promise<UnifiedToolResult>;\n\n /** Send a message to the host/conversation */\n sendMessage: (message: UnifiedMessage, options?: { signal?: AbortSignal }) => Promise<{ isError: boolean }>;\n\n /** Send a log message to the host */\n sendLog: (params: { level: \"info\" | \"warning\" | \"error\" | \"debug\"; data: string }) => Promise<void>;\n\n /** Open an external link */\n openLink: (params: { url: string }) => Promise<{ isError: boolean }>;\n\n /** Get the current host context */\n getHostContext: () => UnifiedHostContext;\n\n /** Get tool input (initial arguments passed to the tool) */\n getToolInput: () => Record<string, unknown>;\n\n /** Get tool output (result from server) */\n getToolOutput: () => unknown;\n\n /** Get tool response metadata (OpenAI only, returns {} on MCP) */\n getToolResponseMetadata: () => Record<string, unknown>;\n\n /** Get widget state (OpenAI only, returns null on MCP) */\n getWidgetState: <T = unknown>() => T | null;\n\n /** Get widget props (OpenAI only, returns {} on MCP) */\n getWidgetProps: <T = Record<string, unknown>>() => T;\n\n /** Set widget state - persists across renders (OpenAI only, no-op on MCP) */\n setWidgetState: <T = unknown>(state: T) => void;\n\n /** Update widget state - partial update (OpenAI only, no-op on MCP) */\n updateWidgetState: <T = unknown>(state: Partial<T>) => void;\n\n /** Request a specific display mode (OpenAI only, no-op on MCP) */\n requestDisplayMode: (mode: \"inline\" | \"pip\" | \"fullscreen\") => Promise<void>;\n\n /** Request to close the widget (OpenAI only, no-op on MCP) */\n requestClose: () => void;\n\n /** Notify intrinsic height for dynamic sizing (OpenAI only, no-op on MCP) */\n notifyIntrinsicHeight: (height: number) => void;\n\n /** Upload a file (OpenAI only, throws on MCP) */\n uploadFile: (file: File) => Promise<{ fileId: string }>;\n\n /** Get file download URL (OpenAI only, throws on MCP) */\n getFileDownloadUrl: (params: { fileId: string }) => Promise<string>;\n\n /** Set URL for \"Open in App\" button (OpenAI only, no-op on MCP) */\n setOpenInAppUrl: (params: { href: string }) => void;\n\n /** Share content (OpenAI only, throws on MCP) */\n share: (params: unknown) => Promise<void>;\n\n /** Call AI completion (OpenAI only, throws on MCP) */\n callCompletion: (params: unknown) => Promise<unknown>;\n\n /** Stream AI completion (OpenAI only, throws on MCP) */\n streamCompletion: (params: unknown) => AsyncIterable<unknown>;\n\n /** The raw underlying app instance */\n _raw: App | OpenAIGlobal | null;\n}\n\n// OpenAI window.openai type definition based on documentation\nexport interface OpenAIGlobal {\n callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;\n sendFollowUpMessage: (params: { prompt: string }) => Promise<void>;\n openExternal: (params: { href: string }) => Promise<void>;\n setOpenInAppUrl: (params: { href: string }) => void;\n requestDisplayMode: (mode: string) => Promise<void>;\n requestModal: (params: unknown) => Promise<void>;\n requestClose: () => void;\n notifyIntrinsicHeight: (height: number) => void;\n uploadFile: (file: File) => Promise<{ fileId: string }>;\n getFileDownloadUrl: (params: { fileId: string }) => Promise<string>;\n getFileMetadata: (params: { fileId: string }) => Promise<unknown>;\n setWidgetState: (state: unknown) => void;\n updateWidgetState: (state: unknown) => void;\n share: (params: unknown) => Promise<void>;\n streamCompletion: (params: unknown) => AsyncIterable<unknown>;\n callCompletion: (params: unknown) => Promise<unknown>;\n\n // Properties\n toolInput: Record<string, unknown>;\n toolOutput: unknown;\n toolResponseMetadata: Record<string, unknown>;\n widgetState: unknown;\n theme: \"light\" | \"dark\";\n displayMode: \"inline\" | \"pip\" | \"fullscreen\";\n locale: string;\n maxWidth?: number;\n maxHeight?: number;\n safeArea: { insets: { top: number; right: number; bottom: number; left: number } };\n userAgent: { device: unknown; capabilities: unknown };\n view: { params: unknown; mode: string };\n widget: { state: unknown; props: unknown; setState: (state: unknown) => void };\n subjectId: string;\n}\n\n/** Widget props from OpenAI (passed to widget via widget.props) */\nexport interface OpenAIWidgetProps {\n [key: string]: unknown;\n}\n\ndeclare global {\n interface Window {\n openai?: OpenAIGlobal;\n }\n}\n\n// ============================================================================\n// Platform Detection\n// ============================================================================\n\n/**\n * Detect which platform the app is running on\n */\nexport function detectPlatform(): Platform {\n if (typeof window !== \"undefined\" && window.openai) {\n return \"openai\";\n }\n // MCP apps are detected by the useApp hook connecting successfully\n // For now, return \"unknown\" and let the hook determine\n return \"unknown\";\n}\n\n/**\n * Check if running in OpenAI ChatGPT environment\n */\nexport function isOpenAI(): boolean {\n return detectPlatform() === \"openai\";\n}\n\n/**\n * Check if running in MCP Apps environment\n */\nexport function isMCP(): boolean {\n return detectPlatform() === \"mcp\" || detectPlatform() === \"unknown\";\n}\n\n// ============================================================================\n// OpenAI Adapter\n// ============================================================================\n\n/**\n * Create a UnifiedApp from OpenAI's window.openai\n */\nexport function createOpenAIAdapter(options: UnifiedAppOptions): UnifiedApp {\n const openai = window.openai!;\n\n // Set up event listeners for OpenAI\n const handleGlobalsChange = (_event: Event) => {\n if (options.onHostContextChanged) {\n options.onHostContextChanged({\n theme: openai.theme,\n displayMode: openai.displayMode as UnifiedHostContext[\"displayMode\"],\n locale: openai.locale,\n safeAreaInsets: openai.safeArea?.insets,\n maxWidth: openai.maxWidth,\n maxHeight: openai.maxHeight,\n });\n }\n };\n\n window.addEventListener(\"openai:set_globals\", handleGlobalsChange);\n\n // Initial context callback\n if (options.onHostContextChanged) {\n options.onHostContextChanged({\n theme: openai.theme,\n displayMode: openai.displayMode as UnifiedHostContext[\"displayMode\"],\n locale: openai.locale,\n safeAreaInsets: openai.safeArea?.insets,\n maxWidth: openai.maxWidth,\n maxHeight: openai.maxHeight,\n });\n }\n\n // If there's initial tool output, notify\n if (options.onToolResult && openai.toolOutput) {\n const result = convertOpenAIToolOutput(openai.toolOutput);\n options.onToolResult(result);\n }\n\n if (options.onToolInput && openai.toolInput) {\n options.onToolInput(openai.toolInput);\n }\n\n return {\n platform: \"openai\",\n\n callServerTool: async ({ name, arguments: args }) => {\n try {\n const result = await openai.callTool(name, args);\n return convertOpenAIToolOutput(result);\n } catch (error) {\n if (options.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n },\n\n sendMessage: async (message, _opts) => {\n try {\n // OpenAI uses sendFollowUpMessage for user messages\n const textContent = message.content.find(c => c.type === \"text\");\n if (textContent?.text) {\n await openai.sendFollowUpMessage({ prompt: textContent.text });\n }\n return { isError: false };\n } catch (error) {\n if (options.onError) {\n options.onError(error as Error);\n }\n return { isError: true };\n }\n },\n\n sendLog: async ({ level, data }) => {\n // OpenAI doesn't have a direct log API, use console as fallback\n // Map 'warning' to 'warn' for console compatibility\n const consoleLevel = level === \"warning\" ? \"warn\" : level;\n console[consoleLevel](`[${options.appInfo.name}]`, data);\n },\n\n openLink: async ({ url }) => {\n try {\n await openai.openExternal({ href: url });\n return { isError: false };\n } catch (error) {\n if (options.onError) {\n options.onError(error as Error);\n }\n return { isError: true };\n }\n },\n\n getHostContext: () => ({\n theme: openai.theme,\n displayMode: openai.displayMode as UnifiedHostContext[\"displayMode\"],\n locale: openai.locale,\n safeAreaInsets: openai.safeArea?.insets,\n maxWidth: openai.maxWidth,\n maxHeight: openai.maxHeight,\n }),\n\n getToolInput: () => openai.toolInput || {},\n\n getToolOutput: () => openai.toolOutput,\n\n getToolResponseMetadata: () => openai.toolResponseMetadata || {},\n\n getWidgetState: <T = unknown>() => openai.widgetState as T | null,\n\n getWidgetProps: <T = Record<string, unknown>>() => (openai.widget?.props || {}) as T,\n\n setWidgetState: <T = unknown>(state: T) => {\n openai.setWidgetState(state);\n },\n\n updateWidgetState: <T = unknown>(state: Partial<T>) => {\n openai.updateWidgetState(state);\n },\n\n requestDisplayMode: async (mode) => {\n await openai.requestDisplayMode(mode);\n },\n\n requestClose: () => {\n openai.requestClose();\n },\n\n notifyIntrinsicHeight: (height) => {\n openai.notifyIntrinsicHeight(height);\n },\n\n uploadFile: async (file) => {\n return await openai.uploadFile(file);\n },\n\n getFileDownloadUrl: async ({ fileId }) => {\n return await openai.getFileDownloadUrl({ fileId });\n },\n\n setOpenInAppUrl: ({ href }) => {\n openai.setOpenInAppUrl({ href });\n },\n\n share: async (params) => {\n await openai.share(params);\n },\n\n callCompletion: async (params) => {\n return await openai.callCompletion(params);\n },\n\n streamCompletion: (params) => {\n return openai.streamCompletion(params);\n },\n\n _raw: openai,\n };\n}\n\n/**\n * Convert OpenAI tool output to unified format\n */\nfunction convertOpenAIToolOutput(output: unknown): UnifiedToolResult {\n // OpenAI tool output can be various formats\n if (typeof output === \"string\") {\n return {\n content: [{ type: \"text\", text: output }],\n };\n }\n\n if (output && typeof output === \"object\") {\n // Check if it's already in MCP-like format\n if (\"content\" in output && Array.isArray((output as { content: unknown }).content)) {\n return output as UnifiedToolResult;\n }\n\n // Check for text property\n if (\"text\" in output) {\n return {\n content: [{ type: \"text\", text: String((output as { text: unknown }).text) }],\n };\n }\n\n // Serialize object as JSON text\n return {\n content: [{ type: \"text\", text: JSON.stringify(output) }],\n };\n }\n\n return {\n content: [{ type: \"text\", text: String(output) }],\n };\n}\n\n// ============================================================================\n// MCP Adapter\n// ============================================================================\n\n/**\n * Create a UnifiedApp from MCP App instance\n */\nexport function createMCPAdapter(app: App, _options: UnifiedAppOptions): UnifiedApp {\n return {\n platform: \"mcp\",\n\n callServerTool: async ({ name, arguments: args }) => {\n const result = await app.callServerTool({ name, arguments: args });\n return result as UnifiedToolResult;\n },\n\n sendMessage: async (message, opts) => {\n const result = await app.sendMessage(message as Parameters<typeof app.sendMessage>[0], opts);\n return { isError: result.isError ?? false };\n },\n\n sendLog: async ({ level, data }) => {\n await app.sendLog({ level, data });\n },\n\n openLink: async ({ url }) => {\n const result = await app.openLink({ url });\n return { isError: result.isError ?? false };\n },\n\n getHostContext: () => {\n const ctx = app.getHostContext();\n return {\n theme: ctx?.theme as UnifiedHostContext[\"theme\"],\n displayMode: ctx?.displayMode as UnifiedHostContext[\"displayMode\"],\n locale: ctx?.locale,\n safeAreaInsets: ctx?.safeAreaInsets,\n };\n },\n\n getToolInput: () => ({}), // MCP handles this via ontoolinput callback\n\n getToolOutput: () => null, // MCP handles this via ontoolresult callback\n\n // OpenAI-specific methods - no-op or stub implementations for MCP\n getToolResponseMetadata: () => ({}),\n\n getWidgetState: <T = unknown>() => null as T | null,\n\n getWidgetProps: <T = Record<string, unknown>>() => ({} as T),\n\n setWidgetState: <T = unknown>(_state: T) => {\n // No-op on MCP\n },\n\n updateWidgetState: <T = unknown>(_state: Partial<T>) => {\n // No-op on MCP\n },\n\n requestDisplayMode: async (_mode) => {\n // No-op on MCP\n },\n\n requestClose: () => {\n // No-op on MCP\n },\n\n notifyIntrinsicHeight: (_height) => {\n // No-op on MCP\n },\n\n uploadFile: async (_file) => {\n throw new Error(\"uploadFile is not supported on MCP platform\");\n },\n\n getFileDownloadUrl: async (_params) => {\n throw new Error(\"getFileDownloadUrl is not supported on MCP platform\");\n },\n\n setOpenInAppUrl: (_params) => {\n // No-op on MCP\n },\n\n share: async (_params) => {\n throw new Error(\"share is not supported on MCP platform\");\n },\n\n callCompletion: async (_params) => {\n throw new Error(\"callCompletion is not supported on MCP platform\");\n },\n\n streamCompletion: (_params) => {\n throw new Error(\"streamCompletion is not supported on MCP platform\");\n },\n\n _raw: app,\n };\n}\n\n// ============================================================================\n// Unified Factory\n// ============================================================================\n\nexport interface CreateUnifiedAppResult {\n app: UnifiedApp | null;\n isConnected: boolean;\n error: Error | null;\n platform: Platform;\n}\n\n/**\n * Create a unified app - automatically detects platform and creates appropriate adapter\n *\n * For OpenAI: Returns immediately with the adapter\n * For MCP: Returns null app (use the React hook instead for MCP)\n */\nexport function createUnifiedApp(options: UnifiedAppOptions): CreateUnifiedAppResult {\n const platform = detectPlatform();\n\n if (platform === \"openai\") {\n try {\n const app = createOpenAIAdapter(options);\n return {\n app,\n isConnected: true,\n error: null,\n platform: \"openai\",\n };\n } catch (error) {\n return {\n app: null,\n isConnected: false,\n error: error as Error,\n platform: \"openai\",\n };\n }\n }\n\n // For MCP, return null - the React hook will handle connection\n return {\n app: null,\n isConnected: false,\n error: null,\n platform: \"unknown\",\n };\n}\n"],"mappings":"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","names":[]}
|