MCP Apps Support
May 20, 2026 ยท View on GitHub
This guide shows how to use MCP Apps metadata with mcp_dart.
What Is Included
mcp_dart provides:
- Extension capability negotiation through
ClientCapabilities.extensionsandServerCapabilities.extensions - Typed helpers for MCP Apps metadata (
McpUiToolMeta,McpUiResourceMeta,McpUiCsp,McpUiPermissions) - Constants for the extension and MIME type (
mcpUiExtensionId,mcpUiResourceMimeType) - TypeScript-style server helpers (
registerAppTool,registerAppResource,getUiCapability)
Advertise Client Support
Hosts/clients can advertise MCP Apps support in initialize:
final client = McpClient(
const Implementation(name: 'my-host', version: '1.0.0'),
options: McpClientOptions(
capabilities: ClientCapabilities(
extensions: withMcpUiExtension(),
),
),
);
You can check negotiated support with typed helpers:
if (getUiCapability(client.getServerCapabilities())
?.supportsMimeType(mcpUiResourceMimeType) ??
false) {
// Server supports text/html;profile=mcp-app
}
Expose MCP Apps Metadata from a Server
Register a ui:// resource and attach _meta.ui metadata to both the tool and the UI content.
Use tool names that follow the MCP naming guidance (for example, weather_get_current).
Some hosts reject names containing /.
final server = McpServer(
const Implementation(name: 'weather-server', version: '1.0.0'),
options: McpServerOptions(
capabilities: ServerCapabilities(
resources: const ServerCapabilitiesResources(),
tools: const ServerCapabilitiesTools(),
extensions: withMcpUiExtension(),
),
),
);
const resourceUri = 'ui://weather/dashboard.html';
registerAppTool(
server,
'weather_get_current',
McpUiAppToolConfig(
description: 'Get current weather',
inputSchema: JsonSchema.object(
properties: {
'location': JsonSchema.string(),
},
required: ['location'],
),
meta: const {
'ui': {
'resourceUri': resourceUri,
'visibility': ['model', 'app'],
},
},
),
(args, extra) async {
final location = args['location'] as String;
const temperatureC = 22;
const condition = 'Partly Cloudy';
return CallToolResult(
content: [
TextContent(
text: 'Current weather for $location: $temperatureC C, $condition.',
),
const ResourceLink(
uri: resourceUri,
name: 'Weather Dashboard UI',
mimeType: mcpUiResourceMimeType,
),
],
structuredContent: {
'location': location,
'temperatureC': temperatureC,
'condition': condition,
},
);
},
);
registerAppResource(
server,
'Weather Dashboard UI',
resourceUri,
const McpUiAppResourceConfig(
description: 'UI resource for weather tool output',
meta: {
'ui': {
'csp': {
'connectDomains': ['https://api.example.com'],
},
'prefersBorder': true,
},
},
),
(uri, extra) async => ReadResourceResult(
contents: [
TextResourceContents(
uri: uri.toString(),
mimeType: mcpUiResourceMimeType,
text: '<!doctype html><html><body>Dashboard</body></html>',
meta: const McpUiResourceMeta(prefersBorder: true).toMeta(),
),
],
),
);
Typed Access to Incoming Metadata
final tools = await client.listTools();
for (final tool in tools.tools) {
final ui = tool.mcpUiMeta;
if (ui?.resourceUri != null) {
// Tool is associated with an MCP Apps UI resource
}
}
Resource, ResourceTemplate, and ResourceContents also expose mcpUiMeta helpers when _meta.ui is present.
Runnable examples
- TypeScript-style helper example:
example/mcp_apps_helpers_server.dart - Manual metadata example:
example/mcp_apps_metadata_server.dart
Inspect either example by letting the CLI launch it as a stdio MCP server:
mcp_dart inspect --tool weather_get_current --json-args '{"location":"Seoul"}' -- dart run example/mcp_apps_helpers_server.dart
mcp_dart inspect --resource ui://weather/dashboard.html -- dart run example/mcp_apps_helpers_server.dart
mcp_dart inspect --resource ui://weather/dashboard -- dart run example/mcp_apps_metadata_server.dart
Do not start a stdio server separately for these commands; stdio transports are process-owned, so the inspector spawns the server process. For an already-running server, expose a Streamable HTTP endpoint and connect to that endpoint from the host or inspector.
MCP hosts that understand MCP Apps metadata can use the same dart run example/... commands as stdio server commands.
Polished example patterns
Weather dashboard card
Use this pattern when a tool returns normal model-readable text plus a UI resource for hosts that can render MCP Apps:
- Register a
ui://weather/dashboard.htmlresource withmcpUiResourceMimeType. - Attach
McpUiResourceMetawithprefersBorderand CSP domains that match only the remote resources the HTML actually uses. - Return both
TextContentand aResourceLinkfrom the tool. - Put machine-readable values in
structuredContentso non-UI hosts still receive useful data.
The checked-in helper example demonstrates the text fallback, ResourceLink, structured content, and self-contained HTML portions of this pattern; tighten CSP entries to match your app before treating them as production-ready.
Form or approval UI
Use this pattern when the UI should collect confirmation before a side-effecting action:
- Keep the actual side effect in a tool call; the HTML resource should only present state and instructions.
- Use
visibility: ['app']for UI-only affordances when the model does not need the resource text. - Add
ToolAnnotations(destructiveHint: true)or other annotations to the tool where appropriate. - Make the tool handler validate all arguments again; never trust host-rendered form controls as the only validation layer.
Resource-link update flow
Use this pattern when tool results should point users to an updated resource:
- Return a
ResourceLinkwith a stable URI andmcpUiResourceMimeType. - Keep resource content idempotent and safe to re-read.
- If the UI depends on remote assets, include only the minimum required domains in
csp.resourceDomainsandcsp.connectDomains.
Host compatibility notes
MCP Apps metadata is extension-based, so hosts can differ in what they render:
- A host that does not advertise
io.modelcontextprotocol/uisupport should still receive useful text or structured content. - Some hosts reject tool names that contain
/; prefersnake_casenames such asweather_get_current. - Keep CSP entries explicit and minimal. Avoid wildcard domains in production examples.
- Treat
_meta.uias host-facing metadata, not as an authorization boundary. Server-side handlers must still enforce authentication and permissions. - Verify final rendering against each target host before claiming host-specific support.