-
Notifications
You must be signed in to change notification settings - Fork 710
Add MCP Apps extension support (typed metadata, attribute, and helpers) #1484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
11
commits into
main
Choose a base branch
from
copilot/add-mcp-apps-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
bb32d0e
Initial plan
Copilot 35e7e59
feat: Add MCP Apps extension support (F1-F3, F6, F7)
Copilot fccaa0a
Move MCP Apps support to its own package
mikekistler 5673bd7
Address review feedback: rename, move types, remove Core coupling
mikekistler 7753c53
Add WithMcpApps() builder extension for IMcpServerBuilder
mikekistler 7f54b8c
Add MCP Apps conceptual documentation
mikekistler b14455c
Merge branch 'main' into copilot/add-mcp-apps-support
mikekistler 5d38a5b
Add WeatherAppServer sample demonstrating MCP Apps extension
mikekistler dbd6d38
Apply suggestions from code review
mikekistler 0948a6c
Address halter73 review comments on MCP Apps extension
mikekistler f471198
Rename ResourceMimeType to HtmlMimeType
mikekistler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| ## 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); | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
|  | ||
|
|
||
| ## 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)) | ||
| }; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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:
["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.inline,fullscreen,pip) — not mentioned anywhere, and there are no types for them.--color-background-primaryetc.) that hosts pass to apps. Worth at least linking to.CallToolResultwhenGetUiCapabilityreturnsnull, so server authors know the recommended fallback pattern.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.