Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Tool.cs
1using System;
3using System.Diagnostics.CodeAnalysis;
4using System.Reflection;
5using System.Threading.Tasks;
13using Waher.Script;
15
17{
21 public class Tool : ProtectedMethod
22 {
23 private const string McpToolResultTitle = "Result";
24 private const string McpToolResultDescription = "Result returned after executing the tool.";
25
26 private Icons? icons = null;
27
50 public Tool(MethodInfo Method, string Title, string Description,
52 bool Idempotent, bool OpenWorldAccess,
53 params KeyValuePair<string, object>[] MetaData)
54 : base(Method, false)
55 {
56 this.Title = Title;
57 this.Description = Description;
58 this.IconsMethod = IconsMethod;
59 this.CanModifyEnvironment = CanModifyEnvironment;
60 this.CanDestroyEnvironment = CanDestroyEnvironment;
61 this.Idempotent = Idempotent;
62 this.OpenWorldAccess = OpenWorldAccess;
63 this.MetaData = MetaData;
64 this.HasStructuredReturnValue = this.HasReturnValue;
65
66 Type ReturnType = this.Method.ReturnType;
67
68 while (this.HasStructuredReturnValue)
69 {
70 if (HttpMcpServerResource.TryGetEncodingContentBlock(ReturnType,
72 {
73 this.HasStructuredReturnValue = ContentBlock.IsStructuredContent;
74 break;
75 }
76 else if (typeof(IContentBlock).IsAssignableFrom(Method.ReturnType))
77 {
80 this.HasStructuredReturnValue = false;
81 break;
82 }
83 else if (ReturnType.IsGenericType &&
84 ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
85 {
86 Type[] TypeArguments = ReturnType.GetGenericArguments();
87 if (TypeArguments.Length == 1)
88 ReturnType = TypeArguments[0];
89 else
90 break;
91 }
92 else
93 break;
94 }
95 }
96
100 public string Title { get; }
101
108 public string Description { get; }
109
115 public string IconsMethod { get; }
116
121 public bool CanModifyEnvironment { get; }
122
130 public bool CanDestroyEnvironment { get; }
131
139 public bool Idempotent { get; }
140
145 public bool OpenWorldAccess { get; }
146
150 public KeyValuePair<string, object>[] MetaData { get; }
151
155 public bool HasStructuredReturnValue { get; }
156
162 public async Task<Dictionary<string, object>> ToJson(HttpMcpServerResource Resource)
163 {
164 this.icons ??= await GetIcons(Resource, this.IconsMethod);
165
166 Dictionary<string, object> Annotations = new Dictionary<string, object>()
167 {
168 { "readOnlyHint", this.CanModifyEnvironment },
169 { "destructiveHint", this.CanDestroyEnvironment },
170 { "idempotentHint", this.Idempotent },
171 { "openWorldHint", this.OpenWorldAccess }
172 };
173 Dictionary<string, object> Result = new Dictionary<string, object>()
174 {
175 { "name", this.Method.Name },
176 { "execution", new Dictionary<string,object>()
177 {
178 { "taskSupport", "optional" }
179 }
180 },
181 { "inputSchema", GenerateSchema(this.Method) },
182 { "annotations", Annotations }
183 };
184
185 if (!string.IsNullOrEmpty(this.Title))
186 Result.Add("title", this.Title);
187
188 if (!string.IsNullOrEmpty(this.Description))
189 Result.Add("description", this.Description);
190
191 if (!this.icons.Empty)
192 Result.Add("icons", this.icons.ToJson());
193
195 {
196 McpParameterAttribute ReturnInfo = this.Method.ReturnParameter.GetCustomAttribute<McpParameterAttribute>(true);
197 IEnumerable<McpEnumValueAttribute>? EnumValues = this.Method.ReturnType.IsEnum ?
198 this.Method.ReturnParameter.GetCustomAttributes<McpEnumValueAttribute>(true) : null;
199
200 Result.Add("outputSchema", GenerateOutputSchema(this.Method.ReturnType,
201 ReturnInfo, EnumValues));
202 }
203
204 if ((this.MetaData?.Length ?? 0) > 0)
205 {
206 Dictionary<string, object> MetaData = new Dictionary<string, object>();
207
208 foreach (KeyValuePair<string, object> P in this.MetaData!)
209 MetaData[P.Key] = P.Value;
210
211 Result["_meta"] = MetaData;
212 }
213
214 return Result;
215 }
216
217 internal static async Task<Icons> GetIcons(HttpMcpServerResource Resource, string IconsMethod)
218 {
219 Icons Icons;
220
221 if (string.IsNullOrEmpty(IconsMethod))
222 Icons = new Icons();
223 else
224 {
225 MethodInfo? MI = Resource.GetType().GetMethod(IconsMethod,
226 BindingFlags.Static | BindingFlags.Instance |
227 BindingFlags.Public | BindingFlags.NonPublic);
228
229 if (MI is null)
230 Icons = new Icons();
231 else
232 {
233 object? Obj = await ScriptNode.WaitPossibleTask(MI.Invoke(Resource, null));
234
235 if (Obj is Icons Typed)
236 Icons = Typed;
237 else if (Obj is Icon[] IconArray)
238 Icons = new Icons(IconArray);
239 else if (Obj is Icon SingleIcon)
240 Icons = new Icons(SingleIcon);
241 else if (Obj is null)
242 Icons = new Icons();
243 else
244 {
245 throw new ArgumentException("Method " + IconsMethod +
246 "returned an invalid type: " + Obj.GetType().FullName,
247 nameof(IconsMethod));
248 }
249 }
250 }
251
252 if (Icons.Empty)
253 Icons = Resource.Icons;
254
255 return Icons;
256 }
257
263 internal static Dictionary<string, object> GenerateSchema(MethodInfo Method)
264 {
265 ParameterInfo[] Parameters = Method.GetParameters();
266 Dictionary<string, object> Result = new Dictionary<string, object>()
267 {
268 { "type", "object" }
269 };
270
271 if (Parameters.Length == 0)
272 Result["additionalProperties"] = false;
273 else
274 {
275 Dictionary<string, object> Properties = new Dictionary<string, object>();
277
278 foreach (ParameterInfo Parameter in Parameters)
279 {
280 Type ParameterType = Parameter.ParameterType;
281
282 if (ParameterType == typeof(HttpRequest) ||
283 ParameterType == typeof(HttpResponse))
284 {
285 continue;
286 }
287
288 if (!Parameter.IsOptional && !Parameter.HasDefaultValue)
289 Required.Add(Parameter.Name);
290
291 McpParameterAttribute ParameterInfo = Parameter.GetCustomAttribute<McpParameterAttribute>(true);
292 IEnumerable<McpEnumValueAttribute>? EnumValues = ParameterType.IsEnum ?
293 Parameter.GetCustomAttributes<McpEnumValueAttribute>(true) : null;
294
295 Properties[Parameter.Name] = GenerateSchema(ParameterType,
296 Parameter.HasDefaultValue, Parameter.DefaultValue, ParameterInfo,
297 EnumValues);
298 }
299
300 Result["properties"] = Properties;
301 Result["required"] = Required.ToArray();
302 }
303
304 return Result;
305 }
306
307 internal static Dictionary<string, object> GenerateOutputSchema(Type ReturnType,
308 McpParameterAttribute? ParameterInfo, IEnumerable<McpEnumValueAttribute>? EnumValues)
309 {
310 Dictionary<string, object> Result = new Dictionary<string, object>()
311 {
312 { "type", "object" },
313 { "result", GenerateSchema(ReturnType, false, null, ParameterInfo, EnumValues) },
314 { "title", McpToolResultTitle },
315 { "description", McpToolResultDescription },
316 };
317
318 if (Expression.IsVoid(ReturnType))
319 Result["required"] = Array.Empty<string>();
320 else
321 Result["required"] = new string[] { "result" };
322
323 return Result;
324 }
325
335 internal static object GenerateSchema(Type T, bool HasDefault, object? Default,
336 McpParameterAttribute? ParameterInfo, IEnumerable<McpEnumValueAttribute>? EnumValues)
337 {
338 Dictionary<string, object?> Result = new Dictionary<string, object?>();
339 bool EmitDefault = HasDefault;
340
341 if (T.IsEnum)
342 {
344
345 if (EnumValues is null)
346 EnumValuesList = null;
347 else
348 {
349 EnumValuesList = new ChunkedList<Dictionary<string, object>>();
350
351 foreach (McpEnumValueAttribute EnumValue in EnumValues)
352 {
353 EnumValuesList.Add(new Dictionary<string, object>()
354 {
355 { "const", EnumValue.Value.ToString() },
356 { "title", EnumValue.Title ?? EnumValue.Value.ToString() }
357 });
358 }
359 }
360
361 if (Attribute.IsDefined(T, typeof(FlagsAttribute)))
362 {
363 Result["type"] = "array";
364
365 if (EnumValuesList is null)
366 {
367 Result["items"] = new Dictionary<string, object>()
368 {
369 { "type", "string" },
370 { "enum", Enum.GetNames(T) }
371 };
372 }
373 else
374 {
375 Result["items"] = new Dictionary<string, object>()
376 {
377 { "anyOf", EnumValuesList.ToArray() }
378 };
379 }
380 }
381 else
382 {
383 Result["type"] = "string";
384
385 if (EnumValuesList is null)
386 Result["enum"] = Enum.GetNames(T);
387 else
388 Result["oneOf"] = EnumValuesList.ToArray();
389 }
390 }
391 else
392 {
393 switch (Type.GetTypeCode(T))
394 {
395 case TypeCode.Empty:
396 Result["type"] = "null";
397 break;
398
399 case TypeCode.Object:
400 if (T == typeof(CaseInsensitiveString))
401 Result["type"] = "string";
402 else if (T == typeof(Uri))
403 {
404 Result["type"] = "string";
405 Result["format"] = "uri";
406 }
407 else if (T.IsArray)
408 {
409 Result["type"] = "array";
410 Result["items"] = GenerateSchema(T.GetElementType()!, false, null, null, null);
411 }
412 else if (T == typeof(Dictionary<string, object>))
413 {
414 EmitDefault = false;
415 Result["type"] = "object";
416 //Result["additionalProperties"] = true;// new Dictionary<string, object>();
417 }
418 else
419 {
420 if (T.IsGenericType)
421 {
422 Type GenericType = T.GetGenericTypeDefinition();
423
424 if (GenericType == typeof(Nullable<>) ||
425 GenericType == typeof(Task<>))
426 {
427 return GenerateSchema(T.GenericTypeArguments[0], true, Default,
428 ParameterInfo, EnumValues);
429 }
430 }
431
432 Dictionary<string, object> Properties = new Dictionary<string, object>();
433
434 EmitDefault = false;
435 Result["type"] = "object";
436 Result["properties"] = Properties;
437
438 foreach (FieldInfo FI in T.GetFields(BindingFlags.Public | BindingFlags.Instance))
439 {
440 Type FieldType = FI.FieldType;
441 McpParameterAttribute FieldInfo = FI.GetCustomAttribute<McpParameterAttribute>(true);
442 IEnumerable<McpEnumValueAttribute>? EnumValues2 = FieldType.IsEnum ?
443 FI.GetCustomAttributes<McpEnumValueAttribute>(true) : null;
444
445 object? FieldDefault = Default is null ? null : FI.GetValue(Default);
446 Properties[FI.Name] = GenerateSchema(FieldType, !(Default is null),
447 FieldDefault, FieldInfo, EnumValues2);
448 }
449
450 foreach (PropertyInfo PI in T.GetProperties(BindingFlags.Public | BindingFlags.Instance))
451 {
452 Type PropertyType = PI.PropertyType;
453 McpParameterAttribute PropertyInfo = PI.GetCustomAttribute<McpParameterAttribute>(true);
454 IEnumerable<McpEnumValueAttribute>? EnumValues2 = PropertyType.IsEnum ?
455 PI.GetCustomAttributes<McpEnumValueAttribute>(true) : null;
456
457 object? PropertyDefault = Default is null ? null : PI.GetValue(Default);
458 Properties[PI.Name] = GenerateSchema(PropertyType, !(Default is null),
459 PropertyDefault, PropertyInfo, EnumValues2);
460 }
461 }
462 break;
463
464 case TypeCode.DBNull:
465 Result["type"] = "null";
466 break;
467
468 case TypeCode.Boolean:
469 Result["type"] = "boolean";
470 break;
471
472 case TypeCode.Char:
473 Result["type"] = "string";
474 break;
475
476 case TypeCode.SByte:
477 Result["type"] = "integer";
478 Result["minimum"] = sbyte.MinValue;
479 Result["maximum"] = sbyte.MaxValue;
480 break;
481
482 case TypeCode.Byte:
483 Result["type"] = "integer";
484 Result["minimum"] = byte.MinValue;
485 Result["maximum"] = byte.MaxValue;
486 break;
487
488 case TypeCode.Int16:
489 Result["type"] = "integer";
490 Result["minimum"] = short.MinValue;
491 Result["maximum"] = short.MaxValue;
492 break;
493
494 case TypeCode.UInt16:
495 Result["type"] = "integer";
496 Result["minimum"] = ushort.MinValue;
497 Result["maximum"] = ushort.MaxValue;
498 break;
499
500 case TypeCode.Int32:
501 Result["type"] = "integer";
502 Result["minimum"] = int.MinValue;
503 Result["maximum"] = int.MaxValue;
504 break;
505
506 case TypeCode.UInt32:
507 Result["type"] = "integer";
508 Result["minimum"] = uint.MinValue;
509 Result["maximum"] = uint.MaxValue;
510 break;
511
512 case TypeCode.Int64:
513 Result["type"] = "integer";
514 Result["minimum"] = long.MinValue;
515 Result["maximum"] = long.MaxValue;
516 break;
517
518 case TypeCode.UInt64:
519 Result["type"] = "integer";
520 Result["minimum"] = ulong.MinValue;
521 Result["maximum"] = ulong.MaxValue;
522 break;
523
524 case TypeCode.Single:
525 case TypeCode.Double:
526 case TypeCode.Decimal:
527 Result["type"] = "number";
528 break;
529
530 case TypeCode.DateTime:
531 {
532 Result["type"] = "string";
533 Result["format"] = "date-time";
534 }
535 break;
536
537 case TypeCode.String:
538 Result["type"] = "string";
539 break;
540 }
541 }
542
543 if (EmitDefault)
544 Result["default"] = Default;
545
546 ParameterInfo?.Annotate(Result);
547
548 return Result;
549 }
550
563 public bool TryBuildRequest(object? Id, Dictionary<string, object?> Parameters,
564 HttpRequest Request, HttpResponse Response,
565 Dictionary<string, object?>? MetaData,
566 [NotNullWhen(false)] out string? Reason,
567 [NotNullWhen(true)] out object?[]? Arguments)
568 {
569 if (!this.TryBuildRequest(Id, Parameters, MetaData, out Reason, out Arguments))
570 return false;
571
572 if (this.RequestArgument.HasValue)
573 Arguments[this.RequestArgument.Value] = Request;
574
575 if (this.ResponseArgument.HasValue)
576 Arguments[this.ResponseArgument.Value] = Response;
577
578 if (this.IdArgument.HasValue)
579 Arguments[this.IdArgument.Value] = Id;
580
581 return true;
582 }
583
584 }
585}
Represents an HTTP request.
Definition: HttpRequest.cs:22
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
Information about a protected method.
ProtectedMethodArgumentInfo[] Arguments
Arguments
bool HasReturnValue
If the method has a return value.
int? ResponseArgument
Response argument index
Abstract base class for HTTP-based Model Context Protocol (MCP) server resource, as defined in:
Abstract base class for annotated objects.
Definition: Annotations.cs:11
virtual void Annotate(Dictionary< string, object?> Schema)
Annotates a schema object with information in the attribute.
Abstract base class for an MCP Content Block.
Definition: ContentBlock.cs:11
abstract bool IsStructuredContent
If the content block is encoded as structured content.
Definition: ContentBlock.cs:43
An optionally-sized icon that can be displayed in a user interface.
Definition: Icon.cs:10
Base interface to add icons property.
Definition: Icons.cs:11
Icons()
Base interface to add icons property.
Definition: Icons.cs:15
bool Empty
If there are icons defined.
Definition: Icons.cs:44
Dictionary< string, object >[] ToJson()
Converts object to a generic representation.
Definition: Icons.cs:81
Contains information about an MCP Server Resource
Definition: Resource.cs:14
Contains information about an MCP Server Tool
Definition: Tool.cs:22
string Description
A human-readable description of the tool.
Definition: Tool.cs:108
bool TryBuildRequest(object? Id, Dictionary< string, object?> Parameters, HttpRequest Request, HttpResponse Response, Dictionary< string, object?>? MetaData, [NotNullWhen(false)] out string? Reason, [NotNullWhen(true)] out object?[]? Arguments)
Tries to build a request for the method, based on the provided named parameters.
Definition: Tool.cs:563
bool OpenWorldAccess
If true, this tool may interact with an "open world" of external entities.If false,...
Definition: Tool.cs:145
async Task< Dictionary< string, object > > ToJson(HttpMcpServerResource Resource)
Converts object to a generic representation.
Definition: Tool.cs:162
string IconsMethod
Name of method that returns an Icon?, an an Icon[]? or an Icons? resource representing the prompt....
Definition: Tool.cs:115
string Title
A human-readable title for the tool.
Definition: Tool.cs:100
bool CanDestroyEnvironment
If true, the tool may perform destructive updates to its environment. If false, the tool performs onl...
Definition: Tool.cs:130
bool HasStructuredReturnValue
If the tool returns a structured return value.
Definition: Tool.cs:155
bool Idempotent
If true, calling the tool repeatedly with the same arguments will have no additional effect on its en...
Definition: Tool.cs:139
KeyValuePair< string, object >[] MetaData
Meta-data associated with tool.
Definition: Tool.cs:150
bool CanModifyEnvironment
If the tool can modify the environment. If false, the tool is expected to be read-only and not cause ...
Definition: Tool.cs:121
Tool(MethodInfo Method, string Title, string Description, string IconsMethod, bool CanModifyEnvironment, bool CanDestroyEnvironment, bool Idempotent, bool OpenWorldAccess, params KeyValuePair< string, object >[] MetaData)
Contains information about an MCP Server Tool
Definition: Tool.cs:50
Represents a case-insensitive string.
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static object Instantiate(Type Type, params object[] Arguments)
Returns an instance of the type Type . If one needs to be created, it is. If the constructor requires...
Definition: Types.cs:1454
Class managing a script expression.
Definition: Expression.cs:41
static bool IsVoid(Type ResultType)
Checks if a result object type is equal to void (i.e. its type equal to System.Threading....
Definition: Expression.cs:4735
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
static async Task< object > WaitPossibleTask(object Result)
Waits for any asynchronous process to terminate.
Definition: ScriptNode.cs:441
PropertyType
Type of indexed property.
FieldType
Field Type flags
Definition: FieldType.cs:10