anthropics / anthropics/claude-ai-mcp

MCP Apps: plugin-provided MCP servers never render widgets — the bundled Code-tab engine doesn't advertise io.modelcontextprotocol/ui

Ouverte
#830 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Aucune donnée de langage
Étoiles
471
Forks
76
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

## MCP Apps widgets can't be shipped via a plugin: plugin-provided servers are connected by the bundled Code-tab engine, which doesn't implement `io.modelcontextprotocol/ui`

### Summary

In Claude Desktop, whether an MCP App renders depends on **how the server was registered**, not on which tab it is called from. Two different clients inside the same application connect to MCP servers, and only one of them implements the MCP Apps extension:

| Registered via | Connection owned by | Advertises `io.modelcontextprotocol/ui` | Widget renders |
|---|---|---|---|
| `claude_desktop_config.json` | Desktop shell (`claude-ai 0.1.0`, `local-agent-mode- 1.0.0`) | Yes | Yes, including when called from the Code tab |
| Plugin (marketplace install) | bundled Code-tab engine (`claude-code 2.1.219` / `2.1.220` / `2.1.226`) | No | No |
| Project-scoped `.mcp.json` | bundled Code-tab engine | No | No |

The consequence: **an MCP App cannot be distributed through the plugin system at all**, on any tab. Plugins are the documented way to distribute MCP servers to other people, and they are the one registration path whose connections can't render an App.

In every case the tool call itself succeeds and returns its normal result, so the failure is silent: the tool works, no widget appears, and nothing reports why.

### Environment

- Claude Desktop on Linux (Electron/GTK)
- Desktop's bundled Code-tab engine at `~/.config/Claude/claude-code/2.1.219/claude`, also observed as `2.1.220` and `2.1.226`
- Desktop shell at `/usr/lib/claude-desktop/resources/app.asar`
- Plugin installed from a git-based marketplace source (`marketplace.json` → `github`), not a zip upload

### Evidence: which client connects to which server

Verbatim from the server's own log, which records each connecting client's `clientInfo` and capabilities at `initialize`. Same machine, same server source, same day. Across the entire session, no `claude-code` connection ever advertised the extension, and no shell connection ever failed to.

**Registered in `claude_desktop_config.json`.** Two shell clients connect within milliseconds of each other (Chat and Cowork both read this file). Both advertise the extension, and the UI resource is subsequently fetched, meaning the widget renders:
```
2026-08-10T19:25:26.308Z initialized by local-agent-mode-demo-mcp-app 1.0.0
2026-08-10T19:25:26.308Z client capabilities: {"roots":{"listChanged":true},"extensions":{"io.modelcontextprotocol/ui":{"mimeTypes":["text/html;profile=mcp-app"]}}}
2026-08-10T19:25:26.894Z initialized by claude-ai 0.1.0
2026-08-10T19:25:26.894Z client capabilities: {"extensions":{"io.modelcontextprotocol/ui":{"mimeTypes":["text/html;profile=mcp-app"]}}}
2026-08-10T19:26:17.827Z tool call: render_widget
2026-08-10T19:26:18.003Z resource read: ui://demo-mcp-app/widget.html
```

**The identical server installed as a plugin.** Only the bundled engine ever connects. No `extensions` key at all, and no resource read ever follows a tool call:
```
2026-08-10T19:53:59.005Z initialized by claude-code 2.1.219
2026-08-10T19:53:59.005Z client capabilities: {"elicitation":{"form":{}},"roots":{"listChanged":true}}
2026-08-10T19:54:04.573Z tool call: render_widget
```

**The identical server in a project-scoped `.mcp.json`.** Same engine, same result, which shows this isn't specific to plugin packaging:
```
2026-08-10T18:13:19.143Z initialized by claude-code 2.1.226
2026-08-10T18:13:19.144Z client capabilities: {"elicitation":{"form":{}},"roots":{"listChanged":true}}
```

Notably, the config-registered server was called from a **Code tab** session and rendered correctly, because the shell owns that connection. The tab is not the variable; the registration path is.

### Evidence: the extension is absent from the engine build

Comparing the two shipped binaries on the same machine indicates this isn't a capability withheld at negotiation time, but an implementation that isn't present in the engine:

| String | bundled Code-tab engine 2.1.219 | Desktop shell `app.asar` |
|---|---|---|
| `io.modelcontextprotocol/ui` | 0 | 1 |
| `profile=mcp-app` | 0 | 3 |
| `resourceUri` | 0 | 5 |

As a control for the method, strings known to be in use appear normally in the same engine binary: `elicitation` (106), `modelcontextprotocol` (15), `mcpServers` (82). So the binary is greppable in plaintext and the zeroes are meaningful. (`mcp-app` returns 2 hits in the engine, but both are `mcp-approval-persist-*` telemetry keys, unrelated.)

### Repro

1. Build a minimal MCP server exposing one tool whose definition carries `_meta.ui.resourceUri`, with the resource served as `text/html;profile=mcp-app`.
2. Register it in `claude_desktop_config.json`, restart Claude Desktop, and call the tool from a Code tab session. The widget renders.
3. Package the identical server as a plugin (`.claude-plugin/plugin.json` plus a plugin-root `.mcp.json`), publish it via a `marketplace.json`, install it with `/plugin install`, and call the tool from the same Code tab session. The tool returns normally and no widget renders.
4. Compare the two `initialize` handshakes. The plugin path's client sends no `extensions.io.modelcontextprotocol/ui`.

Calling both tools back to back in a single session, by their distinct fully-qualified names, makes the difference unambiguous.

### Expected

A plugin-provided MCP server should be able to render an MCP App, since plugins are the supported mechanism for distributing MCP servers and nothing about the server differs between the two registration paths.

If this is a deliberate rollout stage rather than a bug, documenting it in the MCP Apps and plugin references would save others a great deal of time. The only symptom is a widget that silently doesn't appear, which reads as a packaging error: we spent a day eliminating symlink handling, path substitution, workspace trust, marketplace source types, and cross-platform `node_modules` before finding the cause in a capability string.

### Related but distinct

Several MCP Apps rendering issues are already open. All describe negotiation succeeding and rendering still failing, one step downstream of this, where the capability is never advertised at all:

- anthropics/claude-ai-mcp#165 — widget never renders in Claude Desktop, iframe handshake never initiated despite a successful tool call
- anthropics/claude-ai-mcp#471 — widget never renders on Claude Web for a custom remote connector
- anthropics/claude-ai-mcp#236 — iframes don't render in Cowork `deploymentMode: "3p"` despite identical protocol negotiation
- modelcontextprotocol/ext-apps#671 — capability negotiates, resource is fetched, still no render

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.