From f93df7e172e5b58760771196f27a1c6ba97d26c8 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Jun 2026 13:28:47 -0700
Subject: [PATCH] Add MCP Apps extension support (typed metadata, attribute,
and helpers) (#1484)
---
ModelContextProtocol.slnx | 2 +
docs/concepts/apps/apps.md | 191 +++++++
docs/concepts/index.md | 6 +
docs/concepts/toc.yml | 6 +-
docs/list-of-diagnostics.md | 1 +
samples/WeatherAppServer/Program.cs | 34 ++
samples/WeatherAppServer/README.md | 41 ++
samples/WeatherAppServer/UsCity.cs | 64 +++
.../WeatherAppServer/WeatherAppServer.csproj | 20 +
samples/WeatherAppServer/WeatherResources.cs | 25 +
samples/WeatherAppServer/WeatherTools.cs | 64 +++
samples/WeatherAppServer/WeatherUI.png | Bin 0 -> 367532 bytes
.../WeatherAppServer/ui/weather-forecast.html | 257 +++++++++
src/Common/Experimentals.cs | 20 +
...odelContextProtocol.Extensions.Apps.csproj | 44 ++
.../Server/McpAppUiAttribute.cs | 59 +++
.../Server/McpApps.cs | 265 ++++++++++
.../Server/McpAppsBuilderExtensions.cs | 71 +++
.../Server/McpAppsJsonContext.cs | 19 +
.../Server/McpUiClientCapabilities.cs | 26 +
.../Server/McpUiResourceCsp.cs | 49 ++
.../Server/McpUiResourceMeta.cs | 50 ++
.../Server/McpUiResourcePermissions.cs | 27 +
.../Server/McpUiToolMeta.cs | 41 ++
.../Server/McpUiToolVisibility.cs | 25 +
.../ModelContextProtocol.Tests.csproj | 1 +
.../Server/McpAppsTests.cs | 489 ++++++++++++++++++
27 files changed, 1896 insertions(+), 1 deletion(-)
create mode 100644 docs/concepts/apps/apps.md
create mode 100644 samples/WeatherAppServer/Program.cs
create mode 100644 samples/WeatherAppServer/README.md
create mode 100644 samples/WeatherAppServer/UsCity.cs
create mode 100644 samples/WeatherAppServer/WeatherAppServer.csproj
create mode 100644 samples/WeatherAppServer/WeatherResources.cs
create mode 100644 samples/WeatherAppServer/WeatherTools.cs
create mode 100644 samples/WeatherAppServer/WeatherUI.png
create mode 100644 samples/WeatherAppServer/ui/weather-forecast.html
create mode 100644 src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.csproj
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpAppUiAttribute.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpApps.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpAppsBuilderExtensions.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpAppsJsonContext.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiClientCapabilities.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiResourceCsp.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiResourceMeta.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiResourcePermissions.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiToolMeta.cs
create mode 100644 src/ModelContextProtocol.Extensions.Apps/Server/McpUiToolVisibility.cs
create mode 100644 tests/ModelContextProtocol.Tests/Server/McpAppsTests.cs
diff --git a/ModelContextProtocol.slnx b/ModelContextProtocol.slnx
index 2eebe72..abfb430 100644
--- a/ModelContextProtocol.slnx
+++ b/ModelContextProtocol.slnx
@@ -51,6 +51,7 @@
+
@@ -67,6 +68,7 @@
+
diff --git a/docs/concepts/apps/apps.md b/docs/concepts/apps/apps.md
new file mode 100644
index 0000000..285f48d
--- /dev/null
+++ b/docs/concepts/apps/apps.md
@@ -0,0 +1,191 @@
+---
+title: MCP Apps
+author: mikekistler
+description: How to use the MCP Apps extension to deliver interactive UIs from MCP servers.
+uid: apps
+---
+
+# MCP Apps
+
+[MCP Apps] is an extension to the Model Context Protocol that enables MCP servers to deliver interactive user interfaces — dashboards, forms, visualizations, and more — directly inside conversational AI clients.
+
+[MCP Apps]: https://modelcontextprotocol.io/extensions/apps/overview
+
+> [!IMPORTANT]
+> MCP Apps support is experimental. All types are marked with `[Experimental("MCPEXP003")]` and require suppressing that diagnostic to use.
+
+## Installation
+
+MCP Apps is provided in the `ModelContextProtocol.Extensions.Apps` package, which layers on top of the core SDK:
+
+```shell
+dotnet add package ModelContextProtocol.Extensions.Apps
+```
+
+## Overview
+
+The MCP Apps extension introduces the concept of **UI resources** — HTML pages served by the MCP server that a client can display alongside the conversation. Tools can be associated with a UI resource so the client knows which interface to show when a tool is called.
+
+The key concepts are:
+
+- **UI capability negotiation** — Client and server declare support via `extensions["io.modelcontextprotocol/ui"]`
+- **UI resources** — HTML content served with the MIME type `text/html;profile=mcp-app`
+- **Tool UI metadata** — Tools declare their associated UI resource in `_meta.ui`
+
+## Associating tools with UI resources
+
+### Using the builder extension (recommended)
+
+The simplest approach is to apply `[McpAppUi]` attributes to your tool methods and call `WithMcpApps()` on the server builder:
+
+```csharp
+[McpServerToolType]
+public class WeatherTools
+{
+ [McpServerTool, Description("Get current weather for a location")]
+ [McpAppUi(ResourceUri = "ui://weather/view.html")]
+ public static string GetWeather(string location) => $"Weather for {location}";
+
+ [McpServerTool, Description("Get forecast (model-only tool)")]
+ [McpAppUi(ResourceUri = "ui://weather/forecast.html", Visibility = [McpUiToolVisibility.Model])]
+ public static string GetForecast(string location) => $"Forecast for {location}";
+}
+```
+
+```csharp
+builder.Services.AddMcpServer()
+ .WithTools()
+ .WithMcpApps();
+```
+
+The `WithMcpApps()` call registers a post-configuration step that processes all registered tools and applies `[McpAppUi]` attribute metadata to their `_meta.ui` field automatically.
+
+### Using the attribute with manual processing
+
+If you create tools manually (without `WithMcpApps()`), you can still use the attribute and process tools explicitly:
+
+```csharp
+var tools = new[]
+{
+ McpServerTool.Create(typeof(WeatherTools).GetMethod(nameof(WeatherTools.GetWeather))!),
+ McpServerTool.Create(typeof(WeatherTools).GetMethod(nameof(WeatherTools.GetForecast))!),
+};
+
+McpApps.ApplyAppUiAttributes(tools);
+```
+
+### Using the programmatic API
+
+For full control, use `McpApps.SetAppUi` to set UI metadata directly:
+
+```csharp
+var tool = McpServerTool.Create((string location) => $"Weather for {location}");
+
+McpApps.SetAppUi(tool, new McpUiToolMeta
+{
+ ResourceUri = "ui://weather/view.html",
+ Visibility = [McpUiToolVisibility.Model, McpUiToolVisibility.App],
+});
+```
+
+## Checking client capabilities
+
+During a session, you can check whether the connected client supports MCP Apps:
+
+```csharp
+[McpServerTool, Description("Get weather")]
+[McpAppUi(ResourceUri = "ui://weather/view.html")]
+public static string GetWeather(McpServer server, string location)
+{
+ var uiCapability = McpApps.GetUiCapability(server.ClientCapabilities);
+ if (uiCapability is not null)
+ {
+ // Client supports MCP Apps — the UI will be displayed
+ }
+
+ return $"Weather for {location}";
+}
+```
+
+## Tool visibility
+
+The `Visibility` property controls which principals can invoke the tool:
+
+| Value | Meaning |
+| - | - |
+| `McpUiToolVisibility.Model` | Only the LLM can call this tool |
+| `McpUiToolVisibility.App` | Only the app UI can call this tool |
+| Both (or null/empty) | Both the model and app can call the tool (default) |
+
+## UI resources
+
+UI resources are HTML pages registered with the MCP server using the `ui://` URI scheme and the `text/html;profile=mcp-app` MIME type. The `McpUiResourceMeta` type provides metadata for these resources, including:
+
+- **CSP (Content Security Policy)** — Controls allowed origins for network requests and resource loads
+- **Permissions** — Sandbox permissions (scripts, forms, popups, etc.)
+- **Domain** — Dedicated origin for OAuth flows and CORS
+- **PrefersBorder** — Whether the host should render a visual border
+
+## App-only tools
+
+Tools with `Visibility = [McpUiToolVisibility.App]` are not visible to the LLM — they are intended only for use by the app UI.
+This is useful for tools that serve UI interaction (button handlers, form submissions) without cluttering the model's tool list:
+
+```csharp
+[McpServerTool, Description("Submit the weather form")]
+[McpAppUi(ResourceUri = "ui://weather/view.html", Visibility = [McpUiToolVisibility.App])]
+public static string SubmitWeatherForm(string city) => GetWeatherHtml(city);
+```
+
+## Graceful degradation
+
+Not all clients support MCP Apps. Use `GetUiCapability` to detect support and return text-only content as a fallback:
+
+```csharp
+[McpServerTool, Description("Get weather")]
+[McpAppUi(ResourceUri = "ui://weather/view.html")]
+public static string GetWeather(McpServer server, string location)
+{
+ var uiCapability = McpApps.GetUiCapability(server.ClientCapabilities);
+ if (uiCapability is null)
+ {
+ // Client doesn't support MCP Apps — return plain text
+ return $"Current weather for {location}: 72°F, sunny";
+ }
+
+ // Client supports MCP Apps — the UI resource will be displayed
+ return $"Weather data for {location} loaded into UI";
+}
+```
+
+## Display modes
+
+The MCP Apps spec defines display modes (`inline`, `fullscreen`, `pip`) that control how the host renders the UI. Display mode is negotiated between the client and server during capability exchange and is not set per-tool — it depends on the host implementation.
+
+## Host theming
+
+Hosts pass standardized CSS custom properties (e.g., `--color-background-primary`, `--color-text-primary`) to app iframes. Your HTML can reference these variables to automatically match the host's theme without any server-side configuration.
+
+See the [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) for the full list of CSS variables.
+
+## Single-file HTML bundling
+
+The default Content Security Policy restricts external script and style loads. For production apps, bundle all JavaScript and CSS into a single HTML file using tools like [vite-plugin-singlefile](https://github.com/richardtallent/vite-plugin-singlefile). For simple apps, inline `
+