Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
941bc0e
Initial plan
Copilot Sep 19, 2025
31ad1e4
Initial plan for SEP-973 implementation
Copilot Sep 19, 2025
8b1b684
Implement SEP-973: Add Icon class and icon support to Implementation,…
Copilot Sep 19, 2025
24517c2
Complete SEP-973 implementation with documentation and fix property c…
Copilot Sep 19, 2025
6c42203
Address code review feedback: improve tests, remove docs, split test …
Copilot Sep 19, 2025
f666ff0
Fix tests to use S.T.J. source generator
MackinnonBuck Sep 19, 2025
3ac0678
Styling fixes
MackinnonBuck Sep 19, 2025
9e4eba3
Simplify Icons property documentation as suggested in code review
Copilot Sep 23, 2025
8a6f6fd
Rename Icon.Src property to Icon.Source for more .NET-y naming
Copilot Sep 23, 2025
2e077dc
Add icon support to McpServerTool infrastructure and attributes
Copilot Sep 23, 2025
6de63f5
Cleanup
MackinnonBuck Sep 23, 2025
4bcbe1c
Refactor icon tests to use Delegate overload instead of MethodInfo pa…
Copilot Sep 23, 2025
a25abde
Change Icon.Sizes property from string to IList<string> per spec update
Copilot Sep 30, 2025
ae081ca
Add icon support to Prompts and Resources (CreateOptions and Attributes)
Copilot Oct 1, 2025
f37de15
Add client-server integration tests for Icons and WebsiteUrl
Copilot Oct 1, 2025
8e423fd
Fix nullability warnings
MackinnonBuck Oct 1, 2025
803e326
Revert "Add client-server integration tests for Icons and WebsiteUrl"
MackinnonBuck Oct 1, 2025
38b4032
More nullability fixes
MackinnonBuck Oct 1, 2025
0cd7f87
Add more tests
MackinnonBuck Oct 1, 2025
facca54
Address code review feedback: add Theme property, fix docs, improve t…
Copilot Oct 7, 2025
bb762b2
Update Icon tests to exercise Theme property
Copilot Oct 7, 2025
608cf0d
Add Theme property assertions to client-server integration tests
Copilot Oct 7, 2025
c2a3252
Fix nullability
jozkee Oct 7, 2025
45dd79a
Merge branch 'main' into copilot/fix-753
jozkee Oct 7, 2025
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
85 changes: 85 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/Icon.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
24 changes: 24 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/Implementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
9 changes: 9 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/Prompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
9 changes: 9 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
10 changes: 10 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/ResourceTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -108,6 +117,7 @@ public sealed class ResourceTemplate : IBaseMetadata
Description = Description,
MimeType = MimeType,
Annotations = Annotations,
Icons = Icons,
Meta = Meta,
McpServerResource = McpServerResource,
};
Expand Down
9 changes: 9 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ public JsonElement? OutputSchema
[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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
Title = options?.Title,
Description = options?.Description ?? function.Description,
Arguments = args,
Icons = options?.Icons,
};

return new AIFunctionMcpServerPrompt(function, prompt, options?.Metadata ?? []);
Expand All @@ -148,6 +149,12 @@ private static McpServerPromptCreateOptions DeriveOptions(MethodInfo method, Mcp
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
Title = options?.Title,
Description = options?.Description,
MimeType = options?.MimeType ?? "application/octet-stream",
Icons = options?.Icons,
};

return new AIFunctionMcpServerResource(function, resource, options?.Metadata ?? []);
Expand All @@ -233,6 +234,12 @@ private static McpServerResourceCreateOptions DeriveOptions(MemberInfo member, M
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
Description = options?.Description ?? function.Description,
InputSchema = function.JsonSchema,
OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
Icons = options?.Icons,
};

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

if (newOptions.Icons is null && toolAttr.IconSource is { Length: > 0 } iconSource)
{
newOptions.Icons = [new() { Source = iconSource }];
}

newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
}

Expand Down
15 changes: 15 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpServerPromptAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,19 @@ public McpServerPromptAttribute()

/// <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; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Text.Json;

Expand Down Expand Up @@ -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>
Expand All @@ -90,5 +99,6 @@ internal McpServerPromptCreateOptions Clone() =>
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}
15 changes: 15 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpServerResourceAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,19 @@ public McpServerResourceAttribute()

/// <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; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Text.Json;

Expand Down Expand Up @@ -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>
Expand All @@ -107,5 +116,6 @@ internal McpServerResourceCreateOptions Clone() =>
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}
15 changes: 15 additions & 0 deletions src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,19 @@ public bool ReadOnly
/// </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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -182,5 +190,6 @@ internal McpServerToolCreateOptions Clone() =>
SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions,
Metadata = Metadata,
Icons = Icons,
};
}
Loading