Skip to content

Commit fd30845

Browse files
committed
Fix trimming warnings by using generic type registration methods
1 parent efff51f commit fd30845

5 files changed

Lines changed: 122 additions & 6 deletions

File tree

PowerShell.MCP.Proxy/Extensions/LocalizedMcpServerBuilderExtensions.cs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Reflection;
23
using System.Text.Json;
34
using System.Text.Json.Nodes;
@@ -16,6 +17,7 @@ public static class LocalizedMcpServerBuilderExtensions
1617
/// Adds types marked with McpServerPromptTypeAttribute from the given assembly as prompts to the server,
1718
/// with support for localized prompt names via LocalizedNameAttribute.
1819
/// </summary>
20+
[RequiresUnreferencedCode("This method uses reflection to discover prompt types and methods.")]
1921
public static IMcpServerBuilder WithLocalizedPromptsFromAssembly(
2022
this IMcpServerBuilder builder,
2123
Assembly? promptAssembly = null,
@@ -131,7 +133,119 @@ paramNode is JsonObject paramObject &&
131133
}
132134
}
133135

134-
private static object CreateTarget(IServiceProvider? services, Type targetType)
136+
/// <summary>
137+
/// Adds the specified prompt type with support for localized prompt names via LocalizedNameAttribute.
138+
/// This generic version is trimming-safe.
139+
/// </summary>
140+
public static IMcpServerBuilder WithLocalizedPrompts<[DynamicallyAccessedMembers(
141+
DynamicallyAccessedMemberTypes.PublicMethods |
142+
DynamicallyAccessedMemberTypes.NonPublicMethods |
143+
DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TPromptType>(
144+
this IMcpServerBuilder builder,
145+
JsonSerializerOptions? serializerOptions = null)
146+
where TPromptType : class
147+
{
148+
ArgumentNullException.ThrowIfNull(builder);
149+
150+
try
151+
{
152+
var promptType = typeof(TPromptType);
153+
var promptMethods = promptType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
154+
.Where(m => m.GetCustomAttribute<McpServerPromptAttribute>() is not null);
155+
156+
foreach (var promptMethod in promptMethods)
157+
{
158+
// Get localized name from LocalizedNameAttribute if present
159+
string? localizedName = null;
160+
try
161+
{
162+
if (promptMethod.GetCustomAttribute<LocalizedNameAttribute>() is { } localizedNameAttr)
163+
{
164+
localizedName = localizedNameAttr.Name;
165+
}
166+
}
167+
catch
168+
{
169+
// Ignore localization errors
170+
}
171+
172+
// Build parameter name mappings for localized descriptions
173+
var parameterNameMappings = new Dictionary<string, string>();
174+
foreach (var param in promptMethod.GetParameters())
175+
{
176+
var resourceDescAttr = param.GetCustomAttribute<ResourceDescriptionAttribute>();
177+
if (resourceDescAttr is not null)
178+
{
179+
parameterNameMappings[param.Name!] = resourceDescAttr.Description;
180+
}
181+
}
182+
183+
// Create schema options to inject localized titles for parameters
184+
var schemaCreateOptions = new AIJsonSchemaCreateOptions
185+
{
186+
TransformSchemaNode = (context, node) =>
187+
{
188+
if (node.GetValueKind() == JsonValueKind.Object && node is JsonObject jsonObject)
189+
{
190+
foreach (var kvp in parameterNameMappings)
191+
{
192+
var paramName = kvp.Key;
193+
var localizedTitle = kvp.Value;
194+
195+
if (jsonObject.TryGetPropertyValue(paramName, out var paramNode) &&
196+
paramNode is JsonObject paramObject &&
197+
!paramObject.ContainsKey("title"))
198+
{
199+
paramObject["title"] = localizedTitle;
200+
}
201+
}
202+
}
203+
return node;
204+
}
205+
};
206+
207+
var options = new McpServerPromptCreateOptions
208+
{
209+
Services = null,
210+
SerializerOptions = serializerOptions,
211+
Name = localizedName,
212+
SchemaCreateOptions = schemaCreateOptions
213+
};
214+
215+
if (promptMethod.IsStatic)
216+
{
217+
builder.Services.AddSingleton<McpServerPrompt>(services =>
218+
{
219+
options.Services = services;
220+
return McpServerPrompt.Create(promptMethod, options: options);
221+
});
222+
}
223+
else
224+
{
225+
builder.Services.AddSingleton<McpServerPrompt>(services =>
226+
{
227+
options.Services = services;
228+
return McpServerPrompt.Create(
229+
promptMethod,
230+
r => CreateTarget(r.Services, promptType),
231+
options);
232+
});
233+
}
234+
}
235+
236+
return builder;
237+
}
238+
catch (Exception ex)
239+
{
240+
Console.Error.WriteLine($"[ERROR] WithLocalizedPrompts<{typeof(TPromptType).Name}> failed: {ex.Message}");
241+
Console.Error.WriteLine($"[ERROR] Stack trace: {ex.StackTrace}");
242+
throw;
243+
}
244+
}
245+
246+
private static object CreateTarget(
247+
IServiceProvider? services,
248+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type targetType)
135249
{
136250
if (services is not null && services.GetService(targetType) is { } instance)
137251
{

PowerShell.MCP.Proxy/PowerShell.MCP.Proxy.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<Version>1.3.9.0</Version>
9-
<NoWarn>$(NoWarn);IL2026;IL2067;IL2075;NETSDK1206</NoWarn>
9+
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
1010

1111
<!-- Single File デプロイ設定 -->
1212
<PublishSingleFile>true</PublishSingleFile>

PowerShell.MCP.Proxy/Program.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using Microsoft.Extensions.Hosting;
33
using Microsoft.Extensions.Logging;
44
using PowerShell.MCP.Proxy.Services;
5+
using PowerShell.MCP.Proxy.Tools;
6+
using PowerShell.MCP.Proxy.Prompts;
57
using System.Reflection;
68

79
namespace PowerShell.MCP.Proxy
@@ -30,8 +32,8 @@ public static async Task Main(string[] args)
3032
builder.Services
3133
.AddMcpServer()
3234
.WithStdioServerTransport()
33-
.WithToolsFromAssembly()
34-
.WithLocalizedPromptsFromAssembly(Assembly.GetExecutingAssembly()); // Use localized prompts
35+
.WithTools<PowerShellTools>()
36+
.WithLocalizedPrompts<PowerShellPrompts>();
3537

3638
await builder.Build().RunAsync();
3739
}

PowerShell.MCP.Proxy/Prompts/PowerShellPrompts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace PowerShell.MCP.Proxy.Prompts;
66

77
[McpServerPromptType]
8-
public static class PowerShellPrompts
8+
public class PowerShellPrompts
99
{
1010
[McpServerPrompt]
1111
[LocalizedName("Prompt_AnalyzeContent_Name")]

PowerShell.MCP.Proxy/Tools/PowerShellTools.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace PowerShell.MCP.Proxy.Tools;
77

88
[McpServerToolType]
9-
public static class PowerShellTools
9+
public class PowerShellTools
1010
{
1111
// Error message constant definitions
1212
private const string ERROR_CONSOLE_NOT_RUNNING = "The PowerShell 7 console is not running.";

0 commit comments

Comments
 (0)