mcp-app-studio 0.5.1 → 0.6.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 CHANGED
@@ -1,14 +1,16 @@
1
1
  # MCP App Studio
2
2
 
3
- **Build interactive apps for AI assistants (ChatGPT, Claude, MCP hosts).**
3
+ **Build interactive apps for MCP Apps hosts (ChatGPT, Claude Desktop, etc.).**
4
4
 
5
- Create widgets that work across multiple platforms with a single codebase. The SDK auto-detects whether you're running in ChatGPT, Claude Desktop, or another MCP-compatible host.
5
+ ChatGPT is an **MCP Apps host**. This SDK is MCP-first: it uses the standard
6
+ `ui/*` bridge everywhere, and treats `window.openai` as **optional ChatGPT-only
7
+ extensions** layered on top.
6
8
 
7
9
  ## What You Get
8
10
 
9
11
  - **Local workbench** — Preview widgets without deploying
10
- - **Universal SDK** — Single API works on ChatGPT and MCP hosts
11
- - **Platform detection** — Auto-adapts to the host environment
12
+ - **Universal SDK** — Single API works across MCP Apps hosts
13
+ - **Optional ChatGPT extensions** — Feature-detected `window.openai` helpers
12
14
  - **One-command export** — Generate production bundle + MCP server
13
15
 
14
16
  ## Quick Start
@@ -33,18 +35,19 @@ import {
33
35
  useToolInput,
34
36
  useCallTool,
35
37
  useTheme,
36
- useFeature
38
+ useFeature,
39
+ hasChatGPTExtensions,
37
40
  } from "mcp-app-studio";
38
41
 
39
42
  function MyWidget() {
40
- const platform = usePlatform(); // "chatgpt" | "mcp" | "unknown"
43
+ const platform = usePlatform(); // "mcp" | "unknown"
41
44
  const input = useToolInput<{ query: string }>();
42
45
  const callTool = useCallTool();
43
46
  const theme = useTheme();
44
47
 
45
- // Platform-specific features
46
- const hasWidgetState = useFeature('widgetState'); // ChatGPT only
47
- const hasModelContext = useFeature('modelContext'); // MCP only
48
+ // Optional ChatGPT extensions (window.openai)
49
+ const hasWidgetState = useFeature("widgetState");
50
+ const canUseOpenAIExtensions = hasChatGPTExtensions();
48
51
 
49
52
  return (
50
53
  <div className={theme === 'dark' ? 'bg-gray-900' : 'bg-white'}>
@@ -63,17 +66,56 @@ function App() {
63
66
  }
64
67
  ```
65
68
 
69
+ ## Migrating from 0.5.x
70
+
71
+ ### Platform detection
72
+
73
+ `detectPlatform()` now reports host family (`"mcp"` or `"unknown"`). It no
74
+ longer returns `"chatgpt"` directly.
75
+
76
+ ```tsx
77
+ // Before (0.5.x)
78
+ import { detectPlatform } from "mcp-app-studio";
79
+
80
+ if (detectPlatform() === "chatgpt") {
81
+ // ChatGPT-specific behavior
82
+ }
83
+
84
+ // After (MCP-first)
85
+ import { hasChatGPTExtensions, useFeature } from "mcp-app-studio";
86
+
87
+ if (hasChatGPTExtensions()) {
88
+ // ChatGPT extension layer is available (window.openai)
89
+ }
90
+
91
+ const hasWidgetState = useFeature("widgetState");
92
+ ```
93
+
94
+ ### Provider imports
95
+
96
+ Use `UniversalProvider` from the main package export. The
97
+ `mcp-app-studio/chatgpt` entrypoint is removed.
98
+
99
+ ```tsx
100
+ // Before (0.5.x)
101
+ import { ChatGPTProvider } from "mcp-app-studio/chatgpt";
102
+
103
+ // After (MCP-first)
104
+ import { UniversalProvider } from "mcp-app-studio";
105
+ ```
106
+
66
107
  ## Platform Capabilities
67
108
 
68
- | Feature | ChatGPT | MCP |
69
- |---------|---------|-----|
70
- | `callTool` | ✅ | ✅ |
71
- | `openLink` | ✅ | ✅ |
72
- | `sendMessage` | ✅ | ✅ |
73
- | `widgetState` (persistence) | ✅ | |
74
- | `modelContext` (dynamic context) | | ✅ |
75
- | `fileUpload` / `fileDownload` | | |
76
- | `partialToolInput` (streaming) | | ✅ |
109
+ | Feature | MCP Apps bridge | ChatGPT extensions (`window.openai`) |
110
+ |---------|----------------|-------------------------------------|
111
+ | `callTool` | ✅ | ✅ (`callTool`) |
112
+ | `openLink` | ✅ | ✅ (`openExternal`) |
113
+ | `sendMessage` | ✅ | ✅ (`sendFollowUpMessage`) |
114
+ | `modelContext` (`ui/update-model-context`) | ✅ | |
115
+ | `widgetState` (persistence) | | ✅ |
116
+ | `fileUpload` / `fileDownload` | | |
117
+ | `requestModal` | | ✅ |
118
+ | `partialToolInput` (streaming) | Host-dependent | — |
77
119
 
78
120
  ## Workflow
79
121
 
@@ -130,6 +172,12 @@ console.log('Detected by:', result.detectedBy);
130
172
  console.log('Checks:', result.checks);
131
173
  ```
132
174
 
175
+ ## Tool metadata (`_meta.ui.resourceUri`)
176
+
177
+ For tools that render UI, OpenAI recommends using `_meta.ui.resourceUri` (with
178
+ legacy support for `_meta["openai/outputTemplate"]`). See the starter template
179
+ and MCP server generator for a working example.
180
+
133
181
  ## MCP Server
134
182
 
135
183
  If you selected "Include MCP server" during setup:
@@ -17,6 +17,18 @@ interface AppCapabilities {
17
17
  }
18
18
  interface MCPBridgeOptions {
19
19
  autoResize?: boolean;
20
+ /**
21
+ * Guard against hanging forever when rendered outside a host.
22
+ *
23
+ * `@modelcontextprotocol/ext-apps` connects via `postMessage` to the parent
24
+ * host. In a plain browser environment (no host), that handshake may never
25
+ * complete unless we time out.
26
+ *
27
+ * Set to `0` or a negative value to disable.
28
+ *
29
+ * @default 1500
30
+ */
31
+ connectTimeoutMs?: number;
20
32
  }
21
33
  type CallToolHandler = (name: string, args: Record<string, unknown>, extra: unknown) => Promise<ToolResult>;
22
34
  type ListToolsHandler = (cursor?: string) => Promise<string[]>;
@@ -24,6 +36,7 @@ declare class MCPBridge implements ExtendedBridge {
24
36
  readonly platform: "mcp";
25
37
  readonly capabilities: HostCapabilities;
26
38
  private app;
39
+ private connectTimeoutMs;
27
40
  private toolInputCallbacks;
28
41
  private toolInputPartialCallbacks;
29
42
  private toolResultCallbacks;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MCP_CAPABILITIES
3
- } from "./chunk-4LAH4JH6.js";
3
+ } from "./chunk-QNH5NSRH.js";
4
4
 
5
5
  // src/platforms/mcp/bridge.ts
6
6
  import { App } from "@modelcontextprotocol/ext-apps";
@@ -8,6 +8,7 @@ var MCPBridge = class {
8
8
  platform = "mcp";
9
9
  capabilities = MCP_CAPABILITIES;
10
10
  app;
11
+ connectTimeoutMs;
11
12
  toolInputCallbacks = /* @__PURE__ */ new Set();
12
13
  toolInputPartialCallbacks = /* @__PURE__ */ new Set();
13
14
  toolResultCallbacks = /* @__PURE__ */ new Set();
@@ -16,6 +17,7 @@ var MCPBridge = class {
16
17
  teardownCallbacks = /* @__PURE__ */ new Set();
17
18
  constructor(appInfo, appCapabilities, options) {
18
19
  const autoResize = options?.autoResize ?? true;
20
+ this.connectTimeoutMs = options?.connectTimeoutMs ?? 1500;
19
21
  this.app = new App(
20
22
  appInfo ?? { name: "MCP App", version: "1.0.0" },
21
23
  appCapabilities ?? {},
@@ -60,7 +62,37 @@ var MCPBridge = class {
60
62
  };
61
63
  }
62
64
  async connect() {
63
- await this.app.connect();
65
+ const timeoutMs = this.connectTimeoutMs;
66
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
67
+ await this.app.connect();
68
+ return;
69
+ }
70
+ await new Promise((resolve, reject) => {
71
+ let settled = false;
72
+ const timeoutId = setTimeout(() => {
73
+ if (settled) return;
74
+ settled = true;
75
+ reject(
76
+ new Error(
77
+ `MCP bridge connect timed out after ${timeoutMs}ms (no host responded).`
78
+ )
79
+ );
80
+ }, timeoutMs);
81
+ this.app.connect().then(
82
+ () => {
83
+ if (settled) return;
84
+ settled = true;
85
+ clearTimeout(timeoutId);
86
+ resolve();
87
+ },
88
+ (error) => {
89
+ if (settled) return;
90
+ settled = true;
91
+ clearTimeout(timeoutId);
92
+ reject(error);
93
+ }
94
+ );
95
+ });
64
96
  }
65
97
  getHostContext() {
66
98
  const ctx = this.app.getHostContext();
@@ -1,22 +1,4 @@
1
1
  // src/core/capabilities.ts
2
- var CHATGPT_CAPABILITIES = {
3
- platform: "chatgpt",
4
- callTool: true,
5
- openLink: true,
6
- displayModes: ["pip", "inline", "fullscreen"],
7
- sizeReporting: true,
8
- closeWidget: true,
9
- sendMessage: true,
10
- modal: true,
11
- fileUpload: true,
12
- fileDownload: true,
13
- widgetState: true,
14
- modelContext: false,
15
- logging: false,
16
- partialToolInput: false,
17
- toolCancellation: false,
18
- teardown: false
19
- };
20
2
  var MCP_CAPABILITIES = {
21
3
  platform: "mcp",
22
4
  callTool: true,
@@ -41,7 +23,6 @@ function hasFeature(capabilities, feature) {
41
23
  }
42
24
 
43
25
  export {
44
- CHATGPT_CAPABILITIES,
45
26
  MCP_CAPABILITIES,
46
27
  hasFeature
47
28
  };