Skip to content

Commit 0948a6c

Browse files
author
Mike Kistler
committed
Address halter73 review comments on MCP Apps extension
1 parent dbd6d38 commit 0948a6c

16 files changed

Lines changed: 210 additions & 16 deletions

docs/concepts/apps/apps.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,55 @@ UI resources are HTML pages registered with the MCP server using the `ui://` URI
126126
- **Domain** — Dedicated origin for OAuth flows and CORS
127127
- **PrefersBorder** — Whether the host should render a visual border
128128

129+
## App-only tools
130+
131+
Tools with `Visibility = [McpUiToolVisibility.App]` are not visible to the LLM — they are intended only for use by the app UI.
132+
This is useful for tools that serve UI interaction (button handlers, form submissions) without cluttering the model's tool list:
133+
134+
```csharp
135+
[McpServerTool, Description("Submit the weather form")]
136+
[McpAppUi(ResourceUri = "ui://weather/view.html", Visibility = [McpUiToolVisibility.App])]
137+
public static string SubmitWeatherForm(string city) => GetWeatherHtml(city);
138+
```
139+
140+
## Graceful degradation
141+
142+
Not all clients support MCP Apps. Use `GetUiCapability` to detect support and return text-only content as a fallback:
143+
144+
```csharp
145+
[McpServerTool, Description("Get weather")]
146+
[McpAppUi(ResourceUri = "ui://weather/view.html")]
147+
public static string GetWeather(McpServer server, string location)
148+
{
149+
var uiCapability = McpApps.GetUiCapability(server.ClientCapabilities);
150+
if (uiCapability is null)
151+
{
152+
// Client doesn't support MCP Apps — return plain text
153+
return $"Current weather for {location}: 72°F, sunny";
154+
}
155+
156+
// Client supports MCP Apps — the UI resource will be displayed
157+
return $"Weather data for {location} loaded into UI";
158+
}
159+
```
160+
161+
## Display modes
162+
163+
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.
164+
165+
## Host theming
166+
167+
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.
168+
169+
See the [MCP Apps specification](https://modelcontextprotocol.io/specification/draft/extensions/apps) for the full list of CSS variables.
170+
171+
## Single-file HTML bundling
172+
173+
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.
174+
129175
## Constants
130176

131-
The <xref:ModelContextProtocol.Server.McpApps> class provides constants for protocol values:
177+
The <xref:ModelContextProtocol.Extensions.Apps.McpApps> class provides constants for protocol values:
132178

133179
| Constant | Value | Usage |
134180
| - | - | - |

samples/WeatherAppServer/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using ModelContextProtocol.AspNetCore;
3+
using ModelContextProtocol.Extensions.Apps;
34
using ModelContextProtocol.Protocol;
45
using ModelContextProtocol.Server;
56
using System.Net.Http.Headers;

samples/WeatherAppServer/WeatherResources.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ModelContextProtocol.Extensions.Apps;
12
using ModelContextProtocol.Server;
23
using System.ComponentModel;
34
using System.Text.Json;
@@ -9,6 +10,7 @@ public sealed class WeatherResources
910
private static readonly string UiDir = Path.Combine(AppContext.BaseDirectory, "ui");
1011

1112
[McpServerResource(UriTemplate = "ui://weather-app/forecast", Name = "weather-forecast-ui", MimeType = McpApps.ResourceMimeType)]
13+
[McpMeta("ui", """{"csp":{"connectDomains":["https://api.weather.gov"]},"prefersBorder":true}""")]
1214
[Description("Interactive weather forecast UI with city picker")]
1315
public static string GetWeatherForecastUi() => File.ReadAllText(Path.Combine(UiDir, "weather-forecast.html"));
1416

samples/WeatherAppServer/WeatherTools.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ModelContextProtocol;
2+
using ModelContextProtocol.Extensions.Apps;
23
using ModelContextProtocol.Protocol;
34
using ModelContextProtocol.Server;
45
using System.ComponentModel;

src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<PackageId>ModelContextProtocol.Extensions.Apps</PackageId>
88
<Description>MCP Apps extension for the .NET Model Context Protocol (MCP) SDK</Description>
99
<PackageReadmeFile>README.md</PackageReadmeFile>
10-
<!-- Suppress the experimental MCP Apps warning -->
11-
<NoWarn>$(NoWarn);MCPEXP003</NoWarn>
10+
<!-- Suppress the experimental MCP warnings for internal usage -->
11+
<NoWarn>$(NoWarn);MCPEXP001;MCPEXP003</NoWarn>
1212
<!-- New package with no published baseline yet -->
1313
<EnablePackageValidation>false</EnablePackageValidation>
1414
</PropertyGroup>

src/ModelContextProtocol.Extensions.Apps/Server/McpAppUiAttribute.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using ModelContextProtocol.Protocol;
2+
using ModelContextProtocol.Server;
13
using System.Diagnostics.CodeAnalysis;
24

3-
namespace ModelContextProtocol.Server;
5+
namespace ModelContextProtocol.Extensions.Apps;
46

57
/// <summary>
68
/// Specifies MCP Apps UI metadata for a tool method.
@@ -11,10 +13,11 @@ namespace ModelContextProtocol.Server;
1113
/// UI resource with the tool. When processed by <see cref="McpApps.ApplyAppUiAttributes(McpServerTool)"/>
1214
/// or <see cref="McpApps.ApplyAppUiAttributes(IEnumerable{McpServerTool})"/>, it populates the
1315
/// structured <c>_meta.ui</c> object in the tool's metadata.
16+
/// </para>
1417
/// <para>
1518
/// Explicit <c>Meta["ui"]</c> set via <see cref="McpServerToolCreateOptions"/> or a raw
1619
/// <c>[McpMeta("ui", ...)]</c> attribute takes precedence over this attribute: if the tool
17-
/// already has a <c>ui</c> key in <see cref="Protocol.Tool.Meta"/>, this attribute is ignored.
20+
/// already has a <c>ui</c> key in <see cref="Tool.Meta"/>, this attribute is ignored.
1821
/// </para>
1922
/// </remarks>
2023
/// <example>

src/ModelContextProtocol.Extensions.Apps/Server/McpApps.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using ModelContextProtocol.Protocol;
2+
using ModelContextProtocol.Server;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Text.Json;
45
using System.Text.Json.Nodes;
56

6-
namespace ModelContextProtocol.Server;
7+
namespace ModelContextProtocol.Extensions.Apps;
78

89
/// <summary>
910
/// Provides constants and helper methods for building MCP Apps-enabled servers.
@@ -85,10 +86,29 @@ private static JsonSerializerOptions CreateSerializerOptions()
8586
return null;
8687
}
8788

89+
// Handle the case where the value was set programmatically (e.g. in tests
90+
// or a non-JSON code path) rather than deserialized from the handshake.
91+
if (value is McpUiClientCapabilities uiCapabilities)
92+
{
93+
return uiCapabilities;
94+
}
95+
8896
if (value is JsonElement element)
8997
{
90-
return element.ValueKind == JsonValueKind.Null ? null :
91-
JsonSerializer.Deserialize(element, McpAppsJsonContext.Default.McpUiClientCapabilities);
98+
// Guard against malformed extension values (e.g. a bare string or number)
99+
// that would throw JsonException during deserialization. Return null
100+
// consistently for any non-object value, matching the other failure modes.
101+
if (element.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
102+
{
103+
return null;
104+
}
105+
106+
if (element.ValueKind != JsonValueKind.Object)
107+
{
108+
return null;
109+
}
110+
111+
return JsonSerializer.Deserialize(element, McpAppsJsonContext.Default.McpUiClientCapabilities);
92112
}
93113

94114
return null;
@@ -132,6 +152,44 @@ public static McpServerTool SetAppUi(McpServerTool tool, McpUiToolMeta appUi)
132152
return tool;
133153
}
134154

155+
/// <summary>
156+
/// Sets the MCP Apps UI metadata on a resource's <see cref="ResourceTemplate.Meta"/> property.
157+
/// </summary>
158+
/// <param name="resource">The resource to set the UI metadata on.</param>
159+
/// <param name="resourceUi">The UI metadata to apply.</param>
160+
/// <returns>The same <paramref name="resource"/> instance, for chaining.</returns>
161+
/// <remarks>
162+
/// <para>
163+
/// This method sets the <c>ui</c> key in the resource's <see cref="ResourceTemplate.Meta"/> object.
164+
/// If a <c>ui</c> key is already present in <see cref="ResourceTemplate.Meta"/>, it is not overwritten.
165+
/// </para>
166+
/// </remarks>
167+
/// <exception cref="ArgumentNullException"><paramref name="resource"/> or <paramref name="resourceUi"/> is <see langword="null"/>.</exception>
168+
public static McpServerResource SetResourceUi(McpServerResource resource, McpUiResourceMeta resourceUi)
169+
{
170+
#if NET
171+
ArgumentNullException.ThrowIfNull(resource);
172+
ArgumentNullException.ThrowIfNull(resourceUi);
173+
#else
174+
if (resource is null) throw new ArgumentNullException(nameof(resource));
175+
if (resourceUi is null) throw new ArgumentNullException(nameof(resourceUi));
176+
#endif
177+
178+
var protocolResource = resource.ProtocolResourceTemplate;
179+
protocolResource.Meta ??= new JsonObject();
180+
181+
if (!protocolResource.Meta.ContainsKey("ui"))
182+
{
183+
var uiNode = JsonSerializer.SerializeToNode(resourceUi, McpAppsJsonContext.Default.McpUiResourceMeta);
184+
if (uiNode is not null)
185+
{
186+
protocolResource.Meta["ui"] = uiNode;
187+
}
188+
}
189+
190+
return resource;
191+
}
192+
135193
/// <summary>
136194
/// Processes a collection of tools, applying <see cref="McpAppUiAttribute"/> metadata to any
137195
/// tool whose underlying method has the attribute.

src/ModelContextProtocol.Extensions.Apps/Server/McpAppsBuilderExtensions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Microsoft.Extensions.Options;
3+
using ModelContextProtocol.Protocol;
4+
using ModelContextProtocol.Server;
35
using System.Diagnostics.CodeAnalysis;
46

5-
namespace ModelContextProtocol.Server;
7+
namespace ModelContextProtocol.Extensions.Apps;
68

79
/// <summary>
810
/// Extension methods for <see cref="IMcpServerBuilder"/> to enable MCP Apps support.
@@ -49,6 +51,14 @@ private sealed class McpAppsPostConfigureOptions : IPostConfigureOptions<McpServ
4951
{
5052
public void PostConfigure(string? name, McpServerOptions options)
5153
{
54+
// Advertise server-side MCP Apps support in capabilities.
55+
options.Capabilities ??= new ServerCapabilities();
56+
options.Capabilities.Extensions ??= new Dictionary<string, object>();
57+
if (!options.Capabilities.Extensions.ContainsKey(McpApps.ExtensionId))
58+
{
59+
options.Capabilities.Extensions[McpApps.ExtensionId] = new { };
60+
}
61+
5262
if (options.ToolCollection is { IsEmpty: false } tools)
5363
{
5464
foreach (var tool in tools)

src/ModelContextProtocol.Extensions.Apps/Server/McpAppsJsonContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Text.Json;
22
using System.Text.Json.Serialization;
33

4-
namespace ModelContextProtocol.Server;
4+
namespace ModelContextProtocol.Extensions.Apps;
55

66
/// <summary>
77
/// Provides source-generated JSON serialization metadata for MCP Apps extension types.

src/ModelContextProtocol.Extensions.Apps/Server/McpUiClientCapabilities.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Diagnostics.CodeAnalysis;
22
using System.Text.Json.Serialization;
33

4-
namespace ModelContextProtocol.Server;
4+
namespace ModelContextProtocol.Extensions.Apps;
55

66
/// <summary>
77
/// Represents the MCP Apps capabilities advertised by a client.

0 commit comments

Comments
 (0)