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
145 changes: 145 additions & 0 deletions docs/concepts/apps/apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
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

## Constants

The <xref:ModelContextProtocol.Server.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
33 changes: 33 additions & 0 deletions samples/WeatherAppServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.AspNetCore;
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))
};
}
20 changes: 20 additions & 0 deletions samples/WeatherAppServer/WeatherAppServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Suppress experimental MCP Apps warning -->
<NoWarn>$(NoWarn);MCPEXP003</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ModelContextProtocol.AspNetCore\ModelContextProtocol.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\ModelContextProtocol.Extensions.Apps\ModelContextProtocol.Extensions.Apps.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="ui\*.html" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions samples/WeatherAppServer/WeatherResources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;

[McpServerResourceType]
public sealed class WeatherResources
{
private static readonly string UiDir = Path.Combine(AppContext.BaseDirectory, "ui");

[McpServerResource(UriTemplate = "ui://weather-app/forecast", Name = "weather-forecast-ui", MimeType = McpApps.ResourceMimeType)]
[Description("Interactive weather forecast UI with city picker")]
public static string GetWeatherForecastUi() => File.ReadAllText(Path.Combine(UiDir, "weather-forecast.html"));

[McpServerResource(UriTemplate = "data://weather-app/us-cities", Name = "us-cities", MimeType = "application/json")]
[Description("List of supported US cities for weather forecasts")]
public static string GetUsCities()
{
var options = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter<UsCity>() } };
var cities = Enum.GetValues<UsCity>().Select(c => JsonSerializer.Serialize(c, options).Trim('"')).Order().ToList();
return JsonSerializer.Serialize(cities);
}
}
Loading
Loading