Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions ModelContextProtocol.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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
20 changes: 20 additions & 0 deletions src/Common/Experimentals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ internal static class Experimentals
/// </summary>
public const string Extensions_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001";

/// <summary>
/// Diagnostic ID for experimental MCP Apps extension APIs.
/// </summary>
/// <remarks>
/// MCP Apps is the first official MCP extension (<c>"io.modelcontextprotocol/ui"</c>), enabling
/// servers to deliver interactive UIs inside AI clients. This uses a dedicated diagnostic ID
/// so that users can suppress it independently from other experimental APIs.
/// </remarks>
public const string Apps_DiagnosticId = "MCPEXP003";

/// <summary>
/// Message for the experimental MCP Apps extension APIs.
/// </summary>
public const string Apps_Message = "The MCP Apps extension is experimental and subject to change as the specification evolves.";

/// <summary>
/// URL for the experimental MCP Apps extension APIs.
/// </summary>
public const string Apps_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp003";

/// <summary>
/// Diagnostic ID for experimental SDK APIs unrelated to the MCP specification,
/// such as subclassing <c>McpClient</c>/<c>McpServer</c> or referencing <c>RunSessionHandler</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ options.OpenWorld is not null ||
// Auto-detect async methods and mark with taskSupport = "optional" unless explicitly configured.
// This enables implicit task support for async tools: clients can choose to invoke them
// synchronously (wait for completion) or as a task (receive taskId, poll for result).
if (function.UnderlyingMethod is not null &&
if (function.UnderlyingMethod is not null &&
IsAsyncMethod(function.UnderlyingMethod) &&
tool.Execution?.TaskSupport is null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0;net9.0;net8.0;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<PackageId>ModelContextProtocol.Extensions.Apps</PackageId>
<Description>MCP Apps extension for the .NET Model Context Protocol (MCP) SDK</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<!-- Suppress the experimental MCP Apps warning -->
<NoWarn>$(NoWarn);MCPEXP003</NoWarn>
<!-- New package with no published baseline yet -->
<EnablePackageValidation>false</EnablePackageValidation>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<!-- CS0436: Allow ObsoleteAttribute to be redefined internally -->
<NoWarn>$(NoWarn);CS0436</NoWarn>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Common\Experimentals.cs" Link="Experimentals.cs" />
</ItemGroup>

<!-- Exclude polyfills inherited from Directory.Build.props; this package only needs the ExperimentalAttribute polyfill. -->
<ItemGroup>
<Compile Remove="..\Common\Polyfills\**\*.cs" />
<Compile Include="..\Common\Polyfills\System\Diagnostics\CodeAnalysis\ExperimentalAttribute.cs"
Link="Polyfills\ExperimentalAttribute.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ModelContextProtocol\ModelContextProtocol.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\PACKAGE.md" Pack="true" PackagePath="\README.md" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Diagnostics.CodeAnalysis;

namespace ModelContextProtocol.Server;

/// <summary>
/// Specifies MCP Apps UI metadata for a tool method.
/// </summary>
/// <remarks>
/// <para>
/// Apply this attribute alongside <see cref="McpServerToolAttribute"/> to associate an MCP Apps
/// UI resource with the tool. When processed by <see cref="McpApps.ApplyAppUiAttributes(McpServerTool)"/>
/// or <see cref="McpApps.ApplyAppUiAttributes(IEnumerable{McpServerTool})"/>, it populates the
/// structured <c>_meta.ui</c> object in the tool's metadata.
/// </para>
/// <para>
/// This attribute takes precedence over any raw <c>[McpMeta("ui", ...)]</c> attribute on the
/// same method, but explicit <c>Meta["ui"]</c> set via <see cref="McpServerToolCreateOptions"/>
/// takes precedence over this attribute.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// [McpServerTool]
/// [McpAppUi(ResourceUri = "ui://weather/view.html")]
/// [Description("Get current weather for a location")]
/// public string GetWeather(string location) => ...;
///
/// // Restrict visibility to model only:
/// [McpServerTool]
/// [McpAppUi(ResourceUri = "ui://weather/view.html", Visibility = [McpUiToolVisibility.Model])]
/// public string GetWeatherModelOnly(string location) => ...;
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method)]
[Experimental(Experimentals.Apps_DiagnosticId, UrlFormat = Experimentals.Apps_Url)]
public sealed class McpAppUiAttribute : Attribute
{
/// <summary>
/// Gets or sets the URI of the UI resource associated with this tool.
/// </summary>
/// <remarks>
/// This should be a <c>ui://</c> URI pointing to the HTML resource registered
/// with the server (e.g., <c>"ui://weather/view.html"</c>).
/// </remarks>
public string? ResourceUri { get; set; }

/// <summary>
/// Gets or sets the visibility of the tool, controlling which principals can invoke it.
/// </summary>
/// <remarks>
/// <para>
/// Allowed values are <see cref="McpUiToolVisibility.Model"/> and <see cref="McpUiToolVisibility.App"/>.
/// When <see langword="null"/> or empty, the tool is visible to both the model and the app (the default).
/// </para>
/// </remarks>
public string[]? Visibility { get; set; }
}
Loading
Loading