Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ModelContextProtocol.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<Project Path="samples/QuickstartClient/QuickstartClient.csproj" />
<Project Path="samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj" />
<Project Path="samples/TestServerWithHosting/TestServerWithHosting.csproj" />
<Project Path="samples/WeatherAppServer/WeatherAppServer.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="Directory.Build.props" />
Expand All @@ -66,6 +67,7 @@
<Project Path="src/ModelContextProtocol.Analyzers/ModelContextProtocol.Analyzers.csproj" />
<Project Path="src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj" />
<Project Path="src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj" />
<Project Path="src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.csproj" />
<Project Path="src/ModelContextProtocol/ModelContextProtocol.csproj" />
</Folder>
<Folder Name="/tests/">
Expand Down
191 changes: 191 additions & 0 deletions docs/concepts/apps/apps.md
Original file line number Diff line number Diff line change
@@ -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/specification/draft/extensions/apps

> [!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<WeatherTools>()
.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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few topics from the spec that would be worth covering here for parity with the TS/community SDK docs:

  • Tool visibility for app-only tools — the ["app"] visibility pattern (tools the LLM never sees, only the iframe can call) is one of the most powerful patterns in MCP Apps and isn't called out as a use case.
  • Display modes (inline, fullscreen, pip) — not mentioned anywhere, and there are no types for them.
  • Host theming via CSS variables — the spec defines ~80 standardized CSS custom properties (--color-background-primary etc.) that hosts pass to apps. Worth at least linking to.
  • Graceful degradation — show a snippet where the tool returns a text-only CallToolResult when GetUiCapability returns null, so server authors know the recommended fallback pattern.
  • Single-file HTML bundling — the spec recommends bundling all JS/CSS into one HTML file (e.g. via vite-plugin-singlefile) because the default CSP makes external assets painful. The sample uses inline HTML so this Just Works, but real apps will hit this.


## 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://modelcontextprotocol.io/specification/draft/extensions/apps) 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/niccolodipi/vite-plugin-singlefile). For simple apps, inline `<script>` and `<style>` tags work directly.

## Constants

The <xref:ModelContextProtocol.Extensions.Apps.McpApps> class provides constants for protocol values:

| Constant | Value | Usage |
| - | - | - |
| `McpApps.ResourceMimeType` | `text/html;profile=mcp-app` | MIME type for UI resources |
| `McpApps.ExtensionId` | `io.modelcontextprotocol/ui` | Key in `extensions` capability dictionary |

## Serialization

MCP Apps types use source-generated JSON serialization for Native AOT compatibility. Use `McpApps.SerializerOptions` when serializing extension types:

```csharp
var json = JsonSerializer.Serialize(toolMeta, McpApps.SerializerOptions);
var deserialized = JsonSerializer.Deserialize<McpUiToolMeta>(json, McpApps.SerializerOptions);
```
6 changes: 6 additions & 0 deletions docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ Install the SDK and build your first MCP client and server.
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
| [HTTP Context](httpcontext/httpcontext.md) | Learn how to access the underlying `HttpContext` for a request. |
| [MCP Server Handler Filters](filters.md) | Learn how to add filters to the handler pipeline. Filters let you wrap the original handler with additional functionality. |

### Extensions

| Title | Description |
| - | - |
| [MCP Apps](apps/apps.md) | Learn how to use the MCP Apps extension to deliver interactive UIs from MCP servers. |
| [Identity and Roles](identity/identity.md) | Learn how to access caller identity and roles in MCP tool, prompt, and resource handlers. |
6 changes: 5 additions & 1 deletion docs/concepts/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,9 @@ items:
uid: httpcontext
- name: Filters
uid: filters
- name: Extensions
items:
- name: MCP Apps
uid: apps
- name: Identity and Roles
uid: identity
uid: identity
1 change: 1 addition & 0 deletions docs/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T
| :------------ | :---------- |
| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
| `MCPEXP002` | Experimental SDK APIs unrelated to the MCP specification itself, including subclassing `McpClient`/`McpServer` (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)) and `RunSessionHandler`, which may be removed or change signatures in a future release (consider using `ConfigureSessionOptions` instead). |
| `MCPEXP003` | Experimental MCP Apps extension APIs. MCP Apps is the first official MCP extension (`io.modelcontextprotocol/ui`), enabling servers to deliver interactive UIs inside AI clients (see [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx)). |

## Obsolete APIs

Expand Down
34 changes: 34 additions & 0 deletions samples/WeatherAppServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Extensions.Apps;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(_ =>
{
var client = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("WeatherAppServer", "1.0"));
return client;
});

builder.Services
.AddMcpServer(options =>
{
options.ServerInfo = new Implementation { Name = "weather-app-server", Version = "1.0.0" };
options.Capabilities = new ServerCapabilities
{
Tools = new ToolsCapability(),
Resources = new ResourcesCapability(),
};
})
.WithHttpTransport()
.WithTools<WeatherTools>()
.WithResources<WeatherResources>()
.WithMcpApps();

var app = builder.Build();
app.MapMcp("/mcp");
app.Run();
41 changes: 41 additions & 0 deletions samples/WeatherAppServer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Weather App Server

An MCP server that demonstrates the **MCP Apps** extension by serving an interactive weather forecast UI alongside weather tools.

## What it shows

- **`[McpAppUi]` attribute** — declaratively associates a UI resource with a tool
- **`WithMcpApps()`** — builder extension that processes `[McpAppUi]` attributes
- **UI resource** — an HTML page served via `McpServerResource` with MIME type `text/html;profile=mcp-app`
- **Structured content** — tool results include `StructuredContent` for the UI to render

## Running

```bash
dotnet run
```

The server starts on `http://localhost:5000` by default. Connect any MCP Apps-capable client to the `/mcp` endpoint.

Then prompt that will cause the LLM to request the use of the "weather_ui" tool.
A general prompt like "What's the weather?" will probably work, but if not you could try explicitly requesting the tool
with something like "@weather_ui". This should load the Weather App UI in an iFrame that you can then interact with
to get the weather forecast for a number of US cities.

![UI of the Weather Forecast MCP App in VS Code](./WeatherUI.png)

## Tools

| Tool | Description |
|------|-------------|
| `weather_ui` | Opens the weather forecast UI |
| `weather_forecast` | Gets a multi-period forecast from the National Weather Service for a US city |

Both tools are linked to the `ui://weather-app/forecast` resource via the `[McpAppUi]` attribute.

## Resources

| URI | Description |
|-----|-------------|
| `ui://weather-app/forecast` | Interactive weather forecast HTML UI |
| `data://weather-app/us-cities` | JSON list of supported US cities |
64 changes: 64 additions & 0 deletions samples/WeatherAppServer/UsCity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Text.Json.Serialization;

[JsonConverter(typeof(JsonStringEnumConverter<UsCity>))]
public enum UsCity
{
[JsonStringEnumMemberName("Albuquerque, NM")] AlbuquerqueNM,
[JsonStringEnumMemberName("Atlanta, GA")] AtlantaGA,
[JsonStringEnumMemberName("Austin, TX")] AustinTX,
[JsonStringEnumMemberName("Boston, MA")] BostonMA,
[JsonStringEnumMemberName("Charlotte, NC")] CharlotteNC,
[JsonStringEnumMemberName("Chicago, IL")] ChicagoIL,
[JsonStringEnumMemberName("Dallas, TX")] DallasTX,
[JsonStringEnumMemberName("Denver, CO")] DenverCO,
[JsonStringEnumMemberName("Houston, TX")] HoustonTX,
[JsonStringEnumMemberName("Indianapolis, IN")] IndianapolisIN,
[JsonStringEnumMemberName("Las Vegas, NV")] LasVegasNV,
[JsonStringEnumMemberName("Los Angeles, CA")] LosAngelesCA,
[JsonStringEnumMemberName("Miami, FL")] MiamiFL,
[JsonStringEnumMemberName("Minneapolis, MN")] MinneapolisMN,
[JsonStringEnumMemberName("Nashville, TN")] NashvilleTN,
[JsonStringEnumMemberName("New York, NY")] NewYorkNY,
[JsonStringEnumMemberName("Orlando, FL")] OrlandoFL,
[JsonStringEnumMemberName("Philadelphia, PA")] PhiladelphiaPA,
[JsonStringEnumMemberName("Phoenix, AZ")] PhoenixAZ,
[JsonStringEnumMemberName("Portland, OR")] PortlandOR,
[JsonStringEnumMemberName("Salt Lake City, UT")] SaltLakeCityUT,
[JsonStringEnumMemberName("San Diego, CA")] SanDiegoCA,
[JsonStringEnumMemberName("San Francisco, CA")] SanFranciscoCA,
[JsonStringEnumMemberName("Seattle, WA")] SeattleWA,
[JsonStringEnumMemberName("Washington, DC")] WashingtonDC,
}

public static class UsCityData
{
public static (double Latitude, double Longitude) GetCoordinates(UsCity city) => city switch
{
UsCity.AlbuquerqueNM => (35.0844, -106.6504),
UsCity.AtlantaGA => (33.7490, -84.3880),
UsCity.AustinTX => (30.2672, -97.7431),
UsCity.BostonMA => (42.3601, -71.0589),
UsCity.CharlotteNC => (35.2271, -80.8431),
UsCity.ChicagoIL => (41.8781, -87.6298),
UsCity.DallasTX => (32.7767, -96.7970),
UsCity.DenverCO => (39.7392, -104.9903),
UsCity.HoustonTX => (29.7604, -95.3698),
UsCity.IndianapolisIN => (39.7684, -86.1581),
UsCity.LasVegasNV => (36.1699, -115.1398),
UsCity.LosAngelesCA => (34.0522, -118.2437),
UsCity.MiamiFL => (25.7617, -80.1918),
UsCity.MinneapolisMN => (44.9778, -93.2650),
UsCity.NashvilleTN => (36.1627, -86.7816),
UsCity.NewYorkNY => (40.7128, -74.0060),
UsCity.OrlandoFL => (28.5383, -81.3792),
UsCity.PhiladelphiaPA => (39.9526, -75.1652),
UsCity.PhoenixAZ => (33.4484, -112.0740),
UsCity.PortlandOR => (45.5152, -122.6784),
UsCity.SaltLakeCityUT => (40.7608, -111.8910),
UsCity.SanDiegoCA => (32.7157, -117.1611),
UsCity.SanFranciscoCA => (37.7749, -122.4194),
UsCity.SeattleWA => (47.6062, -122.3321),
UsCity.WashingtonDC => (38.9072, -77.0369),
_ => throw new ArgumentOutOfRangeException(nameof(city))
};
}
Loading
Loading