Skip to content

Commit 2e077dc

Browse files
Add icon support to McpServerTool infrastructure and attributes
Co-authored-by: MackinnonBuck <[email protected]>
1 parent 8a6f6fd commit 2e077dc

File tree

4 files changed

+115
-0
lines changed

4 files changed

+115
-0
lines changed

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs

Lines changed: 10 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)
@@ -177,6 +178,15 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe
177178
}
178179

179180
newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
181+
182+
// Handle icon from attribute if not already specified in options
183+
if (newOptions.Icons is null && !string.IsNullOrEmpty(toolAttr.IconSource))
184+
{
185+
newOptions.Icons = new List<Icon>
186+
{
187+
new() { Source = toolAttr.IconSource }
188+
};
189+
}
180190
}
181191

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

src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,19 @@ public bool ReadOnly
254254
/// </para>
255255
/// </remarks>
256256
public bool UseStructuredContent { get; set; }
257+
258+
/// <summary>
259+
/// Gets or sets the source URI for the tool's icon.
260+
/// </summary>
261+
/// <remarks>
262+
/// <para>
263+
/// This can be an HTTP/HTTPS URL pointing to an image file or a data URI with base64-encoded image data.
264+
/// When specified, a single icon will be added to the tool.
265+
/// </para>
266+
/// <para>
267+
/// For more advanced icon configuration (multiple icons, MIME type specification, size characteristics),
268+
/// use <see cref="McpServerToolCreateOptions.Icons"/> when creating the tool programmatically.
269+
/// </para>
270+
/// </remarks>
271+
public string? IconSource { get; set; }
257272
}

src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ public sealed class McpServerToolCreateOptions
164164
/// </remarks>
165165
public IReadOnlyList<object>? Metadata { get; set; }
166166

167+
/// <summary>
168+
/// Gets or sets the icons for this tool.
169+
/// </summary>
170+
/// <remarks>
171+
/// This can be used by clients to display the tool's icon in a user interface.
172+
/// </remarks>
173+
public IList<Icon>? Icons { get; set; }
174+
167175
/// <summary>
168176
/// Creates a shallow clone of the current <see cref="McpServerToolCreateOptions"/> instance.
169177
/// </summary>
@@ -182,5 +190,6 @@ internal McpServerToolCreateOptions Clone() =>
182190
SerializerOptions = SerializerOptions,
183191
SchemaCreateOptions = SchemaCreateOptions,
184192
Metadata = Metadata,
193+
Icons = Icons,
185194
};
186195
}

tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,87 @@ Instance JSON document does not match the specified schema.
678678

679679
record Person(string Name, int Age);
680680

681+
[Fact]
682+
public void SupportsIconsInCreateOptions()
683+
{
684+
var icons = new List<Icon>
685+
{
686+
new() { Source = "https://example.com/icon.png", MimeType = "image/png", Sizes = "48x48" },
687+
new() { Source = "https://example.com/icon.svg", MimeType = "image/svg+xml", Sizes = "any" }
688+
};
689+
690+
McpServerTool tool = McpServerTool.Create(() => "test", new McpServerToolCreateOptions
691+
{
692+
Icons = icons
693+
});
694+
695+
Assert.NotNull(tool.ProtocolTool.Icons);
696+
Assert.Equal(2, tool.ProtocolTool.Icons.Count);
697+
Assert.Equal("https://example.com/icon.png", tool.ProtocolTool.Icons[0].Source);
698+
Assert.Equal("image/png", tool.ProtocolTool.Icons[0].MimeType);
699+
Assert.Equal("48x48", tool.ProtocolTool.Icons[0].Sizes);
700+
Assert.Equal("https://example.com/icon.svg", tool.ProtocolTool.Icons[1].Source);
701+
Assert.Equal("image/svg+xml", tool.ProtocolTool.Icons[1].MimeType);
702+
Assert.Equal("any", tool.ProtocolTool.Icons[1].Sizes);
703+
}
704+
705+
[Fact]
706+
public void SupportsIconSourceInAttribute()
707+
{
708+
MethodInfo? method = typeof(IconTestClass).GetMethod(nameof(IconTestClass.ToolWithIconSource));
709+
Assert.NotNull(method);
710+
711+
McpServerTool tool = McpServerTool.Create(method, new IconTestClass());
712+
713+
Assert.NotNull(tool.ProtocolTool.Icons);
714+
Assert.Single(tool.ProtocolTool.Icons);
715+
Assert.Equal("https://example.com/tool-icon.png", tool.ProtocolTool.Icons[0].Source);
716+
Assert.Null(tool.ProtocolTool.Icons[0].MimeType);
717+
Assert.Null(tool.ProtocolTool.Icons[0].Sizes);
718+
}
719+
720+
[Fact]
721+
public void CreateOptionsIconsOverrideAttributeIconSource()
722+
{
723+
MethodInfo? method = typeof(IconTestClass).GetMethod(nameof(IconTestClass.ToolWithIconSource));
724+
Assert.NotNull(method);
725+
726+
var optionsIcons = new List<Icon>
727+
{
728+
new() { Source = "https://example.com/override-icon.svg", MimeType = "image/svg+xml" }
729+
};
730+
731+
McpServerTool tool = McpServerTool.Create(method, new IconTestClass(), new McpServerToolCreateOptions
732+
{
733+
Icons = optionsIcons
734+
});
735+
736+
Assert.NotNull(tool.ProtocolTool.Icons);
737+
Assert.Single(tool.ProtocolTool.Icons);
738+
Assert.Equal("https://example.com/override-icon.svg", tool.ProtocolTool.Icons[0].Source);
739+
Assert.Equal("image/svg+xml", tool.ProtocolTool.Icons[0].MimeType);
740+
}
741+
742+
[Fact]
743+
public void SupportsToolWithoutIcons()
744+
{
745+
MethodInfo? method = typeof(IconTestClass).GetMethod(nameof(IconTestClass.ToolWithoutIcon));
746+
Assert.NotNull(method);
747+
748+
McpServerTool tool = McpServerTool.Create(method, new IconTestClass());
749+
750+
Assert.Null(tool.ProtocolTool.Icons);
751+
}
752+
753+
private class IconTestClass
754+
{
755+
[McpServerTool(IconSource = "https://example.com/tool-icon.png")]
756+
public string ToolWithIconSource() => "result";
757+
758+
[McpServerTool]
759+
public string ToolWithoutIcon() => "result";
760+
}
761+
681762
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
682763
[JsonSerializable(typeof(DisposableToolType))]
683764
[JsonSerializable(typeof(AsyncDisposableToolType))]

0 commit comments

Comments
 (0)