Skip to content

Add MCP Apps extension support (typed metadata, attribute, and helpers)#1484

Open
Copilot wants to merge 8 commits into
mainfrom
copilot/add-mcp-apps-support
Open

Add MCP Apps extension support (typed metadata, attribute, and helpers)#1484
Copilot wants to merge 8 commits into
mainfrom
copilot/add-mcp-apps-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 31, 2026

MCP Apps is the first official MCP extension (io.modelcontextprotocol/ui), enabling servers to deliver interactive UIs inside AI clients. The C# SDK had the foundational primitives (Extensions, _meta) but no typed convenience layer, requiring manual JSON construction that is error-prone and non-discoverable.

New Package: ModelContextProtocol.Extensions.Apps

A new package (ModelContextProtocol.Extensions.Apps) houses all MCP Apps types and helpers. It references ModelContextProtocol.Core for access to server primitives.

New APIs

Constants (McpApps)

  • McpApps.ResourceMimeType"text/html;profile=mcp-app"
  • McpApps.ExtensionId"io.modelcontextprotocol/ui"
  • McpApps.SerializerOptions → pre-configured JsonSerializerOptions for MCP Apps types

Typed metadata models

  • McpUiToolMetaResourceUri, Visibility
  • McpUiToolVisibilityModel/App string constants
  • McpUiResourceMetaCsp, Permissions, Domain, PrefersBorder
  • McpUiResourceCspConnectDomains, ResourceDomains, FrameDomains, BaseUris
  • McpUiResourcePermissionsAllow
  • McpUiClientCapabilitiesMimeTypes

Client capability helper

McpUiClientCapabilities? caps = McpApps.GetUiCapability(clientCapabilities);
if (caps?.MimeTypes?.Contains(McpApps.ResourceMimeType) is true) { ... }

[McpAppUi] attribute (declarative path)

[McpServerTool]
[McpAppUi(ResourceUri = "ui://weather/view.html")]
[Description("Get current weather for a location")]
public string GetWeather(string location) => ...;

When processed by McpApps.ApplyAppUiAttributes() (or the WithMcpApps() builder extension), this populates the structured _meta.ui object in the tool's metadata.

McpApps.SetAppUi (programmatic path)

McpApps.SetAppUi(tool, new McpUiToolMeta
{
    ResourceUri = "ui://weather/view.html",
    Visibility = [McpUiToolVisibility.Model, McpUiToolVisibility.App]
});

Explicit Meta["ui"] entries (set via McpServerToolCreateOptions.Meta) take precedence over SetAppUi; SetAppUi takes precedence over [McpAppUi] attribute.

WithMcpApps() builder extension

builder.Services
    .AddMcpServer()
    .WithTools<MyToolType>()
    .WithMcpApps();

Automatically processes [McpAppUi] attributes on all registered tools via IPostConfigureOptions<McpServerOptions>.

Notes

  • All new APIs are [Experimental(MCPEXP003)] — a dedicated diagnostic ID for MCP Apps.
  • Source-generated JSON serialization for Native AOT compatibility via McpAppsJsonContext.
  • Documentation added in docs/concepts/apps/apps.md.

Copilot AI changed the title [WIP] Add MCP Apps support for typed metadata and helpers Add MCP Apps extension support (typed metadata, attribute, and helpers) Mar 31, 2026
Copilot AI requested a review from mikekistler March 31, 2026 20:37
@gabrielwhitehair
Copy link
Copy Markdown

Any idea when this will be merged? Would love to see this feature in the SDK.

@VinKamat
Copy link
Copy Markdown

This is a key capability for "enterprises", emerging and especially those who are already on the MCP train wanting to deliver UI Apps within convo experiences.
Can't wait to see this merged, let's go please.

@FelixOhlhof
Copy link
Copy Markdown

Can you please merge this 🙏

Comment thread src/ModelContextProtocol.ExtApps/ModelContextProtocol.ExtApps.csproj Outdated
Comment thread docs/list-of-diagnostics.md Outdated
Comment thread src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs Outdated
Comment thread src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs Outdated
Comment thread src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs Outdated
Comment thread src/Common/Experimentals.cs Outdated
Comment thread src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs Outdated
mikekistler and others added 3 commits April 29, 2026 16:59
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mikekistler mikekistler marked this pull request as ready for review April 30, 2026 01:22
@bharathkreddy
Copy link
Copy Markdown

👀 Tracking this closely - and will adopt this surface as soon as it ships. The tool-side [McpAppUi] and McpServerToolCreateOptions.AppUi look great.

One observation that may be worth addressing in this PR or a fast follow-up: the typed McpUiResourceMeta / McpUiResourceCsp models added to Core aren't reachable via resource registration — only via raw JsonObject on TextResourceContents.Meta. Since per-resource CSP is mandatory for any production MCP App, this leaves the most failure-prone part of the API in raw-JSON territory. Would be great to get a typed path for resources too, mirroring the tool-side ergonomics. Filed as a comment on #1431 with more detail.

/// This MIME type should be used when registering UI resources with
/// <c>text/html;profile=mcp-app</c> to indicate they are MCP App resources.
/// </remarks>
public const string ResourceMimeType = "text/html;profile=mcp-app";
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.

I think this name is too generic. The MCP Apps spec says "The initial specification focuses on HTML resources (text/html;profile=mcp-app) with a clear path for future extensions." To avoid confusion with potential future resource types, I think we should call this "HtmlMimeType".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@weng5e
Copy link
Copy Markdown

weng5e commented May 12, 2026

It is so great to see this PR! Really looking forward to use this in our product!

@Pari-Dhanakoti
Copy link
Copy Markdown

+1 for code review and merging this. Will be super useful to have the attribute instead of band-aid code

@FelixOhlhof
Copy link
Copy Markdown

any updates on this?

}

return tool;
}
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.

There's a tool-side SetAppUi + [McpAppUi] attribute + ApplyAppUiAttributes pipeline, but no equivalent for resources. The types exist (McpUiResourceMeta, McpUiResourceCsp, McpUiResourcePermissions) but there's no SetResourceUi(McpServerResource, McpUiResourceMeta), no [McpAppResource] attribute, and WithMcpApps() doesn't process the resource collection.

Server authors who want to set CSP, permissions, domain, or prefersBorder on their UI resources have to hand-craft the _meta.ui JSON themselves. The sample WeatherResources demonstrates exactly this gap — it only sets MimeType and never gets to apply any of the CSP/permission types this package defines.

JsonSerializer.Deserialize(element, McpAppsJsonContext.Default.McpUiClientCapabilities);
}

return null;
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.

GetUiCapability only handles the case where value is JsonElement. If a server-side caller populated Extensions[ExtensionId] with a strongly typed McpUiClientCapabilities object (e.g. in tests, or via a non-JSON code path), this returns null silently. Worth handling that case too, or at least documenting that values are expected to be JsonElement from the deserialized handshake.

Also, no test covers a malformed value (e.g. JsonElement of kind String or Number) — currently that would throw a JsonException rather than returning null like the other failure modes.


var weatherTool = toolsWithUi.First(t => t.ProtocolTool.Meta!["ui"]!["resourceUri"]?.GetValue<string>() == "ui://weather/view.html");
Assert.NotNull(weatherTool);
}
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 test gaps worth filling:

  • WithMcpApps() with an empty ToolCollection (no tools registered) — should be a no-op, not throw.
  • GetUiCapability with a JsonElement of kind String / Number (malformed but present) — currently this would throw JsonException instead of returning null like the other negative cases.
  • No round-trip integration test that goes through McpClient.ListToolsAsync() and verifies _meta.ui survives serialization end-to-end (e.g. via ClientServerTestBase). All current tests are pure unit tests on the option pipeline.
  • No test for resource-side metadata flow (related to my other comment about the missing resource-side helpers).

/// .WithTools&lt;MyToolType&gt;()
/// .WithMcpApps();
/// </code>
/// </example>
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.

Ergonomics suggestion (worth considering as a follow-up): the TypeScript @modelcontextprotocol/ext-apps package exposes a registerAppTool(...) helper that bundles tool registration + UI resource linkage in one call. Today a C# user has to do WithTools<T>() + WithResources<T>() + WithMcpApps() + [McpServerTool] + [McpAppUi] + a matching [McpServerResource] with the right MIME type. A combined WithAppTool(method, resourceUri, htmlFactory) overload — or at least a doc section showing the "complete recipe" end-to-end — would cut the wiring boilerplate significantly.

<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>
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.

Globally suppressing MCPEXP003 inside the package that defines the experimental APIs is fine, but worth double-checking this doesn't suppress the warning for consumers of the package. The [Experimental] attribute carries its own diagnostic ID into consuming projects independently of NoWarn here, so this should only affect intra-package usage — but a quick verification that dotnet build on a fresh consumer project still emits MCPEXP003 would be reassuring before merge.

/// 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>
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.

This doc comment is backwards relative to the implementation. It says "This attribute takes precedence over any raw [McpMeta("ui", ...)]", but McpApps.ApplyAppUiAttributes (and SetAppUi) explicitly check Meta.ContainsKey("ui") and skip if present — so a pre-existing Meta["ui"] from [McpMeta] actually wins over this attribute, not the other way around.

Either fix the precedence (probably not what you want — explicit [McpMeta] overriding the typed attribute is the safer behavior) or fix the doc to say "explicit Meta["ui"] takes precedence over this attribute."


[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"));
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.

This sample correctly sets the MCP Apps MIME type, but it never demonstrates McpUiResourceMeta (CSP allowlists, permissions, prefersBorder, domain) — which is a significant portion of the surface this package introduces. Since api.weather.gov is the obvious external origin this UI would want to talk to, this is an ideal place to show a real CSP connectDomains configuration. As written, the sample fully exercises the tool-side API but only the MIME-type constant on the resource side.

- **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.


builder.Services.AddSingleton<IPostConfigureOptions<McpServerOptions>, McpAppsPostConfigureOptions>();
return builder;
}
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.

WithMcpApps() reads client capabilities (via GetUiCapability) but never advertises server-side support. The spec requires the server to include "io.modelcontextprotocol/ui": {} (or a capability object) in ServerCapabilities.Extensions during the initialize handshake — otherwise capability-aware clients can't detect that the server supports MCP Apps.

WithMcpApps() should also register a post-configure step that adds ExtensionId to options.Capabilities.Extensions. Right now consumers have to do this manually, which is easy to forget and not mentioned in the docs.

Comment on lines +14 to +19
/// </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>
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.

The doc comment's precedence claim is backwards relative to the code. ApplyAppUiAttributes checks Meta.ContainsKey("ui") and skips when present, so any explicit Meta["ui"] (whether from [McpMeta("ui", ...)] or McpServerToolCreateOptions.Meta) wins over this attribute — not the other way around.

Suggested change
/// </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>
/// <para>
/// Explicit <c>Meta["ui"]</c> set via <see cref="McpServerToolCreateOptions"/> or a raw
/// <c>[McpMeta("ui", ...)]</c> attribute takes precedence over this attribute: if the tool
/// already has a <c>ui</c> key in <see cref="Protocol.Tool.Meta"/>, this attribute is ignored.
/// </para>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SEP-1865: MCP Apps - Interactive User Interfaces for MCP