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 +66 -18
- package/dist/{bridge-BXW_-p2R.d.ts → bridge-BKLezf-H.d.ts} +13 -0
- package/dist/{chunk-2OPSDEPI.js → chunk-IEZAKOIG.js} +34 -2
- package/dist/{chunk-4LAH4JH6.js → chunk-QNH5NSRH.js} +0 -19
- package/dist/cli/index.js +162 -133
- package/dist/core/index.d.ts +11 -29
- package/dist/core/index.js +1 -3
- package/dist/index.d.ts +114 -33
- package/dist/index.js +73 -49
- package/dist/platforms/mcp/index.d.ts +2 -2
- package/dist/platforms/mcp/index.js +13 -3
- package/package.json +4 -8
- package/dist/chunk-EPZCYA26.js +0 -162
- package/dist/platforms/chatgpt/index.d.ts +0 -159
- package/dist/platforms/chatgpt/index.js +0 -167
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# MCP App Studio
|
|
2
2
|
|
|
3
|
-
**Build interactive apps for
|
|
3
|
+
**Build interactive apps for MCP Apps hosts (ChatGPT, Claude Desktop, etc.).**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
11
|
-
- **
|
|
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(); // "
|
|
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
|
-
//
|
|
46
|
-
const hasWidgetState = useFeature(
|
|
47
|
-
const
|
|
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 |
|
|
69
|
-
|
|
70
|
-
| `callTool` | ✅ | ✅ |
|
|
71
|
-
| `openLink` | ✅ | ✅ |
|
|
72
|
-
| `sendMessage` | ✅ | ✅ |
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `fileUpload` / `fileDownload` |
|
|
76
|
-
| `
|
|
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-
|
|
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
|
-
|
|
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
|
};
|