mcp-ui-ext-apps-openai 1.0.2 → 1.0.3
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/dist/react.d.mts +1 -3
- package/dist/react.d.ts +1 -3
- package/dist/react.js +10 -7
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +10 -7
- package/dist/react.mjs.map +1 -1
- package/package.json +13 -2
package/dist/react.d.mts
CHANGED
|
@@ -32,11 +32,9 @@ interface UseUnifiedAppResult {
|
|
|
32
32
|
platform: Platform;
|
|
33
33
|
/** Current host context (theme, displayMode, locale, etc.) */
|
|
34
34
|
hostContext: UnifiedHostContext | undefined;
|
|
35
|
-
/** Initial props from toolOutput (OpenAI only, undefined on MCP) - use as initial data for component */
|
|
36
|
-
initialProps: unknown;
|
|
37
35
|
/** Widget props (OpenAI only, reactive) */
|
|
38
36
|
widgetProps: Record<string, unknown>;
|
|
39
|
-
/** Widget state - works on both platforms via React state */
|
|
37
|
+
/** Widget state - initialized from toolOutput on mount, works on both platforms via React state */
|
|
40
38
|
widgetState: unknown;
|
|
41
39
|
/** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */
|
|
42
40
|
setWidgetState: <T = unknown>(state: T) => void;
|
package/dist/react.d.ts
CHANGED
|
@@ -32,11 +32,9 @@ interface UseUnifiedAppResult {
|
|
|
32
32
|
platform: Platform;
|
|
33
33
|
/** Current host context (theme, displayMode, locale, etc.) */
|
|
34
34
|
hostContext: UnifiedHostContext | undefined;
|
|
35
|
-
/** Initial props from toolOutput (OpenAI only, undefined on MCP) - use as initial data for component */
|
|
36
|
-
initialProps: unknown;
|
|
37
35
|
/** Widget props (OpenAI only, reactive) */
|
|
38
36
|
widgetProps: Record<string, unknown>;
|
|
39
|
-
/** Widget state - works on both platforms via React state */
|
|
37
|
+
/** Widget state - initialized from toolOutput on mount, works on both platforms via React state */
|
|
40
38
|
widgetState: unknown;
|
|
41
39
|
/** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */
|
|
42
40
|
setWidgetState: <T = unknown>(state: T) => void;
|
package/dist/react.js
CHANGED
|
@@ -339,6 +339,13 @@ function useUnifiedApp(options) {
|
|
|
339
339
|
const openaiLocale = useOpenAIGlobal("locale", "en-US");
|
|
340
340
|
const optionsRef = (0, import_react.useRef)(options);
|
|
341
341
|
optionsRef.current = options;
|
|
342
|
+
const hasInitialized = (0, import_react.useRef)(false);
|
|
343
|
+
(0, import_react.useEffect)(() => {
|
|
344
|
+
if (platform === "openai" && !hasInitialized.current && openaiToolOutput !== void 0) {
|
|
345
|
+
setLocalWidgetState(openaiToolOutput);
|
|
346
|
+
hasInitialized.current = true;
|
|
347
|
+
}
|
|
348
|
+
}, [platform, openaiToolOutput]);
|
|
342
349
|
(0, import_react.useEffect)(() => {
|
|
343
350
|
if (platform === "openai" && openaiWidgetState !== null) {
|
|
344
351
|
if (!isOurUpdateRef.current) {
|
|
@@ -423,10 +430,6 @@ function useUnifiedApp(options) {
|
|
|
423
430
|
mcpApp.ontoolresult = async (result) => {
|
|
424
431
|
await opts.onToolResult?.(result);
|
|
425
432
|
};
|
|
426
|
-
mcpApp.onerror = (err) => {
|
|
427
|
-
console.error(`[${opts.appInfo.name}]`, err);
|
|
428
|
-
opts.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
429
|
-
};
|
|
430
433
|
mcpApp.onhostcontextchanged = (params) => {
|
|
431
434
|
if (mounted) {
|
|
432
435
|
setHostContext((prev) => {
|
|
@@ -453,10 +456,12 @@ function useUnifiedApp(options) {
|
|
|
453
456
|
}
|
|
454
457
|
} catch (err) {
|
|
455
458
|
console.error(`[${opts.appInfo.name}]`, err);
|
|
459
|
+
const error2 = err instanceof Error ? err : new Error("Failed to connect");
|
|
456
460
|
if (mounted) {
|
|
457
|
-
setError(
|
|
461
|
+
setError(error2);
|
|
458
462
|
setIsConnected(false);
|
|
459
463
|
}
|
|
464
|
+
opts.onError?.(error2);
|
|
460
465
|
}
|
|
461
466
|
}
|
|
462
467
|
}
|
|
@@ -471,14 +476,12 @@ function useUnifiedApp(options) {
|
|
|
471
476
|
return "unknown";
|
|
472
477
|
}, [platform, isConnected]);
|
|
473
478
|
const widgetProps = platform === "openai" ? openaiWidgetProps?.props || {} : {};
|
|
474
|
-
const initialProps = platform === "openai" ? openaiToolOutput : void 0;
|
|
475
479
|
return {
|
|
476
480
|
app: unifiedApp,
|
|
477
481
|
isConnected,
|
|
478
482
|
error,
|
|
479
483
|
platform: finalPlatform,
|
|
480
484
|
hostContext,
|
|
481
|
-
initialProps,
|
|
482
485
|
widgetProps,
|
|
483
486
|
widgetState: localWidgetState,
|
|
484
487
|
setWidgetState,
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.ts","../src/unified-app.ts","../src/use-unified-app.ts"],"sourcesContent":["/**\n * @file React bindings for mcp-ui-ext-apps-openai\n *\n * @example\n * ```tsx\n * import { useUnifiedApp } from \"mcp-ui-ext-apps-openai/react\";\n *\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Platform: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>+</button>\n * </div>\n * );\n * }\n * ```\n */\n\n// Re-export everything from core\nexport * from \"./index\";\n\n// React hook\nexport {\n useUnifiedApp,\n type UseUnifiedAppOptions,\n type UseUnifiedAppResult,\n} from \"./use-unified-app\";\n","/**\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","/**\n * @file React hook for unified OpenAI/MCP app usage\n *\n * This hook provides a unified interface for apps that need to run on both\n * OpenAI ChatGPT and MCP Apps platforms.\n */\n\nimport type { App, McpUiAppCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { PostMessageTransport } from \"@modelcontextprotocol/ext-apps\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n createMCPAdapter,\n createOpenAIAdapter,\n detectPlatform,\n type Platform,\n type UnifiedApp,\n type UnifiedHostContext,\n type UnifiedToolResult,\n} from \"./unified-app\";\n\nexport interface UseUnifiedAppOptions {\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 UseUnifiedAppResult {\n /** The unified app instance */\n app: UnifiedApp | null;\n /** Whether the app is connected to the host */\n isConnected: boolean;\n /** Any connection error */\n error: Error | null;\n /** The detected platform */\n platform: Platform;\n /** Current host context (theme, displayMode, locale, etc.) */\n hostContext: UnifiedHostContext | undefined;\n /** Initial props from toolOutput (OpenAI only, undefined on MCP) - use as initial data for component */\n initialProps: unknown;\n /** Widget props (OpenAI only, reactive) */\n widgetProps: Record<string, unknown>;\n /** Widget state - works on both platforms via React state */\n widgetState: unknown;\n /** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */\n setWidgetState: <T = unknown>(state: T) => void;\n /** Update widget state partially - works on both platforms */\n updateWidgetState: <T = unknown>(state: Partial<T>) => void;\n}\n\n/**\n * Hook to subscribe to OpenAI global property changes\n * Listens for 'openai:set_globals' events\n */\nfunction useOpenAIGlobal<T>(key: string, initialValue: T): T {\n const [value, setValue] = useState<T>(initialValue);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.openai) return;\n\n // Get initial value - cast to unknown first then to Record\n const openai = window.openai as unknown as Record<string, unknown>;\n if (key in openai) {\n setValue(openai[key] as T);\n }\n\n // Listen for changes\n const handleChange = () => {\n const currentOpenai = window.openai as unknown as Record<string, unknown>;\n if (currentOpenai && key in currentOpenai) {\n setValue(currentOpenai[key] as T);\n }\n };\n\n window.addEventListener(\"openai:set_globals\", handleChange);\n return () => window.removeEventListener(\"openai:set_globals\", handleChange);\n }, [key]);\n\n return value;\n}\n\n/**\n * React hook that provides a unified app interface for both OpenAI and MCP platforms\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * capabilities: {},\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Running on: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useUnifiedApp(options: UseUnifiedAppOptions): UseUnifiedAppResult {\n // Detect platform once on mount\n const platformRef = useRef<Platform>(detectPlatform());\n const platform = platformRef.current;\n\n const [unifiedApp, setUnifiedApp] = useState<UnifiedApp | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [hostContext, setHostContext] = useState<UnifiedHostContext | undefined>();\n\n // React state for widgetState - works on both platforms\n // Initialize from OpenAI's toolOutput (initialProps) if available\n const [localWidgetState, setLocalWidgetState] = useState<unknown>(() => {\n if (platform === \"openai\" && typeof window !== \"undefined\" && window.openai) {\n const openai = window.openai as unknown as Record<string, unknown>;\n return openai.toolOutput ?? openai.widgetState ?? null;\n }\n return null;\n });\n\n // Track if we set widget state ourselves (to prevent sync loop)\n const isOurUpdateRef = useRef(false);\n\n // Store unifiedApp in ref for stable callbacks\n const unifiedAppRef = useRef<UnifiedApp | null>(null);\n unifiedAppRef.current = unifiedApp;\n\n // OpenAI reactive globals\n const openaiWidgetState = useOpenAIGlobal<unknown>(\"widgetState\", null);\n const openaiWidgetProps = useOpenAIGlobal<Record<string, unknown>>(\"widget\", {});\n const openaiToolOutput = useOpenAIGlobal<unknown>(\"toolOutput\", undefined);\n const openaiTheme = useOpenAIGlobal<\"light\" | \"dark\">(\"theme\", \"light\");\n const openaiDisplayMode = useOpenAIGlobal<string>(\"displayMode\", \"inline\");\n const openaiLocale = useOpenAIGlobal<string>(\"locale\", \"en-US\");\n\n // Store options in ref to avoid stale closures\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Sync OpenAI widgetState to local state when it changes (only if not caused by us)\n useEffect(() => {\n if (platform === \"openai\" && openaiWidgetState !== null) {\n if (!isOurUpdateRef.current) {\n setLocalWidgetState(openaiWidgetState);\n }\n isOurUpdateRef.current = false;\n }\n }, [platform, openaiWidgetState]);\n\n // Widget state setters - stable callbacks using refs\n const setWidgetState = useCallback(<T = unknown>(state: T) => {\n setLocalWidgetState(state);\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.setWidgetState(state);\n }\n }, [platform]);\n\n const updateWidgetState = useCallback(<T = unknown>(state: Partial<T>) => {\n setLocalWidgetState((prev: unknown) => ({ ...(prev as object), ...state }));\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.updateWidgetState(state);\n }\n }, [platform]);\n\n // Update host context from OpenAI globals\n useEffect(() => {\n if (platform === \"openai\" && isConnected) {\n setHostContext({\n theme: openaiTheme,\n displayMode: openaiDisplayMode as UnifiedHostContext[\"displayMode\"],\n locale: openaiLocale,\n safeAreaInsets: window.openai?.safeArea?.insets,\n maxWidth: window.openai?.maxWidth,\n maxHeight: window.openai?.maxHeight,\n });\n }\n }, [platform, isConnected, openaiTheme, openaiDisplayMode, openaiLocale]);\n\n useEffect(() => {\n let mounted = true;\n let mcpApp: App | null = null;\n\n async function initialize() {\n const opts = optionsRef.current;\n\n if (platform === \"openai\") {\n // OpenAI platform - use the adapter directly\n try {\n const adapter = createOpenAIAdapter({\n ...opts,\n onToolResult: (result) => {\n opts.onToolResult?.(result);\n },\n onHostContextChanged: (ctx) => {\n if (mounted) {\n setHostContext(ctx);\n opts.onHostContextChanged?.(ctx);\n }\n },\n onError: (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err);\n },\n });\n\n if (mounted) {\n setUnifiedApp(adapter);\n setIsConnected(true);\n setHostContext(adapter.getHostContext());\n }\n } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err as Error);\n setIsConnected(false);\n }\n }\n } else {\n // MCP platform - need to dynamically import and create connection\n try {\n // Dynamic import to avoid issues when running on OpenAI\n const { App } = await import(\"@modelcontextprotocol/ext-apps\");\n\n const transport = new PostMessageTransport(window.parent, window.parent);\n mcpApp = new App(opts.appInfo, opts.capabilities || {});\n\n // Set up handlers before connecting\n mcpApp.onteardown = async () => {\n await opts.onTeardown?.();\n return {};\n };\n\n mcpApp.ontoolinput = async (input) => {\n await opts.onToolInput?.(input);\n };\n\n mcpApp.ontoolresult = async (result) => {\n await opts.onToolResult?.(result as UnifiedToolResult);\n };\n\n mcpApp.onerror = (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err instanceof Error ? err : new Error(String(err)));\n };\n\n mcpApp.onhostcontextchanged = (params) => {\n if (mounted) {\n setHostContext((prev) => {\n const newCtx = { ...prev, ...params } as UnifiedHostContext;\n opts.onHostContextChanged?.(newCtx);\n return newCtx;\n });\n }\n };\n\n await mcpApp.connect(transport);\n\n if (mounted) {\n const adapter = createMCPAdapter(mcpApp, opts);\n setUnifiedApp(adapter);\n setIsConnected(true);\n\n // Get initial host context\n const ctx = mcpApp.getHostContext();\n if (ctx) {\n setHostContext({\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 } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err instanceof Error ? err : new Error(\"Failed to connect\"));\n setIsConnected(false);\n }\n }\n }\n }\n\n initialize();\n\n return () => {\n mounted = false;\n // Note: MCP App cleanup would go here if needed\n };\n }, [platform]);\n\n // Determine final platform based on connection state\n const finalPlatform: Platform = useMemo(() => {\n if (platform === \"openai\") return \"openai\";\n if (isConnected) return \"mcp\";\n return \"unknown\";\n }, [platform, isConnected]);\n\n // Return platform-appropriate values\n const widgetProps = platform === \"openai\" ? ((openaiWidgetProps as { props?: Record<string, unknown> })?.props || {}) : {};\n const initialProps = platform === \"openai\" ? openaiToolOutput : undefined;\n\n return {\n app: unifiedApp,\n isConnected,\n error,\n platform: finalPlatform,\n hostContext,\n initialProps,\n widgetProps,\n widgetState: localWidgetState,\n setWidgetState,\n updateWidgetState,\n };\n}\n\nexport default useUnifiedApp;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/unified-app.ts","../src/use-unified-app.ts"],"sourcesContent":["/**\n * @file React bindings for mcp-ui-ext-apps-openai\n *\n * @example\n * ```tsx\n * import { useUnifiedApp } from \"mcp-ui-ext-apps-openai/react\";\n *\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Platform: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>+</button>\n * </div>\n * );\n * }\n * ```\n */\n\n// Re-export everything from core\nexport * from \"./index\";\n\n// React hook\nexport {\n useUnifiedApp,\n type UseUnifiedAppOptions,\n type UseUnifiedAppResult,\n} from \"./use-unified-app\";\n","/**\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","/**\n * @file React hook for unified OpenAI/MCP app usage\n *\n * This hook provides a unified interface for apps that need to run on both\n * OpenAI ChatGPT and MCP Apps platforms.\n */\n\nimport type { App, McpUiAppCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { PostMessageTransport } from \"@modelcontextprotocol/ext-apps\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n createMCPAdapter,\n createOpenAIAdapter,\n detectPlatform,\n type Platform,\n type UnifiedApp,\n type UnifiedHostContext,\n type UnifiedToolResult,\n} from \"./unified-app\";\n\nexport interface UseUnifiedAppOptions {\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 UseUnifiedAppResult {\n /** The unified app instance */\n app: UnifiedApp | null;\n /** Whether the app is connected to the host */\n isConnected: boolean;\n /** Any connection error */\n error: Error | null;\n /** The detected platform */\n platform: Platform;\n /** Current host context (theme, displayMode, locale, etc.) */\n hostContext: UnifiedHostContext | undefined;\n /** Widget props (OpenAI only, reactive) */\n widgetProps: Record<string, unknown>;\n /** Widget state - initialized from toolOutput on mount, works on both platforms via React state */\n widgetState: unknown;\n /** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */\n setWidgetState: <T = unknown>(state: T) => void;\n /** Update widget state partially - works on both platforms */\n updateWidgetState: <T = unknown>(state: Partial<T>) => void;\n}\n\n/**\n * Hook to subscribe to OpenAI global property changes\n * Listens for 'openai:set_globals' events\n */\nfunction useOpenAIGlobal<T>(key: string, initialValue: T): T {\n const [value, setValue] = useState<T>(initialValue);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.openai) return;\n\n // Get initial value - cast to unknown first then to Record\n const openai = window.openai as unknown as Record<string, unknown>;\n if (key in openai) {\n setValue(openai[key] as T);\n }\n\n // Listen for changes\n const handleChange = () => {\n const currentOpenai = window.openai as unknown as Record<string, unknown>;\n if (currentOpenai && key in currentOpenai) {\n setValue(currentOpenai[key] as T);\n }\n };\n\n window.addEventListener(\"openai:set_globals\", handleChange);\n return () => window.removeEventListener(\"openai:set_globals\", handleChange);\n }, [key]);\n\n return value;\n}\n\n/**\n * React hook that provides a unified app interface for both OpenAI and MCP platforms\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * capabilities: {},\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Running on: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useUnifiedApp(options: UseUnifiedAppOptions): UseUnifiedAppResult {\n // Detect platform once on mount\n const platformRef = useRef<Platform>(detectPlatform());\n const platform = platformRef.current;\n\n const [unifiedApp, setUnifiedApp] = useState<UnifiedApp | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [hostContext, setHostContext] = useState<UnifiedHostContext | undefined>();\n\n // React state for widgetState - works on both platforms\n // Initialize from OpenAI's toolOutput (initialProps) if available\n const [localWidgetState, setLocalWidgetState] = useState<unknown>(() => {\n if (platform === \"openai\" && typeof window !== \"undefined\" && window.openai) {\n const openai = window.openai as unknown as Record<string, unknown>;\n return openai.toolOutput ?? openai.widgetState ?? null;\n }\n return null;\n });\n\n // Track if we set widget state ourselves (to prevent sync loop)\n const isOurUpdateRef = useRef(false);\n\n // Store unifiedApp in ref for stable callbacks\n const unifiedAppRef = useRef<UnifiedApp | null>(null);\n unifiedAppRef.current = unifiedApp;\n\n // OpenAI reactive globals\n const openaiWidgetState = useOpenAIGlobal<unknown>(\"widgetState\", null);\n const openaiWidgetProps = useOpenAIGlobal<Record<string, unknown>>(\"widget\", {});\n const openaiToolOutput = useOpenAIGlobal<unknown>(\"toolOutput\", undefined);\n const openaiTheme = useOpenAIGlobal<\"light\" | \"dark\">(\"theme\", \"light\");\n const openaiDisplayMode = useOpenAIGlobal<string>(\"displayMode\", \"inline\");\n const openaiLocale = useOpenAIGlobal<string>(\"locale\", \"en-US\");\n\n // Store options in ref to avoid stale closures\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Initialize localWidgetState from initialProps (toolOutput) on first mount for OpenAI\n const hasInitialized = useRef(false);\n useEffect(() => {\n if (platform === \"openai\" && !hasInitialized.current && openaiToolOutput !== undefined) {\n setLocalWidgetState(openaiToolOutput);\n hasInitialized.current = true;\n }\n }, [platform, openaiToolOutput]);\n\n // Sync OpenAI widgetState to local state when it changes (only if not caused by us)\n useEffect(() => {\n if (platform === \"openai\" && openaiWidgetState !== null) {\n if (!isOurUpdateRef.current) {\n setLocalWidgetState(openaiWidgetState);\n }\n isOurUpdateRef.current = false;\n }\n }, [platform, openaiWidgetState]);\n\n // Widget state setters - stable callbacks using refs\n const setWidgetState = useCallback(<T = unknown>(state: T) => {\n setLocalWidgetState(state);\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.setWidgetState(state);\n }\n }, [platform]);\n\n const updateWidgetState = useCallback(<T = unknown>(state: Partial<T>) => {\n setLocalWidgetState((prev: unknown) => ({ ...(prev as object), ...state }));\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.updateWidgetState(state);\n }\n }, [platform]);\n\n // Update host context from OpenAI globals\n useEffect(() => {\n if (platform === \"openai\" && isConnected) {\n setHostContext({\n theme: openaiTheme,\n displayMode: openaiDisplayMode as UnifiedHostContext[\"displayMode\"],\n locale: openaiLocale,\n safeAreaInsets: window.openai?.safeArea?.insets,\n maxWidth: window.openai?.maxWidth,\n maxHeight: window.openai?.maxHeight,\n });\n }\n }, [platform, isConnected, openaiTheme, openaiDisplayMode, openaiLocale]);\n\n useEffect(() => {\n let mounted = true;\n let mcpApp: App | null = null;\n\n async function initialize() {\n const opts = optionsRef.current;\n\n if (platform === \"openai\") {\n // OpenAI platform - use the adapter directly\n try {\n const adapter = createOpenAIAdapter({\n ...opts,\n onToolResult: (result) => {\n opts.onToolResult?.(result);\n },\n onHostContextChanged: (ctx) => {\n if (mounted) {\n setHostContext(ctx);\n opts.onHostContextChanged?.(ctx);\n }\n },\n onError: (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err);\n },\n });\n\n if (mounted) {\n setUnifiedApp(adapter);\n setIsConnected(true);\n setHostContext(adapter.getHostContext());\n }\n } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err as Error);\n setIsConnected(false);\n }\n }\n } else {\n // MCP platform - need to dynamically import and create connection\n try {\n // Dynamic import to avoid issues when running on OpenAI\n const { App } = await import(\"@modelcontextprotocol/ext-apps\");\n\n const transport = new PostMessageTransport(window.parent, window.parent);\n mcpApp = new App(opts.appInfo, opts.capabilities || {});\n\n // Set up handlers before connecting\n mcpApp.onteardown = async () => {\n await opts.onTeardown?.();\n return {};\n };\n\n mcpApp.ontoolinput = async (input) => {\n await opts.onToolInput?.(input);\n };\n\n mcpApp.ontoolresult = async (result) => {\n await opts.onToolResult?.(result as UnifiedToolResult);\n };\n\n mcpApp.onhostcontextchanged = (params) => {\n if (mounted) {\n setHostContext((prev) => {\n const newCtx = { ...prev, ...params } as UnifiedHostContext;\n opts.onHostContextChanged?.(newCtx);\n return newCtx;\n });\n }\n };\n\n await mcpApp.connect(transport);\n\n if (mounted) {\n const adapter = createMCPAdapter(mcpApp, opts);\n setUnifiedApp(adapter);\n setIsConnected(true);\n\n // Get initial host context\n const ctx = mcpApp.getHostContext();\n if (ctx) {\n setHostContext({\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 } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n const error = err instanceof Error ? err : new Error(\"Failed to connect\");\n if (mounted) {\n setError(error);\n setIsConnected(false);\n }\n opts.onError?.(error);\n }\n }\n }\n\n initialize();\n\n return () => {\n mounted = false;\n // Note: MCP App cleanup would go here if needed\n };\n }, [platform]);\n\n // Determine final platform based on connection state\n const finalPlatform: Platform = useMemo(() => {\n if (platform === \"openai\") return \"openai\";\n if (isConnected) return \"mcp\";\n return \"unknown\";\n }, [platform, isConnected]);\n\n // Return platform-appropriate values\n const widgetProps = platform === \"openai\" ? ((openaiWidgetProps as { props?: Record<string, unknown> })?.props || {}) : {};\n\n return {\n app: unifiedApp,\n isConnected,\n error,\n platform: finalPlatform,\n hostContext,\n widgetProps,\n widgetState: localWidgetState,\n setWidgetState,\n updateWidgetState,\n };\n}\n\nexport default useUnifiedApp;\n"],"mappings":"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","names":["error"]}
|
package/dist/react.mjs
CHANGED
|
@@ -54,6 +54,13 @@ function useUnifiedApp(options) {
|
|
|
54
54
|
const openaiLocale = useOpenAIGlobal("locale", "en-US");
|
|
55
55
|
const optionsRef = useRef(options);
|
|
56
56
|
optionsRef.current = options;
|
|
57
|
+
const hasInitialized = useRef(false);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (platform === "openai" && !hasInitialized.current && openaiToolOutput !== void 0) {
|
|
60
|
+
setLocalWidgetState(openaiToolOutput);
|
|
61
|
+
hasInitialized.current = true;
|
|
62
|
+
}
|
|
63
|
+
}, [platform, openaiToolOutput]);
|
|
57
64
|
useEffect(() => {
|
|
58
65
|
if (platform === "openai" && openaiWidgetState !== null) {
|
|
59
66
|
if (!isOurUpdateRef.current) {
|
|
@@ -138,10 +145,6 @@ function useUnifiedApp(options) {
|
|
|
138
145
|
mcpApp.ontoolresult = async (result) => {
|
|
139
146
|
await opts.onToolResult?.(result);
|
|
140
147
|
};
|
|
141
|
-
mcpApp.onerror = (err) => {
|
|
142
|
-
console.error(`[${opts.appInfo.name}]`, err);
|
|
143
|
-
opts.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
144
|
-
};
|
|
145
148
|
mcpApp.onhostcontextchanged = (params) => {
|
|
146
149
|
if (mounted) {
|
|
147
150
|
setHostContext((prev) => {
|
|
@@ -168,10 +171,12 @@ function useUnifiedApp(options) {
|
|
|
168
171
|
}
|
|
169
172
|
} catch (err) {
|
|
170
173
|
console.error(`[${opts.appInfo.name}]`, err);
|
|
174
|
+
const error2 = err instanceof Error ? err : new Error("Failed to connect");
|
|
171
175
|
if (mounted) {
|
|
172
|
-
setError(
|
|
176
|
+
setError(error2);
|
|
173
177
|
setIsConnected(false);
|
|
174
178
|
}
|
|
179
|
+
opts.onError?.(error2);
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
}
|
|
@@ -186,14 +191,12 @@ function useUnifiedApp(options) {
|
|
|
186
191
|
return "unknown";
|
|
187
192
|
}, [platform, isConnected]);
|
|
188
193
|
const widgetProps = platform === "openai" ? openaiWidgetProps?.props || {} : {};
|
|
189
|
-
const initialProps = platform === "openai" ? openaiToolOutput : void 0;
|
|
190
194
|
return {
|
|
191
195
|
app: unifiedApp,
|
|
192
196
|
isConnected,
|
|
193
197
|
error,
|
|
194
198
|
platform: finalPlatform,
|
|
195
199
|
hostContext,
|
|
196
|
-
initialProps,
|
|
197
200
|
widgetProps,
|
|
198
201
|
widgetState: localWidgetState,
|
|
199
202
|
setWidgetState,
|
package/dist/react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-unified-app.ts"],"sourcesContent":["/**\n * @file React hook for unified OpenAI/MCP app usage\n *\n * This hook provides a unified interface for apps that need to run on both\n * OpenAI ChatGPT and MCP Apps platforms.\n */\n\nimport type { App, McpUiAppCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { PostMessageTransport } from \"@modelcontextprotocol/ext-apps\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n createMCPAdapter,\n createOpenAIAdapter,\n detectPlatform,\n type Platform,\n type UnifiedApp,\n type UnifiedHostContext,\n type UnifiedToolResult,\n} from \"./unified-app\";\n\nexport interface UseUnifiedAppOptions {\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 UseUnifiedAppResult {\n /** The unified app instance */\n app: UnifiedApp | null;\n /** Whether the app is connected to the host */\n isConnected: boolean;\n /** Any connection error */\n error: Error | null;\n /** The detected platform */\n platform: Platform;\n /** Current host context (theme, displayMode, locale, etc.) */\n hostContext: UnifiedHostContext | undefined;\n /** Initial props from toolOutput (OpenAI only, undefined on MCP) - use as initial data for component */\n initialProps: unknown;\n /** Widget props (OpenAI only, reactive) */\n widgetProps: Record<string, unknown>;\n /** Widget state - works on both platforms via React state */\n widgetState: unknown;\n /** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */\n setWidgetState: <T = unknown>(state: T) => void;\n /** Update widget state partially - works on both platforms */\n updateWidgetState: <T = unknown>(state: Partial<T>) => void;\n}\n\n/**\n * Hook to subscribe to OpenAI global property changes\n * Listens for 'openai:set_globals' events\n */\nfunction useOpenAIGlobal<T>(key: string, initialValue: T): T {\n const [value, setValue] = useState<T>(initialValue);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.openai) return;\n\n // Get initial value - cast to unknown first then to Record\n const openai = window.openai as unknown as Record<string, unknown>;\n if (key in openai) {\n setValue(openai[key] as T);\n }\n\n // Listen for changes\n const handleChange = () => {\n const currentOpenai = window.openai as unknown as Record<string, unknown>;\n if (currentOpenai && key in currentOpenai) {\n setValue(currentOpenai[key] as T);\n }\n };\n\n window.addEventListener(\"openai:set_globals\", handleChange);\n return () => window.removeEventListener(\"openai:set_globals\", handleChange);\n }, [key]);\n\n return value;\n}\n\n/**\n * React hook that provides a unified app interface for both OpenAI and MCP platforms\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * capabilities: {},\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Running on: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useUnifiedApp(options: UseUnifiedAppOptions): UseUnifiedAppResult {\n // Detect platform once on mount\n const platformRef = useRef<Platform>(detectPlatform());\n const platform = platformRef.current;\n\n const [unifiedApp, setUnifiedApp] = useState<UnifiedApp | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [hostContext, setHostContext] = useState<UnifiedHostContext | undefined>();\n\n // React state for widgetState - works on both platforms\n // Initialize from OpenAI's toolOutput (initialProps) if available\n const [localWidgetState, setLocalWidgetState] = useState<unknown>(() => {\n if (platform === \"openai\" && typeof window !== \"undefined\" && window.openai) {\n const openai = window.openai as unknown as Record<string, unknown>;\n return openai.toolOutput ?? openai.widgetState ?? null;\n }\n return null;\n });\n\n // Track if we set widget state ourselves (to prevent sync loop)\n const isOurUpdateRef = useRef(false);\n\n // Store unifiedApp in ref for stable callbacks\n const unifiedAppRef = useRef<UnifiedApp | null>(null);\n unifiedAppRef.current = unifiedApp;\n\n // OpenAI reactive globals\n const openaiWidgetState = useOpenAIGlobal<unknown>(\"widgetState\", null);\n const openaiWidgetProps = useOpenAIGlobal<Record<string, unknown>>(\"widget\", {});\n const openaiToolOutput = useOpenAIGlobal<unknown>(\"toolOutput\", undefined);\n const openaiTheme = useOpenAIGlobal<\"light\" | \"dark\">(\"theme\", \"light\");\n const openaiDisplayMode = useOpenAIGlobal<string>(\"displayMode\", \"inline\");\n const openaiLocale = useOpenAIGlobal<string>(\"locale\", \"en-US\");\n\n // Store options in ref to avoid stale closures\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Sync OpenAI widgetState to local state when it changes (only if not caused by us)\n useEffect(() => {\n if (platform === \"openai\" && openaiWidgetState !== null) {\n if (!isOurUpdateRef.current) {\n setLocalWidgetState(openaiWidgetState);\n }\n isOurUpdateRef.current = false;\n }\n }, [platform, openaiWidgetState]);\n\n // Widget state setters - stable callbacks using refs\n const setWidgetState = useCallback(<T = unknown>(state: T) => {\n setLocalWidgetState(state);\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.setWidgetState(state);\n }\n }, [platform]);\n\n const updateWidgetState = useCallback(<T = unknown>(state: Partial<T>) => {\n setLocalWidgetState((prev: unknown) => ({ ...(prev as object), ...state }));\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.updateWidgetState(state);\n }\n }, [platform]);\n\n // Update host context from OpenAI globals\n useEffect(() => {\n if (platform === \"openai\" && isConnected) {\n setHostContext({\n theme: openaiTheme,\n displayMode: openaiDisplayMode as UnifiedHostContext[\"displayMode\"],\n locale: openaiLocale,\n safeAreaInsets: window.openai?.safeArea?.insets,\n maxWidth: window.openai?.maxWidth,\n maxHeight: window.openai?.maxHeight,\n });\n }\n }, [platform, isConnected, openaiTheme, openaiDisplayMode, openaiLocale]);\n\n useEffect(() => {\n let mounted = true;\n let mcpApp: App | null = null;\n\n async function initialize() {\n const opts = optionsRef.current;\n\n if (platform === \"openai\") {\n // OpenAI platform - use the adapter directly\n try {\n const adapter = createOpenAIAdapter({\n ...opts,\n onToolResult: (result) => {\n opts.onToolResult?.(result);\n },\n onHostContextChanged: (ctx) => {\n if (mounted) {\n setHostContext(ctx);\n opts.onHostContextChanged?.(ctx);\n }\n },\n onError: (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err);\n },\n });\n\n if (mounted) {\n setUnifiedApp(adapter);\n setIsConnected(true);\n setHostContext(adapter.getHostContext());\n }\n } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err as Error);\n setIsConnected(false);\n }\n }\n } else {\n // MCP platform - need to dynamically import and create connection\n try {\n // Dynamic import to avoid issues when running on OpenAI\n const { App } = await import(\"@modelcontextprotocol/ext-apps\");\n\n const transport = new PostMessageTransport(window.parent, window.parent);\n mcpApp = new App(opts.appInfo, opts.capabilities || {});\n\n // Set up handlers before connecting\n mcpApp.onteardown = async () => {\n await opts.onTeardown?.();\n return {};\n };\n\n mcpApp.ontoolinput = async (input) => {\n await opts.onToolInput?.(input);\n };\n\n mcpApp.ontoolresult = async (result) => {\n await opts.onToolResult?.(result as UnifiedToolResult);\n };\n\n mcpApp.onerror = (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err instanceof Error ? err : new Error(String(err)));\n };\n\n mcpApp.onhostcontextchanged = (params) => {\n if (mounted) {\n setHostContext((prev) => {\n const newCtx = { ...prev, ...params } as UnifiedHostContext;\n opts.onHostContextChanged?.(newCtx);\n return newCtx;\n });\n }\n };\n\n await mcpApp.connect(transport);\n\n if (mounted) {\n const adapter = createMCPAdapter(mcpApp, opts);\n setUnifiedApp(adapter);\n setIsConnected(true);\n\n // Get initial host context\n const ctx = mcpApp.getHostContext();\n if (ctx) {\n setHostContext({\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 } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err instanceof Error ? err : new Error(\"Failed to connect\"));\n setIsConnected(false);\n }\n }\n }\n }\n\n initialize();\n\n return () => {\n mounted = false;\n // Note: MCP App cleanup would go here if needed\n };\n }, [platform]);\n\n // Determine final platform based on connection state\n const finalPlatform: Platform = useMemo(() => {\n if (platform === \"openai\") return \"openai\";\n if (isConnected) return \"mcp\";\n return \"unknown\";\n }, [platform, isConnected]);\n\n // Return platform-appropriate values\n const widgetProps = platform === \"openai\" ? ((openaiWidgetProps as { props?: Record<string, unknown> })?.props || {}) : {};\n const initialProps = platform === \"openai\" ? openaiToolOutput : undefined;\n\n return {\n app: unifiedApp,\n isConnected,\n error,\n platform: finalPlatform,\n hostContext,\n initialProps,\n widgetProps,\n widgetState: localWidgetState,\n setWidgetState,\n updateWidgetState,\n };\n}\n\nexport default useUnifiedApp;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/use-unified-app.ts"],"sourcesContent":["/**\n * @file React hook for unified OpenAI/MCP app usage\n *\n * This hook provides a unified interface for apps that need to run on both\n * OpenAI ChatGPT and MCP Apps platforms.\n */\n\nimport type { App, McpUiAppCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { PostMessageTransport } from \"@modelcontextprotocol/ext-apps\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n createMCPAdapter,\n createOpenAIAdapter,\n detectPlatform,\n type Platform,\n type UnifiedApp,\n type UnifiedHostContext,\n type UnifiedToolResult,\n} from \"./unified-app\";\n\nexport interface UseUnifiedAppOptions {\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 UseUnifiedAppResult {\n /** The unified app instance */\n app: UnifiedApp | null;\n /** Whether the app is connected to the host */\n isConnected: boolean;\n /** Any connection error */\n error: Error | null;\n /** The detected platform */\n platform: Platform;\n /** Current host context (theme, displayMode, locale, etc.) */\n hostContext: UnifiedHostContext | undefined;\n /** Widget props (OpenAI only, reactive) */\n widgetProps: Record<string, unknown>;\n /** Widget state - initialized from toolOutput on mount, works on both platforms via React state */\n widgetState: unknown;\n /** Set widget state - works on both platforms (also syncs to OpenAI if on that platform) */\n setWidgetState: <T = unknown>(state: T) => void;\n /** Update widget state partially - works on both platforms */\n updateWidgetState: <T = unknown>(state: Partial<T>) => void;\n}\n\n/**\n * Hook to subscribe to OpenAI global property changes\n * Listens for 'openai:set_globals' events\n */\nfunction useOpenAIGlobal<T>(key: string, initialValue: T): T {\n const [value, setValue] = useState<T>(initialValue);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.openai) return;\n\n // Get initial value - cast to unknown first then to Record\n const openai = window.openai as unknown as Record<string, unknown>;\n if (key in openai) {\n setValue(openai[key] as T);\n }\n\n // Listen for changes\n const handleChange = () => {\n const currentOpenai = window.openai as unknown as Record<string, unknown>;\n if (currentOpenai && key in currentOpenai) {\n setValue(currentOpenai[key] as T);\n }\n };\n\n window.addEventListener(\"openai:set_globals\", handleChange);\n return () => window.removeEventListener(\"openai:set_globals\", handleChange);\n }, [key]);\n\n return value;\n}\n\n/**\n * React hook that provides a unified app interface for both OpenAI and MCP platforms\n *\n * @example\n * ```tsx\n * function MyApp() {\n * const {\n * app,\n * isConnected,\n * platform,\n * hostContext,\n * widgetState,\n * setWidgetState,\n * } = useUnifiedApp({\n * appInfo: { name: \"My App\", version: \"1.0.0\" },\n * capabilities: {},\n * onError: console.error,\n * });\n *\n * const counter = (widgetState as { value?: number } | null)?.value ?? 0;\n *\n * return (\n * <div>\n * <p>Running on: {platform}</p>\n * <p>Counter: {counter}</p>\n * <button onClick={() => setWidgetState({ value: counter + 1 })}>\n * Increment\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useUnifiedApp(options: UseUnifiedAppOptions): UseUnifiedAppResult {\n // Detect platform once on mount\n const platformRef = useRef<Platform>(detectPlatform());\n const platform = platformRef.current;\n\n const [unifiedApp, setUnifiedApp] = useState<UnifiedApp | null>(null);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [hostContext, setHostContext] = useState<UnifiedHostContext | undefined>();\n\n // React state for widgetState - works on both platforms\n // Initialize from OpenAI's toolOutput (initialProps) if available\n const [localWidgetState, setLocalWidgetState] = useState<unknown>(() => {\n if (platform === \"openai\" && typeof window !== \"undefined\" && window.openai) {\n const openai = window.openai as unknown as Record<string, unknown>;\n return openai.toolOutput ?? openai.widgetState ?? null;\n }\n return null;\n });\n\n // Track if we set widget state ourselves (to prevent sync loop)\n const isOurUpdateRef = useRef(false);\n\n // Store unifiedApp in ref for stable callbacks\n const unifiedAppRef = useRef<UnifiedApp | null>(null);\n unifiedAppRef.current = unifiedApp;\n\n // OpenAI reactive globals\n const openaiWidgetState = useOpenAIGlobal<unknown>(\"widgetState\", null);\n const openaiWidgetProps = useOpenAIGlobal<Record<string, unknown>>(\"widget\", {});\n const openaiToolOutput = useOpenAIGlobal<unknown>(\"toolOutput\", undefined);\n const openaiTheme = useOpenAIGlobal<\"light\" | \"dark\">(\"theme\", \"light\");\n const openaiDisplayMode = useOpenAIGlobal<string>(\"displayMode\", \"inline\");\n const openaiLocale = useOpenAIGlobal<string>(\"locale\", \"en-US\");\n\n // Store options in ref to avoid stale closures\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // Initialize localWidgetState from initialProps (toolOutput) on first mount for OpenAI\n const hasInitialized = useRef(false);\n useEffect(() => {\n if (platform === \"openai\" && !hasInitialized.current && openaiToolOutput !== undefined) {\n setLocalWidgetState(openaiToolOutput);\n hasInitialized.current = true;\n }\n }, [platform, openaiToolOutput]);\n\n // Sync OpenAI widgetState to local state when it changes (only if not caused by us)\n useEffect(() => {\n if (platform === \"openai\" && openaiWidgetState !== null) {\n if (!isOurUpdateRef.current) {\n setLocalWidgetState(openaiWidgetState);\n }\n isOurUpdateRef.current = false;\n }\n }, [platform, openaiWidgetState]);\n\n // Widget state setters - stable callbacks using refs\n const setWidgetState = useCallback(<T = unknown>(state: T) => {\n setLocalWidgetState(state);\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.setWidgetState(state);\n }\n }, [platform]);\n\n const updateWidgetState = useCallback(<T = unknown>(state: Partial<T>) => {\n setLocalWidgetState((prev: unknown) => ({ ...(prev as object), ...state }));\n if (platform === \"openai\" && unifiedAppRef.current) {\n isOurUpdateRef.current = true;\n unifiedAppRef.current.updateWidgetState(state);\n }\n }, [platform]);\n\n // Update host context from OpenAI globals\n useEffect(() => {\n if (platform === \"openai\" && isConnected) {\n setHostContext({\n theme: openaiTheme,\n displayMode: openaiDisplayMode as UnifiedHostContext[\"displayMode\"],\n locale: openaiLocale,\n safeAreaInsets: window.openai?.safeArea?.insets,\n maxWidth: window.openai?.maxWidth,\n maxHeight: window.openai?.maxHeight,\n });\n }\n }, [platform, isConnected, openaiTheme, openaiDisplayMode, openaiLocale]);\n\n useEffect(() => {\n let mounted = true;\n let mcpApp: App | null = null;\n\n async function initialize() {\n const opts = optionsRef.current;\n\n if (platform === \"openai\") {\n // OpenAI platform - use the adapter directly\n try {\n const adapter = createOpenAIAdapter({\n ...opts,\n onToolResult: (result) => {\n opts.onToolResult?.(result);\n },\n onHostContextChanged: (ctx) => {\n if (mounted) {\n setHostContext(ctx);\n opts.onHostContextChanged?.(ctx);\n }\n },\n onError: (err) => {\n console.error(`[${opts.appInfo.name}]`, err);\n opts.onError?.(err);\n },\n });\n\n if (mounted) {\n setUnifiedApp(adapter);\n setIsConnected(true);\n setHostContext(adapter.getHostContext());\n }\n } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n if (mounted) {\n setError(err as Error);\n setIsConnected(false);\n }\n }\n } else {\n // MCP platform - need to dynamically import and create connection\n try {\n // Dynamic import to avoid issues when running on OpenAI\n const { App } = await import(\"@modelcontextprotocol/ext-apps\");\n\n const transport = new PostMessageTransport(window.parent, window.parent);\n mcpApp = new App(opts.appInfo, opts.capabilities || {});\n\n // Set up handlers before connecting\n mcpApp.onteardown = async () => {\n await opts.onTeardown?.();\n return {};\n };\n\n mcpApp.ontoolinput = async (input) => {\n await opts.onToolInput?.(input);\n };\n\n mcpApp.ontoolresult = async (result) => {\n await opts.onToolResult?.(result as UnifiedToolResult);\n };\n\n mcpApp.onhostcontextchanged = (params) => {\n if (mounted) {\n setHostContext((prev) => {\n const newCtx = { ...prev, ...params } as UnifiedHostContext;\n opts.onHostContextChanged?.(newCtx);\n return newCtx;\n });\n }\n };\n\n await mcpApp.connect(transport);\n\n if (mounted) {\n const adapter = createMCPAdapter(mcpApp, opts);\n setUnifiedApp(adapter);\n setIsConnected(true);\n\n // Get initial host context\n const ctx = mcpApp.getHostContext();\n if (ctx) {\n setHostContext({\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 } catch (err) {\n console.error(`[${opts.appInfo.name}]`, err);\n const error = err instanceof Error ? err : new Error(\"Failed to connect\");\n if (mounted) {\n setError(error);\n setIsConnected(false);\n }\n opts.onError?.(error);\n }\n }\n }\n\n initialize();\n\n return () => {\n mounted = false;\n // Note: MCP App cleanup would go here if needed\n };\n }, [platform]);\n\n // Determine final platform based on connection state\n const finalPlatform: Platform = useMemo(() => {\n if (platform === \"openai\") return \"openai\";\n if (isConnected) return \"mcp\";\n return \"unknown\";\n }, [platform, isConnected]);\n\n // Return platform-appropriate values\n const widgetProps = platform === \"openai\" ? ((openaiWidgetProps as { props?: Record<string, unknown> })?.props || {}) : {};\n\n return {\n app: unifiedApp,\n isConnected,\n error,\n platform: finalPlatform,\n hostContext,\n widgetProps,\n widgetState: localWidgetState,\n setWidgetState,\n updateWidgetState,\n };\n}\n\nexport default useUnifiedApp;\n"],"mappings":"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","names":["error"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ui-ext-apps-openai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Unified utility for building apps that work on both OpenAI ChatGPT and MCP Apps platforms",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"build": "tsup",
|
|
26
26
|
"dev": "tsup --watch",
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest",
|
|
29
|
+
"test:run": "vitest run",
|
|
28
30
|
"prepublishOnly": "npm run build"
|
|
29
31
|
},
|
|
30
32
|
"keywords": [
|
|
@@ -52,9 +54,18 @@
|
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@modelcontextprotocol/ext-apps": "^0.3.0",
|
|
57
|
+
"@testing-library/dom": "^10.4.1",
|
|
58
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
59
|
+
"@testing-library/react": "^16.3.1",
|
|
60
|
+
"@types/node": "^25.0.9",
|
|
55
61
|
"@types/react": "^18.2.0",
|
|
62
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
63
|
+
"jsdom": "^27.0.1",
|
|
56
64
|
"react": "^18.2.0",
|
|
65
|
+
"react-dom": "^18.2.0",
|
|
57
66
|
"tsup": "^8.0.0",
|
|
58
|
-
"typescript": "^5.0.0"
|
|
67
|
+
"typescript": "^5.0.0",
|
|
68
|
+
"vite": "^7.3.1",
|
|
69
|
+
"vitest": "^3.2.4"
|
|
59
70
|
}
|
|
60
71
|
}
|