Skip to content

Commit f286391

Browse files
CopilotMackinnonBuckjozkee
authored
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 f37de15. * 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>
1 parent a0049c7 commit f286391

27 files changed

+982
-8
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace ModelContextProtocol.Protocol;
4+
5+
/// <summary>
6+
/// Represents an icon that can be used to visually identify an implementation, resource, tool, or prompt.
7+
/// </summary>
8+
/// <remarks>
9+
/// <para>
10+
/// Icons enhance user interfaces by providing visual context and improving the discoverability of available functionality.
11+
/// Each icon includes a source URI pointing to the icon resource, and optional MIME type and size information.
12+
/// </para>
13+
/// <para>
14+
/// Clients that support rendering icons MUST support at least the following MIME types:
15+
/// </para>
16+
/// <list type="bullet">
17+
/// <item><description>image/png - PNG images (safe, universal compatibility)</description></item>
18+
/// <item><description>image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)</description></item>
19+
/// </list>
20+
/// <para>
21+
/// Clients that support rendering icons SHOULD also support:
22+
/// </para>
23+
/// <list type="bullet">
24+
/// <item><description>image/svg+xml - SVG images (scalable but requires security precautions)</description></item>
25+
/// <item><description>image/webp - WebP images (modern, efficient format)</description></item>
26+
/// </list>
27+
/// <para>
28+
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
29+
/// </para>
30+
/// </remarks>
31+
public sealed class Icon
32+
{
33+
/// <summary>
34+
/// Gets or sets the URI pointing to the icon resource.
35+
/// </summary>
36+
/// <remarks>
37+
/// <para>
38+
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
39+
/// </para>
40+
/// <para>
41+
/// Consumers SHOULD take steps to ensure URLs serving icons are from the same domain as the client/server
42+
/// or a trusted domain.
43+
/// </para>
44+
/// <para>
45+
/// Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain executable JavaScript.
46+
/// </para>
47+
/// </remarks>
48+
[JsonPropertyName("src")]
49+
public required string Source { get; init; }
50+
51+
/// <summary>
52+
/// Gets or sets the optional MIME type of the icon.
53+
/// </summary>
54+
/// <remarks>
55+
/// This can be used to override the server's MIME type if it's missing or generic.
56+
/// Common values include "image/png", "image/jpeg", "image/svg+xml", and "image/webp".
57+
/// </remarks>
58+
[JsonPropertyName("mimeType")]
59+
public string? MimeType { get; init; }
60+
61+
/// <summary>
62+
/// Gets or sets the optional size specifications for the icon.
63+
/// </summary>
64+
/// <remarks>
65+
/// <para>
66+
/// This can specify one or more sizes at which the icon file can be used.
67+
/// Examples include "48x48", "any" for scalable formats like SVG.
68+
/// </para>
69+
/// <para>
70+
/// If not provided, clients should assume that the icon can be used at any size.
71+
/// </para>
72+
/// </remarks>
73+
[JsonPropertyName("sizes")]
74+
public IList<string>? Sizes { get; init; }
75+
76+
/// <summary>
77+
/// Gets or sets the optional theme for this icon.
78+
/// </summary>
79+
/// <remarks>
80+
/// Can be "light", "dark", or a custom theme identifier.
81+
/// Used to specify which UI theme the icon is designed for.
82+
/// </remarks>
83+
[JsonPropertyName("theme")]
84+
public string? Theme { get; init; }
85+
}

src/ModelContextProtocol.Core/Protocol/Implementation.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,28 @@ public sealed class Implementation : IBaseMetadata
3636
/// </remarks>
3737
[JsonPropertyName("version")]
3838
public required string Version { get; set; }
39+
40+
/// <summary>
41+
/// Gets or sets an optional list of icons for this implementation.
42+
/// </summary>
43+
/// <remarks>
44+
/// This can be used by clients to display the implementation's icon in a user interface.
45+
/// </remarks>
46+
[JsonPropertyName("icons")]
47+
public IList<Icon>? Icons { get; set; }
48+
49+
/// <summary>
50+
/// Gets or sets an optional URL of the website for this implementation.
51+
/// </summary>
52+
/// <remarks>
53+
/// <para>
54+
/// This URL can be used by clients to link to documentation or more information about the implementation.
55+
/// </para>
56+
/// <para>
57+
/// Consumers SHOULD take steps to ensure URLs are from the same domain as the client/server
58+
/// or a trusted domain to prevent security issues.
59+
/// </para>
60+
/// </remarks>
61+
[JsonPropertyName("websiteUrl")]
62+
public string? WebsiteUrl { get; set; }
3963
}

src/ModelContextProtocol.Core/Protocol/Prompt.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ public sealed class Prompt : IBaseMetadata
5252
[JsonPropertyName("arguments")]
5353
public IList<PromptArgument>? Arguments { get; set; }
5454

55+
/// <summary>
56+
/// Gets or sets an optional list of icons for this prompt.
57+
/// </summary>
58+
/// <remarks>
59+
/// This can be used by clients to display the prompt's icon in a user interface.
60+
/// </remarks>
61+
[JsonPropertyName("icons")]
62+
public IList<Icon>? Icons { get; set; }
63+
5564
/// <summary>
5665
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
5766
/// </summary>

src/ModelContextProtocol.Core/Protocol/Resource.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ public sealed class Resource : IBaseMetadata
8080
[JsonPropertyName("size")]
8181
public long? Size { get; init; }
8282

83+
/// <summary>
84+
/// Gets or sets an optional list of icons for this resource.
85+
/// </summary>
86+
/// <remarks>
87+
/// This can be used by clients to display the resource's icon in a user interface.
88+
/// </remarks>
89+
[JsonPropertyName("icons")]
90+
public IList<Icon>? Icons { get; set; }
91+
8392
/// <summary>
8493
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
8594
/// </summary>

src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ public sealed class ResourceTemplate : IBaseMetadata
7272
[JsonPropertyName("annotations")]
7373
public Annotations? Annotations { get; init; }
7474

75+
/// <summary>
76+
/// Gets or sets an optional list of icons for this resource template.
77+
/// </summary>
78+
/// <remarks>
79+
/// This can be used by clients to display the resource template's icon in a user interface.
80+
/// </remarks>
81+
[JsonPropertyName("icons")]
82+
public IList<Icon>? Icons { get; set; }
83+
7584
/// <summary>
7685
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
7786
/// </summary>
@@ -108,6 +117,7 @@ public sealed class ResourceTemplate : IBaseMetadata
108117
Description = Description,
109118
MimeType = MimeType,
110119
Annotations = Annotations,
120+
Icons = Icons,
111121
Meta = Meta,
112122
McpServerResource = McpServerResource,
113123
};

src/ModelContextProtocol.Core/Protocol/Tool.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ public JsonElement? OutputSchema
107107
[JsonPropertyName("annotations")]
108108
public ToolAnnotations? Annotations { get; set; }
109109

110+
/// <summary>
111+
/// Gets or sets an optional list of icons for this tool.
112+
/// </summary>
113+
/// <remarks>
114+
/// This can be used by clients to display the tool's icon in a user interface.
115+
/// </remarks>
116+
[JsonPropertyName("icons")]
117+
public IList<Icon>? Icons { get; set; }
118+
110119
/// <summary>
111120
/// Gets or sets metadata reserved by MCP for protocol-level metadata.
112121
/// </summary>

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
135135
Title = options?.Title,
136136
Description = options?.Description ?? function.Description,
137137
Arguments = args,
138+
Icons = options?.Icons,
138139
};
139140

140141
return new AIFunctionMcpServerPrompt(function, prompt, options?.Metadata ?? []);
@@ -148,6 +149,12 @@ private static McpServerPromptCreateOptions DeriveOptions(MethodInfo method, Mcp
148149
{
149150
newOptions.Name ??= promptAttr.Name;
150151
newOptions.Title ??= promptAttr.Title;
152+
153+
// Handle icon from attribute if not already specified in options
154+
if (newOptions.Icons is null && promptAttr.IconSource is { Length: > 0 } iconSource)
155+
{
156+
newOptions.Icons = [new() { Source = iconSource }];
157+
}
151158
}
152159

153160
if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
218218
Title = options?.Title,
219219
Description = options?.Description,
220220
MimeType = options?.MimeType ?? "application/octet-stream",
221+
Icons = options?.Icons,
221222
};
222223

223224
return new AIFunctionMcpServerResource(function, resource, options?.Metadata ?? []);
@@ -233,6 +234,12 @@ private static McpServerResourceCreateOptions DeriveOptions(MemberInfo member, M
233234
newOptions.Name ??= resourceAttr.Name;
234235
newOptions.Title ??= resourceAttr.Title;
235236
newOptions.MimeType ??= resourceAttr.MimeType;
237+
238+
// Handle icon from attribute if not already specified in options
239+
if (newOptions.Icons is null && resourceAttr.IconSource is { Length: > 0 } iconSource)
240+
{
241+
newOptions.Icons = [new() { Source = iconSource }];
242+
}
236243
}
237244

238245
if (member.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
121121
Description = options?.Description ?? function.Description,
122122
InputSchema = function.JsonSchema,
123123
OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
124+
Icons = options?.Icons,
124125
};
125126

126127
if (options is not null)
@@ -176,6 +177,11 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe
176177
newOptions.ReadOnly ??= readOnly;
177178
}
178179

180+
if (newOptions.Icons is null && toolAttr.IconSource is { Length: > 0 } iconSource)
181+
{
182+
newOptions.Icons = [new() { Source = iconSource }];
183+
}
184+
179185
newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
180186
}
181187

src/ModelContextProtocol.Core/Server/McpServerPromptAttribute.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,19 @@ public McpServerPromptAttribute()
120120

121121
/// <summary>Gets or sets the title of the prompt.</summary>
122122
public string? Title { get; set; }
123+
124+
/// <summary>
125+
/// Gets or sets the source URI for the prompt's icon.
126+
/// </summary>
127+
/// <remarks>
128+
/// <para>
129+
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
130+
/// When specified, a single icon will be added to the prompt.
131+
/// </para>
132+
/// <para>
133+
/// For more advanced icon configuration (multiple icons, MIME type specification, size characteristics),
134+
/// use <see cref="McpServerPromptCreateOptions.Icons"/> when creating the prompt programmatically.
135+
/// </para>
136+
/// </remarks>
137+
public string? IconSource { get; set; }
123138
}

0 commit comments

Comments
 (0)