Implement SEP-973: Icons and metadata support for Implementations, Resources, Tools, and Prompts (#802)

* Initial plan

* Initial plan for SEP-973 implementation

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Implement SEP-973: Add Icon class and icon support to Implementation, Resource, Tool, and Prompt

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Complete SEP-973 implementation with documentation and fix property consistency

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Address code review feedback: improve tests, remove docs, split test files

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Fix tests to use S.T.J. source generator

* Styling fixes

* Simplify Icons property documentation as suggested in code review

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Rename Icon.Src property to Icon.Source for more .NET-y naming

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Add icon support to McpServerTool infrastructure and attributes

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Cleanup

* Refactor icon tests to use Delegate overload instead of MethodInfo pattern

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Change Icon.Sizes property from string to IList<string> per spec update

Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>

* Add icon support to Prompts and Resources (CreateOptions and Attributes)

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Add client-server integration tests for Icons and WebsiteUrl

Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>

* Fix nullability warnings

* Revert "Add client-server integration tests for Icons and WebsiteUrl"

This reverts commit f37de15569e54e86a0709121c9bdf80c5045511c.

* More nullability fixes

* Add more tests

* Address code review feedback: add Theme property, fix docs, improve test patterns

Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>

* Update Icon tests to exercise Theme property

Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>

* Add Theme property assertions to client-server integration tests

Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>

* Fix nullability

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
Co-authored-by: Mackinnon Buck <mackinnon.buck@gmail.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
Co-authored-by: David Cantú <dacantu@microsoft.com>
This commit is contained in:
Copilot 2025-10-08 12:23:01 -07:00 • committed by GitHub
commit f28639119b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 982 additions and 8 deletions

View file

@ -0,0 +1,85 @@
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol;
/// <summary>
/// Represents an icon that can be used to visually identify an implementation, resource, tool, or prompt.
/// </summary>
/// <remarks>
/// <para>
/// Icons enhance user interfaces by providing visual context and improving the discoverability of available functionality.
/// Each icon includes a source URI pointing to the icon resource, and optional MIME type and size information.
/// </para>
/// <para>
/// Clients that support rendering icons MUST support at least the following MIME types:
/// </para>
/// <list type="bullet">
/// <item><description>image/png - PNG images (safe, universal compatibility)</description></item>
/// <item><description>image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)</description></item>
/// </list>
/// <para>
/// Clients that support rendering icons SHOULD also support:
/// </para>
/// <list type="bullet">
/// <item><description>image/svg+xml - SVG images (scalable but requires security precautions)</description></item>
/// <item><description>image/webp - WebP images (modern, efficient format)</description></item>
/// </list>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </para>
/// </remarks>
public sealed class Icon
{
/// <summary>
/// Gets or sets the URI pointing to the icon resource.
/// </summary>
/// <remarks>
/// <para>
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
/// </para>
/// <para>
/// Consumers SHOULD take steps to ensure URLs serving icons are from the same domain as the client/server
/// or a trusted domain.
/// </para>
/// <para>
/// Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.
/// </para>
/// </remarks>
[JsonPropertyName("src")]
public required string Source { get; init; }
/// <summary>
/// Gets or sets the optional MIME type of the icon.
/// </summary>
/// <remarks>
/// This can be used to override the server's MIME type if it's missing or generic.
/// Common values include "image/png", "image/jpeg", "image/svg+xml", and "image/webp".
/// </remarks>
[JsonPropertyName("mimeType")]
public string? MimeType { get; init; }
/// <summary>
/// Gets or sets the optional size specifications for the icon.
/// </summary>
/// <remarks>
/// <para>
/// This can specify one or more sizes at which the icon file can be used.
/// Examples include "48x48", "any" for scalable formats like SVG.
/// </para>
/// <para>
/// If not provided, clients should assume that the icon can be used at any size.
/// </para>
/// </remarks>
[JsonPropertyName("sizes")]
public IList<string>? Sizes { get; init; }
/// <summary>
/// Gets or sets the optional theme for this icon.
/// </summary>
/// <remarks>
/// Can be "light", "dark", or a custom theme identifier.
/// Used to specify which UI theme the icon is designed for.
/// </remarks>
[JsonPropertyName("theme")]
public string? Theme { get; init; }
}

View file

@ -36,4 +36,28 @@ public sealed class Implementation : IBaseMetadata
/// </remarks>
[JsonPropertyName("version")]
public required string Version { get; set; }
/// <summary>
/// Gets or sets an optional list of icons for this implementation.
/// </summary>
/// <remarks>
/// This can be used by clients to display the implementation's icon in a user interface.
/// </remarks>
[JsonPropertyName("icons")]
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Gets or sets an optional URL of the website for this implementation.
/// </summary>
/// <remarks>
/// <para>
/// This URL can be used by clients to link to documentation or more information about the implementation.
/// </para>
/// <para>
/// Consumers SHOULD take steps to ensure URLs are from the same domain as the client/server
/// or a trusted domain to prevent security issues.
/// </para>
/// </remarks>
[JsonPropertyName("websiteUrl")]
public string? WebsiteUrl { get; set; }
}

View file

@ -52,6 +52,15 @@ public sealed class Prompt : IBaseMetadata
[JsonPropertyName("arguments")]
public IList<PromptArgument>? Arguments { get; set; }
/// <summary>
/// Gets or sets an optional list of icons for this prompt.
/// </summary>
/// <remarks>
/// This can be used by clients to display the prompt's icon in a user interface.
/// </remarks>
[JsonPropertyName("icons")]
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
/// </summary>

View file

@ -80,6 +80,15 @@ public sealed class Resource : IBaseMetadata
[JsonPropertyName("size")]
public long? Size { get; init; }
/// <summary>
/// Gets or sets an optional list of icons for this resource.
/// </summary>
/// <remarks>
/// This can be used by clients to display the resource's icon in a user interface.
/// </remarks>
[JsonPropertyName("icons")]
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
/// </summary>

View file

@ -72,6 +72,15 @@ public sealed class ResourceTemplate : IBaseMetadata
[JsonPropertyName("annotations")]
public Annotations? Annotations { get; init; }
/// <summary>
/// Gets or sets an optional list of icons for this resource template.
/// </summary>
/// <remarks>
/// This can be used by clients to display the resource template's icon in a user interface.
/// </remarks>
[JsonPropertyName("icons")]
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
/// </summary>
@ -108,6 +117,7 @@ public sealed class ResourceTemplate : IBaseMetadata
Description = Description,
MimeType = MimeType,
Annotations = Annotations,
Icons = Icons,
Meta = Meta,
McpServerResource = McpServerResource,
};

View file

@ -107,6 +107,15 @@ public sealed class Tool : IBaseMetadata
[JsonPropertyName("annotations")]
public ToolAnnotations? Annotations { get; set; }
/// <summary>
/// Gets or sets an optional list of icons for this tool.
/// </summary>
/// <remarks>
/// This can be used by clients to display the tool's icon in a user interface.
/// </remarks>
[JsonPropertyName("icons")]
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
/// </summary>

View file

@ -135,6 +135,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
Title = options?.Title,
Description = options?.Description ?? function.Description,
Arguments = args,
Icons = options?.Icons,
};
return new AIFunctionMcpServerPrompt(function, prompt, options?.Metadata ?? []);
@ -148,6 +149,12 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
{
newOptions.Name ??= promptAttr.Name;
newOptions.Title ??= promptAttr.Title;
// Handle icon from attribute if not already specified in options
if (newOptions.Icons is null && promptAttr.IconSource is { Length: > 0 } iconSource)
{
newOptions.Icons = [new() { Source = iconSource }];
}
}
if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)

View file

@ -218,6 +218,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
Title = options?.Title,
Description = options?.Description,
MimeType = options?.MimeType ?? "application/octet-stream",
Icons = options?.Icons,
};
return new AIFunctionMcpServerResource(function, resource, options?.Metadata ?? []);
@ -233,6 +234,12 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
newOptions.Name ??= resourceAttr.Name;
newOptions.Title ??= resourceAttr.Title;
newOptions.MimeType ??= resourceAttr.MimeType;
// Handle icon from attribute if not already specified in options
if (newOptions.Icons is null && resourceAttr.IconSource is { Length: > 0 } iconSource)
{
newOptions.Icons = [new() { Source = iconSource }];
}
}
if (member.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)

View file

@ -121,6 +121,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
Description = options?.Description ?? function.Description,
InputSchema = function.JsonSchema,
OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
Icons = options?.Icons,
};
if (options is not null)
@ -176,6 +177,11 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
newOptions.ReadOnly ??= readOnly;
}
if (newOptions.Icons is null && toolAttr.IconSource is { Length: > 0 } iconSource)
{
newOptions.Icons = [new() { Source = iconSource }];
}
newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
}

View file

@ -120,4 +120,19 @@ public sealed class McpServerPromptAttribute : Attribute
/// <summary>Gets or sets the title of the prompt.</summary>
public string? Title { get; set; }
/// <summary>
/// Gets or sets the source URI for the prompt's icon.
/// </summary>
/// <remarks>
/// <para>
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
/// When specified, a single icon will be added to the prompt.
/// </para>
/// <para>
/// For more advanced icon configuration (multiple icons, MIME type specification, size characteristics),
/// use <see cref="McpServerPromptCreateOptions.Icons"/> when creating the prompt programmatically.
/// </para>
/// </remarks>
public string? IconSource { get; set; }
}

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Text.Json;
@ -77,6 +78,14 @@ public sealed class McpServerPromptCreateOptions
/// </remarks>
public IReadOnlyList<object>? Metadata { get; set; }
/// <summary>
/// Gets or sets the icons for this prompt.
/// </summary>
/// <remarks>
/// This can be used by clients to display the prompt's icon in a user interface.
/// </remarks>
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Creates a shallow clone of the current <see cref="McpServerPromptCreateOptions"/> instance.
/// </summary>
@ -90,5 +99,6 @@ public sealed class McpServerPromptCreateOptions
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}

View file

@ -135,4 +135,19 @@ public sealed class McpServerResourceAttribute : Attribute
/// <summary>Gets or sets the MIME (media) type of the resource.</summary>
public string? MimeType { get; set; }
/// <summary>
/// Gets or sets the source URI for the resource's icon.
/// </summary>
/// <remarks>
/// <para>
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
/// When specified, a single icon will be added to the resource.
/// </para>
/// <para>
/// For more advanced icon configuration (multiple icons, MIME type specification, size characteristics),
/// use <see cref="McpServerResourceCreateOptions.Icons"/> when creating the resource programmatically.
/// </para>
/// </remarks>
public string? IconSource { get; set; }
}

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Text.Json;
@ -92,6 +93,14 @@ public sealed class McpServerResourceCreateOptions
/// </remarks>
public IReadOnlyList<object>? Metadata { get; set; }
/// <summary>
/// Gets or sets the icons for this resource.
/// </summary>
/// <remarks>
/// This can be used by clients to display the resource's icon in a user interface.
/// </remarks>
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Creates a shallow clone of the current <see cref="McpServerResourceCreateOptions"/> instance.
/// </summary>
@ -107,5 +116,6 @@ public sealed class McpServerResourceCreateOptions
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}

View file

@ -254,4 +254,19 @@ public sealed class McpServerToolAttribute : Attribute
/// </para>
/// </remarks>
public bool UseStructuredContent { get; set; }
/// <summary>
/// Gets or sets the source URI for the tool's icon.
/// </summary>
/// <remarks>
/// <para>
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
/// When specified, a single icon will be added to the tool.
/// </para>
/// <para>
/// For more advanced icon configuration (multiple icons, MIME type specification, size characteristics),
/// use <see cref="McpServerToolCreateOptions.Icons"/> when creating the tool programmatically.
/// </para>
/// </remarks>
public string? IconSource { get; set; }
}

View file

@ -164,6 +164,14 @@ public sealed class McpServerToolCreateOptions
/// </remarks>
public IReadOnlyList<object>? Metadata { get; set; }
/// <summary>
/// Gets or sets the icons for this tool.
/// </summary>
/// <remarks>
/// This can be used by clients to display the tool's icon in a user interface.
/// </remarks>
public IList<Icon>? Icons { get; set; }
/// <summary>
/// Creates a shallow clone of the current <see cref="McpServerToolCreateOptions"/> instance.
/// </summary>
@ -182,5 +190,6 @@ public sealed class McpServerToolCreateOptions
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}