Skip to content

Commit 0d8276d

Browse files
jirispilkaclaudeCopilot
authored
feat: Migrate widget metadata to MCP Apps standard (#532)
* feat: Migrate widget metadata to MCP Apps standard Add MCP Apps standard `_meta.ui.*` metadata alongside legacy `openai/*` keys: - Add `ui.resourceUri`, `ui.visibility`, `ui.csp`, `ui.domain`, `ui.prefersBorder` - MIME type: `text/html+skybridge` → `RESOURCE_MIME_TYPE` from `@modelcontextprotocol/ext-apps` - CSP: ship both snake_case (legacy, MCP Jam compat) and camelCase (MCP Apps spec) - CSP: add missing `fonts.googleapis.com` and `fonts.gstatic.com` origins - `stripOpenAiMeta` now also strips `ui` key in non-openai mode - Add `TODO-mcp-apps-migration.md` tracking remaining migration work Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * move TODO-mcp-apps-migration.md to res directory * refactor: widget metadata cleanup + MCP Apps capability negotiation (#536) * refactor: rename stripOpenAiMeta → stripWidgetMeta and filterOpenAiMeta → filterWidgetMeta The function strips both openai/* and ui keys (not just openai/*), so the name should reflect it strips all widget-specific metadata. Also updates comment references in 6 tool files and renames test helpers accordingly. Closes #533 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add MCP Apps capability negotiation via oninitialized handler Detect whether the connected client supports MCP Apps UI by checking for the `io.modelcontextprotocol/ui` extension with `text/html;profile=mcp-app` MIME type in client capabilities. The `clientSupportsUi` flag is informational for now — mode gating remains the primary control. Future PRs can use this flag to conditionally serve widget resources and UI tools. Closes #534 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: Migrate widget client code to MCP Apps SDK (#537) * refactor: replace OpenAI hooks with MCP Apps context - Completely removed `useOpenAiGlobal` hook and replaced all references with the new `useMcpApp`. - Introduced `McpAppContext` for managing app state using MCP Apps API, providing host context, tool output, and connection status. - Updated widget hooks (`use-widget-props`, `use-max-height`, `use-display-mode`, etc.) to use MCP Apps context. - Migrated widget state to React state due to lack of MCP state persistence. - Updated `ActorSearchDetail` and related display mode calls to use MCP Apps APIs. - Finalized outstanding TODO-mcp-apps-migration tasks. * feat: update theme handling and widget metadata to MCP Apps standards * feat: theme synchronization by applying host styles and fonts * feat: implement legacy OpenAI globals fallback and enhance CSP compatibility * feat: enhance ActorRun component to support stable runId retrieval and improve run data initialization * feat: remove long-running task support from OpenAI call-actor and update related tests * feat: add ChatGPT compatibility alias for widget output template * feat: fix MCP App init in chatgpt * feat: update widget metadata for ChatGPT UX hints and remove outputTemplate * feat: update widget metadata for ChatGPT UX hints and remove outputTemplate * feat: Remove unused useDisplayMode * refactor: remove unused Theme and DisplayMode types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * nit: extract WIDGET_DOMAIN constant to avoid repeating 'https://apify.com' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: replace setWidgetState with app.updateModelContext() on run completion When an Actor run reaches a terminal status, notify the model via ui/update-model-context so it can follow up with the results. Removes the now-unused useWidgetState hook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert "feat: replace setWidgetState with app.updateModelContext() on run completion" This reverts commit 017108c. * feat: replace setWidgetState with app.updateModelContext() on run completion When an Actor run reaches a terminal status, notify the model via ui/update-model-context so it can follow up with the results. Removes the now-unused useWidgetState hook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: Remove any and remove script safety replacement * fix: Simplify window.openai checks and remove any * fix: Remove app guard and simplify * fix: remove hostContext and runId * fix: update mock window.openai setup for local development * fix: add AppBridge for mocks * feat: Remove mock data from prod build (#542) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: Update-ui-parameter (#543) * fix: Update UI mode parameter from 'openai' to 'true' in documentation and code * fix: Add integration test for ui=true URL parameter (#544) * Initial plan * test: add integration test for ui=true URL parameter Co-authored-by: jirispilka <19406805+jirispilka@users.noreply.github.com> * fix: Update uiMode type to string and improve related comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jirispilka <19406805+jirispilka@users.noreply.github.com> Co-authored-by: Jiri Spilka <jirka.spilka@gmail.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent fb8252e commit 0d8276d

43 files changed

Lines changed: 2525 additions & 936 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DEVELOPMENT.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ This command builds the core project and the `src/web/` widgets, then copies the
6666

6767
All widget code lives in the self-contained `src/web/` React project. The widgets (MCP Apps) are rendered based on the structured output returned by MCP tools. If you need to add specific data to a widget, modify the corresponding MCP tool's output, since widgets can only render data returned by the MCP tool call result.
6868

69-
> **Important (UI mode):** Widget rendering is enabled only when the server runs in UI mode. Use the `ui=openai` query parameter (e.g., `/?ui=openai`) or set `UI_MODE=openai`. Currently, `openai` is the only supported `ui` value.
69+
> **Important (UI mode):** Widget rendering is enabled only when the server runs in UI mode. Use the `ui=true` query parameter (e.g., `/mcp?ui=true`) or set `UI_MODE=true`.
7070
7171
### Hot-reload development
7272

@@ -150,7 +150,7 @@ You can use [MCPJam](https://www.mcpjam.com/) to connect to and test the MCP ser
150150

151151
1. Click **"Add new server"**
152152
2. Fill in a name for the server
153-
3. Enter the URL: `http://localhost:3001/?ui=openai` (Note: the `ui=openai` query parameter is required for widget rendering)
153+
3. Enter the URL: `http://localhost:3001/mcp?ui=true` (Note: the `ui=openai` query parameter is required for widget rendering)
154154
4. Select **"No authentication"** as the auth method
155155
5. Click **Add**
156156

@@ -196,13 +196,13 @@ Then start the tunnel:
196196
ngrok start app
197197
```
198198

199-
The MCP server API will be reachable at `https://mcp-apify.ngrok.dev/?ui=openai`.
199+
The MCP server API will be reachable at `https://mcp-apify.ngrok.dev/mcp?ui=true`.
200200

201201
#### Adding the server in ChatGPT
202202

203203
1. Go to [chatgpt.com](https://chatgpt.com) and open **Settings → Connectors**
204204
2. Click **"Add a custom connector"**
205-
3. Enter the URL: `https://mcp-apify.ngrok.dev/?ui=openai`
205+
3. Enter the URL: `https://mcp-apify.ngrok.dev/mcp?ui=true`
206206
4. Save and start a new chat
207207

208208
> **Important:** After restarting ngrok, use the **Refresh** button in the connector settings to reconnect — ChatGPT does not detect the tunnel restart automatically.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,34 +294,34 @@ As above, this exposes only the specified Actor (`apify/my-actor`) as a tool. No
294294
295295
### UI mode configuration
296296

297-
The `uiMode` parameter enables OpenAI-specific widget rendering in tool responses. When enabled, tools like `search-actors` return interactive widget responses optimized for OpenAI clients.
297+
The `ui` parameter enables [MCP Apps](https://mcp.apify.com/) widget rendering in tool responses. When enabled, tools like `search-actors` return interactive MCP App responses.
298298

299299
**Configuring the hosted server:**
300300

301301
Enable UI mode using the `ui` query parameter:
302302

303303
```
304-
https://mcp.apify.com?ui=openai
304+
https://mcp.apify.com?ui=true
305305
```
306306

307307
You can combine it with other parameters:
308308

309309
```
310-
https://mcp.apify.com?tools=actors,docs&ui=openai
310+
https://mcp.apify.com?tools=actors,docs&ui=true
311311
```
312312

313313
**Configuring the CLI:**
314314

315315
The CLI can be configured using command-line flags. For example, to enable UI mode:
316316

317317
```bash
318-
npx @apify/actors-mcp-server --ui openai
318+
npx @apify/actors-mcp-server --ui true
319319
```
320320

321321
You can also set it via the `UI_MODE` environment variable:
322322

323323
```bash
324-
export UI_MODE=openai
324+
export UI_MODE=true
325325
npx @apify/actors-mcp-server
326326
```
327327

package-lock.json

Lines changed: 187 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@apify/datastructures": "^2.0.3",
4444
"@apify/log": "^2.5.16",
4545
"@apify/utilities": "^2.25.1",
46+
"@modelcontextprotocol/ext-apps": "^1.1.2",
4647
"@modelcontextprotocol/sdk": "^1.25.2",
4748
"@segment/analytics-node": "^2.3.0",
4849
"@sentry/node": "^10.38.0",

res/TODO-mcp-apps-migration.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# TODO: MCP Apps Full Migration
2+
3+
Remaining work to achieve full MCP Apps (`@modelcontextprotocol/ext-apps`) compliance.
4+
Tracks items not covered by the initial metadata migration PR.
5+
6+
## Server-side
7+
8+
### Capability negotiation (high priority)
9+
- [x] Check `io.modelcontextprotocol/ui` extension in client capabilities during `initialize`
10+
- [x] Use `getUiCapability()` equivalent inline (`getUiCapability()` from `@modelcontextprotocol/ext-apps/server` can't be imported without `moduleResolution: 'node16'` — implemented as 3-line inline check)
11+
- [ ] Conditionally register UI tools/resources only when client supports `text/html;profile=mcp-app` (capability flag is informational for now; future PR)
12+
- [ ] Provide text-only fallback tools when client doesn't support MCP Apps (future PR)
13+
14+
> **Note**: `registerAppTool()`/`registerAppResource()` from `@modelcontextprotocol/ext-apps/server` require `McpServer` (high-level SDK class), but our server uses the low-level `Server` class. Adoption requires a separate `McpServer` migration effort.
15+
16+
### Mode decoupling
17+
- [ ] Decouple `ui` metadata from `mode: 'openai'` — the `_meta.ui.*` keys are the MCP standard and should be available in all modes, not just `openai`
18+
- [ ] Only gate `openai/*` legacy keys behind `mode: 'openai'`
19+
- [ ] Widget resources should be served when widgets are available, regardless of mode
20+
21+
### CSP dual format
22+
- [ ] Remove snake_case CSP fields (`connect_domains`, `resource_domains`) from `WIDGET_CSP` in `src/resources/widgets.ts` once MCP Jam and all hosts support camelCase (`connectDomains`, `resourceDomains`). Currently shipping both for compatibility.
23+
24+
### Cleanup / renames
25+
- [x] Rename `stripOpenAiMeta``stripWidgetMeta` in `src/utils/tools.ts`
26+
- [x] Rename `filterOpenAiMeta``filterWidgetMeta` in `ToolPublicFieldOptions` and `src/mcp/server.ts`
27+
- [x] Update `stripOpenAiMeta` comment references in 6 tool files
28+
- [ ] Remove `openai/resultCanProduceWidget` (no MCP equivalent) once ChatGPT no longer needs it
29+
- [ ] Remove `openai/widgetAccessible` once ChatGPT fully supports `ui.visibility`
30+
31+
## Client-side (`src/web/`)
32+
33+
### Replace `window.openai` with MCP Apps SDK `App` instance
34+
- [x] Install `@modelcontextprotocol/ext-apps` in the web package
35+
- [x] `window.openai.theme``app.getHostContext()?.theme` (`src/web/src/utils/init-widget.tsx`, `src/web/src/hooks/use-open-ai-global.ts`)
36+
- [x] `window.openai.toolOutput = ...``app.ontoolresult` callback (`src/web/src/widgets/actor-run-widget.tsx:225`)
37+
- [x] `window.openai.callTool(...)``app.callServerTool(...)` (`src/web/src/pages/ActorRun/ActorRun.tsx:367`)
38+
- [x] `window.openai.openExternal({href})``app.openLink({url})` (`src/web/src/pages/ActorRun/ActorRun.tsx:464,472`)
39+
- [x] `window.openai.widgetState` / `setWidgetState` → plain React state (`src/web/src/hooks/use-widget-state.ts`) — no MCP equivalent yet
40+
- [x] Replace `src/web/src/hooks/use-open-ai-global.ts` hook with MCP Apps context
41+
- [x] Update `src/web/src/utils/mock-openai.ts` dev mock for new `App` API
42+
- [x] Remove/update `window.openai` type declarations
43+
44+
### Theming
45+
- [ ] Adopt MCP Apps CSS variables (`--color-background-primary`, `--color-text-primary`, etc.) from host context
46+
- [ ] Use `applyHostStyleVariables` / `applyDocumentTheme` utilities from ext-apps SDK
47+
48+
## Blocked upstream (not yet available in MCP Apps SDK)
49+
50+
| Feature | OpenAI API | Status |
51+
|---------|-----------|--------|
52+
| Tool invocation progress | `openai/toolInvocation/invoking` / `invoked` | Not yet implemented |
53+
| Widget description | `openai/widgetDescription` | Use `app.updateModelContext()` |
54+
| Widget state persistence | `widgetState` / `setWidgetState` | Use `localStorage` or server-side |
55+
| File operations | `uploadFile` / `getFileDownloadUrl` | Not yet implemented |
56+
| Modal management | `requestModal` / `requestClose` | Not yet implemented |
57+
| Open in app URL | `setOpenInAppUrl` | Not yet implemented |

0 commit comments

Comments
 (0)