Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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.ExtApps/ModelContextProtocol.ExtApps.csproj" />
<Project Path="src/ModelContextProtocol/ModelContextProtocol.csproj" />
</Folder>
<Folder Name="/tests/">
Expand Down
2 changes: 1 addition & 1 deletion docs/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T

| Diagnostic ID | Description |
| :------------ | :---------- |
| `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)). |
| `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)). MCP Apps is the first official MCP extension, 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)). |
Comment thread
mikekistler marked this conversation as resolved.
Outdated
| `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). |

## Obsolete APIs
Expand Down
19 changes: 19 additions & 0 deletions src/Common/Experimentals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ 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>
/// This uses the same diagnostic ID as <see cref="Extensions_DiagnosticId"/> because
/// MCP Apps is implemented as an MCP extension (<c>"io.modelcontextprotocol/ui"</c>).
/// </remarks>
public const string Apps_DiagnosticId = "MCPEXP001";
Comment thread
mikekistler marked this conversation as resolved.
Outdated

/// <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#mcpexp001";

/// <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
7 changes: 7 additions & 0 deletions src/ModelContextProtocol.Core/McpJsonUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ internal static bool IsValidMcpToolSchema(JsonElement element)
[JsonSerializable(typeof(DynamicClientRegistrationRequest))]
[JsonSerializable(typeof(DynamicClientRegistrationResponse))]

// MCP Apps extension types
[JsonSerializable(typeof(Server.McpUiToolMeta))]
[JsonSerializable(typeof(Server.McpUiClientCapabilities))]
[JsonSerializable(typeof(Server.McpUiResourceMeta))]
[JsonSerializable(typeof(Server.McpUiResourceCsp))]
[JsonSerializable(typeof(Server.McpUiResourcePermissions))]

// Primitive types for use in consuming AIFunctions
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(byte))]
Expand Down
41 changes: 37 additions & 4 deletions src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,21 @@ options.OpenWorld is not null ||
};
}

// Populate Meta from options and/or McpMetaAttribute instances if a MethodInfo is available
// Populate Meta from options and/or McpMetaAttribute instances if a MethodInfo is available.
// Priority order (highest to lowest):
// 1. Explicit options.Meta entries
// 2. AppUi metadata (from McpAppUiAttribute or McpServerToolCreateOptions.AppUi)
// 3. McpMetaAttribute entries on the method
JsonObject? seededMeta = options.Meta;
if (options.AppUi is { } appUi)
{
seededMeta = seededMeta is not null ? CloneJsonObject(seededMeta) : new JsonObject();
McpAppsInternal.ApplyUiToolMetaToJsonObject(appUi, seededMeta);
Comment thread
mikekistler marked this conversation as resolved.
Outdated
}

tool.Meta = function.UnderlyingMethod is not null ?
CreateMetaFromAttributes(function.UnderlyingMethod, options.Meta) :
options.Meta;
CreateMetaFromAttributes(function.UnderlyingMethod, seededMeta) :
seededMeta;

// Apply user-specified Execution settings if provided
if (options.Execution is not null)
Expand All @@ -159,7 +170,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 Expand Up @@ -225,6 +236,16 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe
newOptions.Description ??= descAttr.Description;
}

// Process McpAppUiAttribute — takes precedence over options.AppUi set via constructor.
if (method.GetCustomAttribute<McpAppUiAttribute>() is { } appUiAttr)
Comment thread
mikekistler marked this conversation as resolved.
Outdated
{
newOptions.AppUi = new McpUiToolMeta
{
ResourceUri = appUiAttr.ResourceUri,
Visibility = appUiAttr.Visibility,
};
}

// Set metadata if not already provided
newOptions.Metadata ??= CreateMetadata(method);

Expand Down Expand Up @@ -405,6 +426,18 @@ internal static IReadOnlyList<object> CreateMetadata(MethodInfo method)
return meta;
}

/// <summary>Creates a copy of a <see cref="JsonObject"/> so that keys can be added without mutating the original.</summary>
private static JsonObject CloneJsonObject(JsonObject source)
Comment thread
mikekistler marked this conversation as resolved.
Outdated
{
var clone = new JsonObject();
foreach (var kvp in source)
{
// DeepClone each value to avoid sharing nodes between two JsonObject instances.
clone[kvp.Key] = kvp.Value?.DeepClone();
}
return clone;
}

#if NET
/// <summary>Regex that flags runs of characters other than ASCII digits or letters.</summary>
[GeneratedRegex("[^0-9A-Za-z]+")]
Expand Down
55 changes: 55 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpAppUiAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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, 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.
/// </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; }
}
31 changes: 31 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpApps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using ModelContextProtocol.Protocol;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;

namespace ModelContextProtocol.Server;

/// <summary>
/// Internal helper methods for MCP Apps integration within the Core package.
/// The public MCP Apps API surface is in the ModelContextProtocol.ExtApps package.
/// </summary>
internal static class McpAppsInternal
{
/// <summary>
/// Applies UI tool metadata to a <see cref="System.Text.Json.Nodes.JsonObject"/>, setting the
/// <c>ui</c> object key if not already present.
/// </summary>
/// <param name="appUi">The UI tool metadata to apply.</param>
/// <param name="meta">The <see cref="System.Text.Json.Nodes.JsonObject"/> to populate.</param>
internal static void ApplyUiToolMetaToJsonObject(McpUiToolMeta appUi, System.Text.Json.Nodes.JsonObject meta)
{
// Populate the structured "ui" object if not already present.
if (!meta.ContainsKey("ui"))
{
var uiNode = JsonSerializer.SerializeToNode(appUi, McpJsonUtilities.JsonContext.Default.McpUiToolMeta);
if (uiNode is not null)
{
meta["ui"] = uiNode;
}
}
}
}
30 changes: 30 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,35 @@ public sealed class McpServerToolCreateOptions
/// </remarks>
public JsonObject? Meta { get; set; }

/// <summary>
/// Gets or sets the MCP Apps UI metadata for this tool.
/// </summary>
/// <remarks>
/// <para>
/// When set, this metadata is merged into <see cref="Meta"/> during tool creation, populating
/// the structured <c>_meta.ui</c> object.
/// </para>
/// <para>
/// Explicit entries already present in <see cref="Meta"/> take precedence over values from
/// this property. The <see cref="McpAppUiAttribute"/> on a method overrides this property
/// when both are specified.
/// </para>
/// </remarks>
/// <example>
/// <code language="csharp">
/// var tool = McpServerTool.Create(handler, new McpServerToolCreateOptions
/// {
/// AppUi = new McpUiToolMeta
/// {
/// ResourceUri = "ui://weather/view.html",
/// Visibility = [McpUiToolVisibility.Model, McpUiToolVisibility.App]
/// }
/// });
/// </code>
/// </example>
[Experimental(Experimentals.Apps_DiagnosticId, UrlFormat = Experimentals.Apps_Url)]
public McpUiToolMeta? AppUi { get; set; }

/// <summary>
/// Gets or sets the execution hints for this tool.
/// </summary>
Expand Down Expand Up @@ -235,6 +264,7 @@ internal McpServerToolCreateOptions Clone() =>
Metadata = Metadata,
Icons = Icons,
Meta = Meta,
AppUi = AppUi,
Execution = Execution,
};
}
26 changes: 26 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpUiClientCapabilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Server;

/// <summary>
/// Represents the MCP Apps capabilities advertised by a client.
/// </summary>
/// <remarks>
/// <para>
/// This object is the value associated with the <c>"io.modelcontextprotocol/ui"</c> key in the
/// <see cref="Protocol.ClientCapabilities.Extensions"/> dictionary.
/// </para>
/// </remarks>
[Experimental(Experimentals.Apps_DiagnosticId, UrlFormat = Experimentals.Apps_Url)]
public sealed class McpUiClientCapabilities
{
/// <summary>
/// Gets or sets the list of MIME types supported by the client for MCP App UI resources.
/// </summary>
/// <remarks>
/// A client that supports MCP Apps must include <c>"text/html;profile=mcp-app"</c> in this list.
/// </remarks>
[JsonPropertyName("mimeTypes")]
public IList<string>? MimeTypes { get; set; }
}
49 changes: 49 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpUiResourceCsp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Server;

/// <summary>
/// Represents the Content Security Policy (CSP) domain allowlists for an MCP Apps UI resource.
/// </summary>
/// <remarks>
/// <para>
/// These allowlists are used by the MCP host to construct the Content-Security-Policy HTTP header
/// for the sandboxed iframe that hosts the UI resource.
/// </para>
/// <para>
/// Each list contains origins (e.g., <c>"https://api.example.com"</c>) that are permitted for
/// the corresponding CSP directive.
/// </para>
/// </remarks>
[Experimental(Experimentals.Apps_DiagnosticId, UrlFormat = Experimentals.Apps_Url)]
public sealed class McpUiResourceCsp
{
/// <summary>
/// Gets or sets the list of origins allowed for fetch, XMLHttpRequest, WebSocket, and EventSource
/// connections (<c>connect-src</c> CSP directive).
/// </summary>
[JsonPropertyName("connectDomains")]
public IList<string>? ConnectDomains { get; set; }

/// <summary>
/// Gets or sets the list of origins allowed for loading scripts, stylesheets, images, and fonts
/// (<c>script-src</c>, <c>style-src</c>, <c>img-src</c>, <c>font-src</c> CSP directives).
/// </summary>
[JsonPropertyName("resourceDomains")]
public IList<string>? ResourceDomains { get; set; }

/// <summary>
/// Gets or sets the list of origins allowed for loading nested frames
/// (<c>frame-src</c> CSP directive).
/// </summary>
[JsonPropertyName("frameDomains")]
public IList<string>? FrameDomains { get; set; }

/// <summary>
/// Gets or sets the list of allowed base URIs
/// (<c>base-uri</c> CSP directive).
/// </summary>
[JsonPropertyName("baseUris")]
public IList<string>? BaseUris { get; set; }
}
50 changes: 50 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpUiResourceMeta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Server;

/// <summary>
/// Represents the UI metadata associated with an MCP resource in the MCP Apps extension.
/// </summary>
/// <remarks>
/// This metadata is placed under the <c>ui</c> key in the resource's <c>_meta</c> object.
/// It provides Content Security Policy (CSP) configuration, sandbox permissions, CORS origin, and
/// visual boundary preferences for the UI resource served by this MCP server.
/// </remarks>
[Experimental(Experimentals.Apps_DiagnosticId, UrlFormat = Experimentals.Apps_Url)]
public sealed class McpUiResourceMeta
{
/// <summary>
/// Gets or sets the Content Security Policy configuration for this resource.
/// </summary>
/// <remarks>
/// Specifies the allowed origins for network requests, resource loads, and nested frames.
/// </remarks>
[JsonPropertyName("csp")]
public McpUiResourceCsp? Csp { get; set; }

/// <summary>
/// Gets or sets the sandbox permissions for this resource.
/// </summary>
/// <remarks>
/// Controls which browser sandbox features the UI resource is allowed to use.
/// </remarks>
[JsonPropertyName("permissions")]
public McpUiResourcePermissions? Permissions { get; set; }

/// <summary>
/// Gets or sets the dedicated origin domain for this resource.
/// </summary>
/// <remarks>
/// When set, the host will serve the resource from this dedicated origin,
/// enabling OAuth flows and CORS without wildcard exceptions.
/// </remarks>
[JsonPropertyName("domain")]
public string? Domain { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the host should render a visual border around the UI.
/// </summary>
[JsonPropertyName("prefersBorder")]
public bool? PrefersBorder { get; set; }
}
Loading
Loading