docs: add tool visibility and restructure _meta.ui (#131)

* docs: add tool visibility and restructure _meta.ui

- Restructure tool metadata: `_meta["ui/resourceUri"]` → `_meta.ui.resourceUri`
- Add `visibility` array field: ["model"], ["apps"], or ["model", "apps"]
- Default ["model"] preserves standard MCP behavior
- ["apps"] enables widget-only tools hidden from agent
- Add McpUiToolMeta interface for type safety
- Add Design Decision #4 explaining approach vs OpenAI's two-field model

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* address PR review comments

- Change default visibility to ["model", "apps"]
- Add deprecation notice for flat ui/resourceUri format
- Add McpUiToolMeta and McpUiToolVisibility to spec.types.ts
- Improve tools/list and tools/call behavior wording

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: regenerate schemas for McpUiToolMeta types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: tool visibility and nested _meta.ui format

- Rename visibility "apps" → "app" in McpUiToolVisibility type
- Add _meta.ui.resourceUri nested format (deprecate flat format)
- Add getToolUiResourceUri() utility with backward compatibility
- Add visibility demo to system-monitor-server:
  - get-system-stats: visibility ["model"] with resourceUri
  - refresh-stats: visibility ["app"] (app-only polling)
- Update all example servers to use new _meta.ui format
- Add 11 unit tests for getToolUiResourceUri()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: regenerate schemas with updated dependencies

* feat: export McpUiToolMeta type and add type annotations

- Export McpUiToolMeta and McpUiToolVisibility types from types.ts
- Export corresponding Zod schemas (McpUiToolMetaSchema, McpUiToolVisibilitySchema)
- Add `as McpUiToolMeta` type annotations to all example servers
- Update docs/quickstart.md with proper typing

Ensures type safety for `_meta.ui` tool metadata across the codebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add server helpers and optional connect() transport

Add `src/server/` with convenience functions for registering MCP App tools
and resources:

- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`

The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update src/server/index.ts

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* feat: update McpUiAppToolConfig to support both _meta.ui and flat formats

* refactor: simplify server helper imports

* docs: add deprecation notice and improve resourceUri documentation

* feat: add backward compat normalization in registerAppTool

- If _meta.ui.resourceUri is set, also set legacy flat key
- If legacy flat key is set, also set _meta.ui.resourceUri
- Preserves existing visibility when merging
- Does not overwrite if both formats already set

* refactor: avoid mutating config arg in registerAppTool

* style: format with prettier

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
This commit is contained in:
Anton Pidkuiko 2025-12-17 04:47:40 +07:00 • committed by GitHub
commit 37aab03de0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -245,21 +245,35 @@ Example:
### Resource Discovery
Tools are associated with UI resources through the `_meta` field:
Tools are associated with UI resources through the `_meta.ui` field:
```typescript
interface McpUiToolMeta {
/** URI of UI resource for rendering tool results */
resourceUri?: string;
/**
* Who can access this tool. Default: ["model", "app"]
* - "model": Tool visible to and callable by the agent
* - "app": Tool callable by the app from this server only
*/
visibility?: Array<"model" | "app">;
}
interface Tool {
name: string;
description: string;
inputSchema: object;
_meta?: {
// Required: URI of the UI resource to use for rendering
ui?: McpUiToolMeta;
/** @deprecated Use `ui.resourceUri` instead. Will be removed before GA. */
"ui/resourceUri"?: string;
};
}
```
Example:
> **Deprecation notice:** The flat `_meta["ui/resourceUri"]` format is deprecated. Use `_meta.ui.resourceUri` instead. The deprecated format will be removed before GA.
Example (tool visible to both model and app):
```json
{
@ -272,20 +286,48 @@ Example:
}
},
"_meta": {
"ui/resourceUri": "ui://weather-server/dashboard-template"
"ui": {
"resourceUri": "ui://weather-server/dashboard-template",
"visibility": ["model", "app"]
}
}
}
```
Example (app-only tool, hidden from model):
```json
{
"name": "refresh_dashboard",
"description": "Refresh dashboard data",
"inputSchema": { "type": "object" },
"_meta": {
"ui": {
"resourceUri": "ui://weather-server/dashboard-template",
"visibility": ["app"]
}
}
}
```
#### Behavior:
- If `ui/resourceUri` is present and host supports MCP Apps, host renders tool results using the specified UI resource
- If `ui.resourceUri` is present and host supports MCP Apps, host renders tool results using the specified UI resource
- If host does not support MCP Apps, tool behaves as standard tool (text-only fallback)
- Resource MUST exist on the server
- Host MUST use `resources/read` to fetch the referenced resource URI.
- Host MUST use `resources/read` to fetch the referenced resource URI
- Host MAY prefetch and cache UI resource content for performance optimization
- Since UI resources are primarily discovered through tool metadata, Servers MAY omit UI-only resources from `resources/list` and `notifications/resources/list_changed`
#### Visibility:
- `visibility` defaults to `["model", "app"]` if omitted
- `"model"`: Tool is visible to and callable by the agent
- `"app"`: Tool is callable by the app from the same server connection only
- **tools/list behavior:** Host MUST NOT include tools in the agent's tool list when their visibility does not include `"model"` (e.g., `visibility: ["app"]`)
- **tools/call behavior:** Host MUST reject `tools/call` requests from apps for tools that don't include `"app"` in visibility
- Cross-server tool calls are always blocked for app-only tools
#### Benefits:
- **Performance:** Host can preload templates before tool execution
@ -879,7 +921,7 @@ sequenceDiagram
autonumber
S -->> H: resources/list (includes ui:// resources)
S -->> H: tools/list (includes tools with ui/resourceUri metadata)
S -->> H: tools/list (includes tools with _meta.ui metadata)
```
#### 2. UI Initialization (Desktop/Native Hosts)
@ -893,7 +935,7 @@ sequenceDiagram
autonumber
par UI Tool call
H ->> S: tools/call to Tool with ui/resourceUri metadata
H ->> S: tools/call to Tool with _meta.ui metadata
and UI initialization
alt Desktop/Native hosts
H ->> H: Render Guest UI in an iframe (HTML from the ui:// resource)
@ -1079,7 +1121,7 @@ await client.callTool("get_weather", { location: "New York" });
This pattern enables interactive, self-updating widgets.
Note: The called tool may not appear in `tools/list` responses. MCP servers MAY expose private tools specifically designed for UI interaction that are not visible to the agent. UI implementations SHOULD attempt to call tools by name regardless of discoverability. The specification for Private Tools will be covered in a future SEP.
Note: Tools with `visibility: ["app"]` are hidden from the agent but remain callable by apps via `tools/call`. This enables UI-only interactions (refresh buttons, form submissions) without exposing implementation details to the model. See the Visibility section under Resource Discovery for details.
### Client\<\>Server Capability Negotiation
@ -1132,7 +1174,7 @@ if (hasUISupport) {
description: "Get weather with interactive dashboard",
inputSchema: { /* ... */ },
_meta: {
"ui/resourceUri": "ui://weather-server/dashboard"
ui: { resourceUri: "ui://weather-server/dashboard" }
}
});
} else {
@ -1241,6 +1283,24 @@ This proposal synthesizes feedback from the UI CWG and MCP-UI community, host im
- **Inline styles in tool results:** Rejected; separating theming from data enables caching and updates
- **CSS-in-JS injection:** Rejected; framework-specific and security concerns with injected code
#### 5. Tool Visibility via Metadata
**Decision:** Use `_meta.ui.visibility` array to control tool accessibility between model and app.
**Rationale:**
- Nested `_meta.ui` structure groups all UI-related metadata cleanly
- Array format (`["model", "app"]`) allows flexible combinations
- Default `["model", "app"]` allows both agent and app to access tools
- `"app"` scope is per-server, preventing cross-server tool calls
- Cleaner than OpenAI's two-field approach (`widgetAccessible` + `visibility`)
**Alternatives considered:**
- **Two separate fields:** OpenAI uses `widgetAccessible` and `visibility` separately. Rejected as redundant; single `visibility` array covers all cases.
- **Boolean `private` flag:** Simpler but less flexible; doesn't express model-only tools.
- **Flat `ui/visibility` key:** Rejected in favor of nested structure for consistency with future `_meta.ui` fields.
### Backward Compatibility
The proposal builds on the existing core protocol. There are no incompatibilities.