1+ using System . Diagnostics . CodeAnalysis ;
12using System . Reflection ;
23using System . Text . Json ;
34using 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 {
0 commit comments