11using System . ComponentModel ;
2+ using System . Diagnostics . CodeAnalysis ;
3+ using System . Reflection ;
4+ using System . Text . Json ;
25using Devlooped . Agents . AI ;
36using Devlooped . Extensions . AI ;
47using Microsoft . Agents . AI ;
710using Microsoft . Extensions . Configuration ;
811using Microsoft . Extensions . DependencyInjection . Extensions ;
912using Microsoft . Extensions . Hosting ;
13+ using ModelContextProtocol . Server ;
1014
1115namespace Microsoft . Extensions . DependencyInjection ;
1216
@@ -16,6 +20,52 @@ namespace Microsoft.Extensions.DependencyInjection;
1620[ EditorBrowsable ( EditorBrowsableState . Never ) ]
1721public static class ConfigurableAgentsExtensions
1822{
23+ /// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
24+ /// <typeparam name="TToolType">The tool type.</typeparam>
25+ /// <param name="builder">The builder instance.</param>
26+ /// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
27+ /// <returns>The builder provided in <paramref name="builder"/>.</returns>
28+ /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
29+ /// <remarks>
30+ /// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TToolType"/>
31+ /// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="AIFunction"/>
32+ /// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
33+ /// </remarks>
34+ public static IAIAgentsBuilder WithTools < [ DynamicallyAccessedMembers (
35+ DynamicallyAccessedMemberTypes . PublicMethods |
36+ DynamicallyAccessedMemberTypes . NonPublicMethods |
37+ DynamicallyAccessedMemberTypes . PublicConstructors ) ] TToolType > (
38+ this IAIAgentsBuilder builder ,
39+ JsonSerializerOptions ? serializerOptions = null ,
40+ ServiceLifetime lifetime = ServiceLifetime . Singleton )
41+ {
42+ Throw . IfNull ( builder ) ;
43+
44+ // Preserve existing registration if any, such as when using Devlooped.Extensions.DependencyInjection
45+ // via [Service] attribute or by convention.
46+ builder . Services . TryAdd ( ServiceDescriptor . Describe ( typeof ( TToolType ) , typeof ( TToolType ) , lifetime ) ) ;
47+
48+ serializerOptions ??= ToolJsonOptions . Default ;
49+
50+ foreach ( var toolMethod in typeof ( TToolType ) . GetMethods ( BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Static | BindingFlags . Instance ) )
51+ {
52+ if ( toolMethod . GetCustomAttribute < McpServerToolAttribute > ( ) is { } toolAttribute )
53+ {
54+ var function = toolMethod . IsStatic
55+ ? AIFunctionFactory . Create ( toolMethod , null , toolAttribute . Name ?? ToolJsonOptions . Default . PropertyNamingPolicy ! . ConvertName ( toolMethod . Name ) )
56+ : AIFunctionFactory . Create ( toolMethod , args => args . Services ? . GetRequiredService ( typeof ( TToolType ) ) ??
57+ throw new InvalidOperationException ( "Could not determine target instance for tool." ) ,
58+ new AIFunctionFactoryOptions { Name = toolAttribute . Name ?? ToolJsonOptions . Default . PropertyNamingPolicy ! . ConvertName ( toolMethod . Name ) } ) ;
59+
60+ builder . Services . TryAdd ( ServiceDescriptor . DescribeKeyed (
61+ typeof ( AIFunction ) , function . Name ,
62+ ( _ , _ ) => function , lifetime ) ) ;
63+ }
64+ }
65+
66+ return builder ;
67+ }
68+
1969 /// <summary>
2070 /// Adds AI agents to the host application builder based on configuration.
2171 /// </summary>
@@ -24,8 +74,7 @@ public static class ConfigurableAgentsExtensions
2474 /// <param name="configureOptions">Optional action to configure options for each agent.</param>
2575 /// <param name="prefix">The configuration prefix for agents, defaults to "ai:agents".</param>
2676 /// <returns>The host application builder with AI agents added.</returns>
27- public static TBuilder AddAIAgents < TBuilder > ( this TBuilder builder , Action < string , AIAgentBuilder > ? configurePipeline = default , Action < string , ChatClientAgentOptions > ? configureOptions = default , string prefix = "ai:agents" )
28- where TBuilder : IHostApplicationBuilder
77+ public static IAIAgentsBuilder AddAIAgents ( this IHostApplicationBuilder builder , Action < string , AIAgentBuilder > ? configurePipeline = default , Action < string , ChatClientAgentOptions > ? configureOptions = default , string prefix = "ai:agents" )
2978 {
3079 builder . AddChatClients ( ) ;
3180
@@ -61,7 +110,7 @@ public static TBuilder AddAIAgents<TBuilder>(this TBuilder builder, Action<strin
61110 => sp . GetRequiredKeyedService < AIAgent > ( name ) ) ) ;
62111 }
63112
64- return builder ;
113+ return new DefaultAIAgentsBuilder ( builder ) ;
65114 }
66115
67116 /// <summary>Gets an AI agent by name (case-insensitive) from the service provider.</summary>
0 commit comments