11
11
namespace ModelContextProtocol . Server ;
12
12
13
13
/// <summary>Provides an <see cref="McpServerTool"/> that's implemented via an <see cref="AIFunction"/>.</summary>
14
- internal sealed class AIFunctionMcpServerTool : McpServerTool
14
+ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
15
15
{
16
16
private readonly ILogger _logger ;
17
17
18
18
/// <summary>
19
19
/// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
20
20
/// </summary>
21
21
public static new AIFunctionMcpServerTool Create (
22
- Delegate method ,
23
- McpServerToolCreateOptions ? options )
22
+ Delegate method ,
23
+ McpServerToolCreateOptions ? options )
24
24
{
25
25
Throw . IfNull ( method ) ;
26
26
@@ -33,139 +33,139 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
33
33
/// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
34
34
/// </summary>
35
35
public static new AIFunctionMcpServerTool Create (
36
- MethodInfo method ,
37
- object ? target ,
38
- McpServerToolCreateOptions ? options )
36
+ MethodInfo method ,
37
+ object ? target ,
38
+ McpServerToolCreateOptions ? options )
39
39
{
40
40
Throw . IfNull ( method ) ;
41
41
42
42
options = DeriveOptions ( method , options ) ;
43
43
44
44
return Create (
45
- AIFunctionFactory . Create ( method , target , CreateAIFunctionFactoryOptions ( method , options ) ) ,
46
- options ) ;
45
+ AIFunctionFactory . Create ( method , target , CreateAIFunctionFactoryOptions ( method , options ) ) ,
46
+ options ) ;
47
47
}
48
48
49
49
/// <summary>
50
50
/// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="MethodInfo"/> instance.
51
51
/// </summary>
52
52
public static new AIFunctionMcpServerTool Create (
53
- MethodInfo method ,
54
- Func < RequestContext < CallToolRequestParams > , object > createTargetFunc ,
55
- McpServerToolCreateOptions ? options )
53
+ MethodInfo method ,
54
+ Func < RequestContext < CallToolRequestParams > , object > createTargetFunc ,
55
+ McpServerToolCreateOptions ? options )
56
56
{
57
57
Throw . IfNull ( method ) ;
58
58
Throw . IfNull ( createTargetFunc ) ;
59
59
60
60
options = DeriveOptions ( method , options ) ;
61
61
62
62
return Create (
63
- AIFunctionFactory . Create ( method , args =>
64
- {
65
- var request = ( RequestContext < CallToolRequestParams > ) args . Context ! [ typeof ( RequestContext < CallToolRequestParams > ) ] ! ;
66
- return createTargetFunc ( request ) ;
67
- } , CreateAIFunctionFactoryOptions ( method , options ) ) ,
68
- options ) ;
63
+ AIFunctionFactory . Create ( method , args =>
64
+ {
65
+ var request = ( RequestContext < CallToolRequestParams > ) args . Context ! [ typeof ( RequestContext < CallToolRequestParams > ) ] ! ;
66
+ return createTargetFunc ( request ) ;
67
+ } , CreateAIFunctionFactoryOptions ( method , options ) ) ,
68
+ options ) ;
69
69
}
70
70
71
71
// TODO: Fix the need for this suppression.
72
72
[ UnconditionalSuppressMessage ( "ReflectionAnalysis" , "IL2111:ReflectionToDynamicallyAccessedMembers" ,
73
- Justification = "AIFunctionFactory ensures that the Type passed to AIFunctionFactoryOptions.CreateInstance has public constructors preserved" ) ]
73
+ Justification = "AIFunctionFactory ensures that the Type passed to AIFunctionFactoryOptions.CreateInstance has public constructors preserved" ) ]
74
74
internal static Func < Type , AIFunctionArguments , object > GetCreateInstanceFunc ( ) =>
75
- static ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors ) ] type , args ) => args . Services is { } services ?
76
- ActivatorUtilities . CreateInstance ( services , type ) :
77
- Activator . CreateInstance ( type ) ! ;
75
+ static ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors ) ] type , args ) => args . Services is { } services ?
76
+ ActivatorUtilities . CreateInstance ( services , type ) :
77
+ Activator . CreateInstance ( type ) ! ;
78
78
79
79
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions (
80
- MethodInfo method , McpServerToolCreateOptions ? options ) =>
81
- new ( )
80
+ MethodInfo method , McpServerToolCreateOptions ? options ) =>
81
+ new ( )
82
+ {
83
+ Name = options ? . Name ?? method . GetCustomAttribute < McpServerToolAttribute > ( ) ? . Name ,
84
+ Description = options ? . Description ,
85
+ MarshalResult = static ( result , _ , cancellationToken ) => new ValueTask < object ? > ( result ) ,
86
+ SerializerOptions = options ? . SerializerOptions ?? McpJsonUtilities . DefaultOptions ,
87
+ ConfigureParameterBinding = pi =>
88
+ {
89
+ if ( pi . ParameterType == typeof ( RequestContext < CallToolRequestParams > ) )
82
90
{
83
- Name = options ? . Name ?? method . GetCustomAttribute < McpServerToolAttribute > ( ) ? . Name ,
84
- Description = options ? . Description ,
85
- MarshalResult = static ( result , _ , cancellationToken ) => new ValueTask < object ? > ( result ) ,
86
- SerializerOptions = options ? . SerializerOptions ?? McpJsonUtilities . DefaultOptions ,
87
- ConfigureParameterBinding = pi =>
91
+ return new ( )
88
92
{
89
- if ( pi . ParameterType == typeof ( RequestContext < CallToolRequestParams > ) )
90
- {
91
- return new ( )
92
- {
93
- ExcludeFromSchema = true ,
94
- BindParameter = ( pi , args ) => GetRequestContext ( args ) ,
95
- } ;
96
- }
93
+ ExcludeFromSchema = true ,
94
+ BindParameter = ( pi , args ) => GetRequestContext ( args ) ,
95
+ } ;
96
+ }
97
97
98
- if ( pi . ParameterType == typeof ( IMcpServer ) )
99
- {
100
- return new ( )
101
- {
102
- ExcludeFromSchema = true ,
103
- BindParameter = ( pi , args ) => GetRequestContext ( args ) ? . Server ,
104
- } ;
105
- }
98
+ if ( pi . ParameterType == typeof ( IMcpServer ) )
99
+ {
100
+ return new ( )
101
+ {
102
+ ExcludeFromSchema = true ,
103
+ BindParameter = ( pi , args ) => GetRequestContext ( args ) ? . Server ,
104
+ } ;
105
+ }
106
106
107
- if ( pi . ParameterType == typeof ( IProgress < ProgressNotificationValue > ) )
107
+ if ( pi . ParameterType == typeof ( IProgress < ProgressNotificationValue > ) )
108
+ {
109
+ // Bind IProgress<ProgressNotificationValue> to the progress token in the request,
110
+ // if there is one. If we can't get one, return a nop progress.
111
+ return new ( )
112
+ {
113
+ ExcludeFromSchema = true ,
114
+ BindParameter = ( pi , args ) =>
115
+ {
116
+ var requestContent = GetRequestContext ( args ) ;
117
+ if ( requestContent ? . Server is { } server &&
118
+ requestContent ? . Params ? . Meta ? . ProgressToken is { } progressToken )
108
119
{
109
- // Bind IProgress<ProgressNotificationValue> to the progress token in the request,
110
- // if there is one. If we can't get one, return a nop progress.
111
- return new ( )
112
- {
113
- ExcludeFromSchema = true ,
114
- BindParameter = ( pi , args ) =>
115
- {
116
- var requestContent = GetRequestContext ( args ) ;
117
- if ( requestContent ? . Server is { } server &&
118
- requestContent ? . Params ? . Meta ? . ProgressToken is { } progressToken )
119
- {
120
- return new TokenProgress ( server , progressToken ) ;
121
- }
122
-
123
- return NullProgress . Instance ;
124
- } ,
125
- } ;
120
+ return new TokenProgress ( server , progressToken ) ;
126
121
}
127
122
128
- if ( options ? . Services is { } services &&
129
- services . GetService < IServiceProviderIsService > ( ) is { } ispis &&
130
- ispis . IsService ( pi . ParameterType ) )
131
- {
132
- return new ( )
133
- {
134
- ExcludeFromSchema = true ,
135
- BindParameter = ( pi , args ) =>
136
- GetRequestContext ( args ) ? . Services ? . GetService ( pi . ParameterType ) ??
137
- ( pi . HasDefaultValue ? null :
138
- throw new ArgumentException ( "No service of the requested type was found." ) ) ,
139
- } ;
140
- }
123
+ return NullProgress . Instance ;
124
+ } ,
125
+ } ;
126
+ }
141
127
142
- if ( pi . GetCustomAttribute < FromKeyedServicesAttribute > ( ) is { } keyedAttr )
143
- {
144
- return new ( )
145
- {
146
- ExcludeFromSchema = true ,
147
- BindParameter = ( pi , args ) =>
148
- ( GetRequestContext ( args ) ? . Services as IKeyedServiceProvider ) ? . GetKeyedService ( pi . ParameterType , keyedAttr . Key ) ??
149
- ( pi . HasDefaultValue ? null :
150
- throw new ArgumentException ( "No service of the requested type was found." ) ) ,
151
- } ;
152
- }
128
+ if ( options ? . Services is { } services &&
129
+ services . GetService < IServiceProviderIsService > ( ) is { } ispis &&
130
+ ispis . IsService ( pi . ParameterType ) )
131
+ {
132
+ return new ( )
133
+ {
134
+ ExcludeFromSchema = true ,
135
+ BindParameter = ( pi , args ) =>
136
+ GetRequestContext ( args ) ? . Services ? . GetService ( pi . ParameterType ) ??
137
+ ( pi . HasDefaultValue ? null :
138
+ throw new ArgumentException ( "No service of the requested type was found." ) ) ,
139
+ } ;
140
+ }
141
+
142
+ if ( pi . GetCustomAttribute < FromKeyedServicesAttribute > ( ) is { } keyedAttr )
143
+ {
144
+ return new ( )
145
+ {
146
+ ExcludeFromSchema = true ,
147
+ BindParameter = ( pi , args ) =>
148
+ ( GetRequestContext ( args ) ? . Services as IKeyedServiceProvider ) ? . GetKeyedService ( pi . ParameterType , keyedAttr . Key ) ??
149
+ ( pi . HasDefaultValue ? null :
150
+ throw new ArgumentException ( "No service of the requested type was found." ) ) ,
151
+ } ;
152
+ }
153
153
154
- return default ;
154
+ return default ;
155
155
156
- static RequestContext < CallToolRequestParams > ? GetRequestContext ( AIFunctionArguments args )
157
- {
158
- if ( args . Context ? . TryGetValue ( typeof ( RequestContext < CallToolRequestParams > ) , out var orc ) is true &&
159
- orc is RequestContext < CallToolRequestParams > requestContext )
160
- {
161
- return requestContext ;
162
- }
156
+ static RequestContext < CallToolRequestParams > ? GetRequestContext ( AIFunctionArguments args )
157
+ {
158
+ if ( args . Context ? . TryGetValue ( typeof ( RequestContext < CallToolRequestParams > ) , out var orc ) is true &&
159
+ orc is RequestContext < CallToolRequestParams > requestContext )
160
+ {
161
+ return requestContext ;
162
+ }
163
163
164
- return null ;
165
- }
166
- } ,
167
- JsonSchemaCreateOptions = options ? . SchemaCreateOptions ,
168
- } ;
164
+ return null ;
165
+ }
166
+ } ,
167
+ JsonSchemaCreateOptions = options ? . SchemaCreateOptions ,
168
+ } ;
169
169
170
170
/// <summary>Creates an <see cref="McpServerTool"/> that wraps the specified <see cref="AIFunction"/>.</summary>
171
171
public static new AIFunctionMcpServerTool Create ( AIFunction function , McpServerToolCreateOptions ? options )
@@ -182,10 +182,10 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
182
182
if ( options is not null )
183
183
{
184
184
if ( options . Title is not null ||
185
- options . Idempotent is not null ||
186
- options . Destructive is not null ||
187
- options . OpenWorld is not null ||
188
- options . ReadOnly is not null )
185
+ options . Idempotent is not null ||
186
+ options . Destructive is not null ||
187
+ options . OpenWorld is not null ||
188
+ options . ReadOnly is not null )
189
189
{
190
190
tool . Annotations = new ( )
191
191
{
@@ -255,7 +255,7 @@ private AIFunctionMcpServerTool(AIFunction function, Tool tool, IServiceProvider
255
255
256
256
/// <inheritdoc />
257
257
public override async ValueTask < CallToolResponse > InvokeAsync (
258
- RequestContext < CallToolRequestParams > request , CancellationToken cancellationToken = default )
258
+ RequestContext < CallToolRequestParams > request , CancellationToken cancellationToken = default )
259
259
{
260
260
Throw . IfNull ( request ) ;
261
261
cancellationToken . ThrowIfCancellationRequested ( ) ;
@@ -282,12 +282,11 @@ public override async ValueTask<CallToolResponse> InvokeAsync(
282
282
}
283
283
catch ( Exception e ) when ( e is not OperationCanceledException )
284
284
{
285
- _logger . LogError ( e , "Error invoking AIFunction tool '{ToolName}' with arguments '{Args}'." ,
286
- request . Params ? . Name , string . Join ( "," , request . Params ? . Arguments ? . Keys ?? Array . Empty < string > ( ) ) ) ;
285
+ ToolCallError ( request . Params ? . Name ?? string . Empty , e ) ;
287
286
288
287
string errorMessage = e is McpException ?
289
- $ "An error occurred invoking '{ request . Params ? . Name } ': { e . Message } " :
290
- $ "An error occurred invoking '{ request . Params ? . Name } '.";
288
+ $ "An error occurred invoking '{ request . Params ? . Name } ': { e . Message } " :
289
+ $ "An error occurred invoking '{ request . Params ? . Name } '.";
291
290
292
291
return new ( )
293
292
{
@@ -336,10 +335,10 @@ public override async ValueTask<CallToolResponse> InvokeAsync(
336
335
_ => new ( )
337
336
{
338
337
Content = [ new ( )
339
- {
340
- Text = JsonSerializer . Serialize ( result , AIFunction . JsonSerializerOptions . GetTypeInfo ( typeof ( object ) ) ) ,
341
- Type = "text"
342
- } ]
338
+ {
339
+ Text = JsonSerializer . Serialize ( result , AIFunction . JsonSerializerOptions . GetTypeInfo ( typeof ( object ) ) ) ,
340
+ Type = "text"
341
+ } ]
343
342
} ,
344
343
} ;
345
344
}
@@ -367,4 +366,7 @@ private static CallToolResponse ConvertAIContentEnumerableToCallToolResponse(IEn
367
366
IsError = allErrorContent && hasAny
368
367
} ;
369
368
}
369
+
370
+ [ LoggerMessage ( Level = LogLevel . Error , Message = "\" {ToolName}\" threw an unhandled exception." ) ]
371
+ private partial void ToolCallError ( string toolName , Exception exception ) ;
370
372
}
0 commit comments