Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
HttpMcpServerResource.cs
1using SkiaSharp;
2using System;
5using System.Reflection;
6using System.Text;
7using System.Threading.Tasks;
8using Waher.Content;
15using Waher.Events;
32using Waher.Script;
34using Waher.Security;
36
38{
46 [OAuthScopesSupported(true, "McpScopesSupported")]
48 {
49 private static readonly Cache<string, Session> sessions = GetCache();
50
54 public const string ToolsScopeSuffix = ":Tools";
55
59 public const string PromptsScopeSuffix = ":Prompts";
60
64 public const string ResourcesScopeSuffix = ":Resources";
65
66 private static readonly ObjectContent defaultObjectEncoder = new ObjectContent();
67 private static Dictionary<Type, IContentBlock> contentBlocks = GetContentBlocksFirstTime();
68 private const int PageSize = 20;
69 private readonly SortedDictionary<string, Tool> tools = new SortedDictionary<string, Tool>();
70 private readonly SortedDictionary<string, Prompt> prompts = new SortedDictionary<string, Prompt>();
71 private readonly ISnifferSet? snifferSet;
72 private readonly string[] rootScopes;
73 private readonly string[] toolScopes;
74 private readonly string[] promptScopes;
75 private readonly string[] resourceScopes;
76 private readonly string[] scopesSupported;
77 private readonly string title;
78 private readonly string description;
79 private readonly bool hasScopes;
80 private readonly bool hasSnifferSet;
81 private bool hasPrompts;
82 private bool hasTools;
83 private bool requiresAuthentication;
84
85 private static Cache<string, Session> GetCache()
86 {
87 Cache<string, Session> Result = new Cache<string, Session>(int.MaxValue,
88 TimeSpan.MaxValue, TimeSpan.FromHours(1));
89
90 Result.Removed += (sender, e) => e.Value.DisposeAsync();
91
92 return Result;
93 }
94
95 private static Dictionary<Type, IContentBlock> GetContentBlocksFirstTime()
96 {
97 Types.OnInvalidated += (_, e) => contentBlocks = GetContentBlocks();
98 return GetContentBlocks();
99 }
100
101 private static Dictionary<Type, IContentBlock> GetContentBlocks()
102 {
103 Dictionary<Type, IContentBlock> Result = new Dictionary<Type, IContentBlock>();
104 Type[] ContentBlockTypes = Types.GetTypesImplementingInterface(typeof(IContentBlock));
105
106 foreach (Type T in ContentBlockTypes)
107 {
108 if (T.IsAbstract)
109 continue;
110
111 ConstructorInfo? CI = Types.GetDefaultConstructor(T);
112 if (CI is null)
113 continue;
114
115 try
116 {
117 IContentBlock Encoder = (IContentBlock)CI.Invoke(Array.Empty<object>());
118
119 foreach (Type T2 in Encoder.Encodes)
120 Result[T2] = Encoder;
121 }
122 catch (Exception ex)
123 {
124 Log.Exception(ex);
125 }
126 }
127
128 return Result;
129 }
130
131 internal static bool TryGetEncodingContentBlock(Type Type, out IContentBlock ContentBlock)
132 {
133 return contentBlocks.TryGetValue(Type, out ContentBlock);
134 }
135
147 public HttpMcpServerResource(string ResourceName, string Name, string Title,
148 string Version, string Description, Icon[] Icons, Uri? WebSiteUri,
149 string Instructions)
150 : this(ResourceName, Name, Title, Version, Description, Icons, WebSiteUri,
151 Instructions, null)
152 {
153 }
154
168 public HttpMcpServerResource(string ResourceName, string Name, string Title,
169 string Version, string Description, Icon[] Icons, Uri? WebSiteUri,
171 : base(ResourceName, false, false)
172 {
173 this.Name = Name;
174 this.title = Title;
175 this.description = Description;
176 this.Version = Version;
177 this.Icons = new Icons(Icons);
178 this.WebSiteUri = WebSiteUri;
179 this.Instructions = Instructions;
180 this.snifferSet = SnifferSet;
181 this.hasSnifferSet = !(SnifferSet is null);
182
183 if (this.Icons.Empty)
184 {
185 Icon[] DefaultIcons = GetDefaultIcons();
186 if (DefaultIcons.Length > 0)
187 this.Icons = new Icons(DefaultIcons);
188 }
189
190 ChunkedList<string> ScopeRoots = new ChunkedList<string>();
191
192 foreach (McpScopeRootAttribute ScopeRoot in this.GetType().GetCustomAttributes<McpScopeRootAttribute>())
193 ScopeRoots.Add(ScopeRoot.ScopeRoot);
194
195 this.hasScopes = ScopeRoots.Count > 0;
196 this.rootScopes = ScopeRoots.ToArray();
197
198 int i, j, c = this.rootScopes.Length;
199
200 this.toolScopes = new string[c];
201 this.promptScopes = new string[c];
202 this.resourceScopes = new string[c];
203 this.scopesSupported = new string[c * 3];
204 this.requiresAuthentication = this.ResourcesRequireAuthentication;
205
206 for (i = j = 0; i < c; i++)
207 {
208 this.toolScopes[i] = this.scopesSupported[j++] = this.rootScopes[i] + ToolsScopeSuffix;
209 this.promptScopes[i] = this.scopesSupported[j++] = this.rootScopes[i] + PromptsScopeSuffix;
210 this.resourceScopes[i] = this.scopesSupported[j++] = this.rootScopes[i] + ResourcesScopeSuffix;
211 }
212
213 foreach (MethodInfo Method in this.GetType().GetMethods(BindingFlags.Instance |
214 BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
215 {
216 if (Method.GetCustomAttribute<McpServerToolAttribute>() is
218 {
219 this.RegisterToolNoNotification(Method, McpServerToolAttribute);
220 }
221
222 if (Method.GetCustomAttribute<McpServerPromptAttribute>() is
224 {
225 this.RegisterPromptNoNotification(Method, McpServerPromptAttribute);
226 }
227 }
228 }
229
233 public override bool SupportsServerSentEvents => true;
234
239 public override bool SendSseWelcomeMessage => false;
240
244 public string Name { get; }
245
249 public override string Title => this.title;
250
254 public string Version { get; }
255
259 public Icons Icons { get; }
260
264 public Uri? WebSiteUri { get; }
265
269 public string Instructions { get; }
270
274 public bool AllowsDELETE => true;
275
280 public string[] McpScopesSupported()
281 {
282 return this.scopesSupported;
283 }
284
288 public override bool HandlesSubPaths => true;
289
296 public override async Task GET(HttpRequest Request, HttpResponse Response)
297 {
298 if (string.IsNullOrEmpty(Request.SubPath))
299 {
300 await base.GET(Request, Response);
301 return;
302 }
303
304 string FormId = Request.SubPath[1..];
305
306 if (FormId == "UserInput.js")
307 {
308 StringBuilder Javascript = new StringBuilder();
309
310 Javascript.AppendLine("function Ok() { PostForm('true'); }");
311 Javascript.AppendLine("function Cancel() { PostForm('false'); }");
312 Javascript.AppendLine("function PostForm(Response)");
313 Javascript.AppendLine("{");
314 Javascript.AppendLine("\tdocument.getElementById('_r_').value=Response;");
315 Javascript.AppendLine("\tdocument.getElementById('InputForm').submit();");
316 Javascript.AppendLine("}");
317 Javascript.AppendLine("function Loaded()");
318 Javascript.AppendLine("{");
319 Javascript.AppendLine("\tdocument.getElementById('OkButton').addEventListener('click', Ok);");
320 Javascript.AppendLine("\tdocument.getElementById('CancelButton').addEventListener('click', Cancel);");
321 Javascript.AppendLine("}");
322 Javascript.AppendLine("window.addEventListener('load', Loaded);");
323
324 Response.SetHeader("Cache-Control", "max-age=0, no-cache, no-store");
325 Response.SetHeader("Pragma", "no-cache");
326
327 await Response.Return(new JavaScriptDocument(Javascript.ToString()));
328 }
329 else if (FormId == "CloseInput.js")
330 {
331 StringBuilder Javascript = new StringBuilder();
332
333 Javascript.AppendLine("window.close();");
334
335 Response.SetHeader("Cache-Control", "max-age=0, no-cache, no-store");
336 Response.SetHeader("Pragma", "no-cache");
337
338 await Response.Return(new JavaScriptDocument(Javascript.ToString()));
339 }
340 else
341 {
342 if (!this.TryGetRequest(FormId, out IJsonRpcClientRequest? ClientRequest) ||
343 ClientRequest.Tag is null)
344 {
345 await Response.Return(await CloseForm(Response));
346 }
347 else
348 {
349 await Response.Return(await this.GenerateInputForm(Request, Response,
350 ClientRequest));
351 }
352 }
353 }
354
355 private async Task<HtmlDocument> GenerateInputForm(HttpRequest Request,
356 HttpResponse Response, IJsonRpcClientRequest ClientRequest)
357 {
358 StringBuilder Markdown = new StringBuilder();
359
360 Markdown.AppendLine("Title: User Input");
361 Markdown.AppendLine("Description: Form allowing a user to input elicited information.");
362 Markdown.AppendLine("Javascript: UserInput.js");
363
364 if (Types.TryGetModuleParameter<OAuth2Environment>("OAUTH2", out OAuth2Environment? Environment) &&
365 Environment.HasLoginMasterFileName)
366 {
367 Markdown.Append("Master: ");
368 Markdown.AppendLine(Environment.LoginMasterFileName);
369 }
370
371 Markdown.Append("Date: ");
372 Markdown.AppendLine(CommonTypes.EncodeRfc822(DateTime.UtcNow));
373 Markdown.AppendLine();
374 Markdown.AppendLine(new string('=', 40));
375 Markdown.AppendLine();
376
377 Markdown.AppendLine("Requested Information");
378 Markdown.AppendLine("========================");
379 Markdown.AppendLine();
380
381 Markdown.AppendLine(MarkdownDocument.Encode(ClientRequest.Message));
382 Markdown.AppendLine();
383
384 string ParametersToken = this.JwtFactory?.Create(
385 new KeyValuePair<string, object>(JwtClaims.JwtId, ClientRequest.Id?.ToString() ?? string.Empty),
386 new KeyValuePair<string, object>(JwtClaims.Subject, Request.RemoteEndPoint.RemovePortNumber()))
387 ?? string.Empty;
388
389 Markdown.Append("<form id='InputForm' action='");
390 Markdown.Append(Request.Header.GetURL(false, false));
391 Markdown.AppendLine("' method='post' enctype='multipart/form-data'>");
392 Markdown.Append("<input type='hidden' name='_p_' value='");
393 Markdown.Append(XML.HtmlAttributeEncode(ParametersToken));
394 Markdown.AppendLine("'/>");
395 Markdown.AppendLine("<input type='hidden' id='_r_' name='_r_' value=''/>");
396 Markdown.AppendLine();
397
398 Type T = ClientRequest.Tag!.GetType();
399 McpParameterAttribute? ParameterInfo;
400 Dictionary<string, string> InputAttributes = new Dictionary<string, string>();
401 object Value;
402 bool Password;
403 StringBuilder Label = new StringBuilder();
404 StringBuilder Input = new StringBuilder();
405 bool LabelFirst;
406
407 foreach (MemberInfo MI in T.GetMembers(BindingFlags.Instance | BindingFlags.Public))
408 {
409 if (MI is FieldInfo FI)
410 Value = FI.GetValue(ClientRequest.Tag);
411 else if (MI is PropertyInfo PI)
412 Value = PI.GetValue(ClientRequest.Tag);
413 else
414 continue;
415
416 ParameterInfo = MI.GetCustomAttribute<McpParameterAttribute>(true);
417 Password = ParameterInfo is McpPasswordParameterAttribute;
418 InputAttributes.Clear();
419 LabelFirst = true;
420
421 if (MI.Name == "_p_" ||
422 MI.Name == "_r_" ||
423 MI.Name.EndsWith("_Binary") ||
424 MI.Name.EndsWith("_ContentType"))
425 {
426 throw new Exception("Reserved name: " + MI.Name);
427 }
428
429 InputAttributes["id"] = MI.Name;
430 InputAttributes["name"] = MI.Name;
431 InputAttributes["value"] = ParameterInfo?.GetHtmlAttributeValue(Value)
432 ?? Value?.ToString() ?? string.Empty;
433
434 if (Value is double || Value is float || Value is decimal ||
435 Value is int || Value is long || Value is short || Value is sbyte ||
436 Value is uint || Value is ulong || Value is ushort || Value is byte)
437 {
438 InputAttributes["type"] = "number";
439 InputAttributes["step"] = "any";
440 }
441 else if (Value is bool)
442 {
443 InputAttributes.Remove("value");
444 InputAttributes["type"] = "checkbox";
445
446 if (Value is bool b && b)
447 InputAttributes["checked"] = "checked";
448
449 LabelFirst = false;
450 }
451 else if (Value is string s)
452 InputAttributes["type"] = "text";
453 else if (Value is TimeSpan)
454 InputAttributes["type"] = "time";
455 else if (Value is DateTime)
456 InputAttributes["type"] = "datetime-local";
457 else if (Value is Uri)
458 InputAttributes["type"] = "url";
459 else if (Value is SKColor)
460 InputAttributes["type"] = "color";
461 else
462 InputAttributes["type"] = "text";
463
464 ParameterInfo?.GetHtmlInputAttributes(InputAttributes);
465
466 Label.Clear();
467 Input.Clear();
468
469 Label.Append("<label for=\"");
470 Label.Append(MI.Name);
471 Label.Append("\">");
472 Label.Append(ParameterInfo?.Title ?? MI.Name);
473 Label.Append("</label>");
474
475 if (Value is Enum EnumValue)
476 {
477 Type EnumType = EnumValue.GetType();
478 IEnumerable<McpEnumValueAttribute> Options = MI.GetCustomAttributes<McpEnumValueAttribute>(true);
479
480 if (EnumType.IsDefined(typeof(FlagsAttribute)))
481 throw new NotImplementedException("Flagged enumerations are not yet supported in input forms."); // TODO:
482 else
483 {
484 InputAttributes.Remove("value");
485 Input.Append("<select");
486
487 foreach (KeyValuePair<string, string> P in InputAttributes)
488 {
489 Input.Append(' ');
490 Input.Append(P.Key);
491 Input.Append("=\"");
492 Input.Append(XML.HtmlAttributeEncode(P.Value));
493 Input.Append('"');
494 }
495
496 Input.AppendLine(">");
497
498 foreach (McpEnumValueAttribute Option in Options)
499 {
500 Input.Append("<option value=\"");
502
503 if (Option.Value.Equals(EnumValue))
504 Input.Append("\" selected=\"selected");
505
506 Input.Append("\">");
507 Input.Append(XML.HtmlValueEncode(Option.Title));
508 Input.AppendLine("</option>");
509 }
510
511 Input.Append("</select>");
512 }
513 }
514 else if (Value is string[] ||
515 (Value is string s && s.IndexOfAny(CommonTypes.CRLF) >= 0))
516 {
517 InputAttributes.Remove("value");
518 Input.Append("<textarea");
519
520 foreach (KeyValuePair<string, string> P in InputAttributes)
521 {
522 Input.Append(' ');
523 Input.Append(P.Key);
524 Input.Append("=\"");
525 Input.Append(XML.HtmlAttributeEncode(P.Value));
526 Input.Append('"');
527 }
528
529 Input.Append(">");
530
531 if (Value is string[] Rows)
532 {
533 bool First = true;
534
535 foreach (string Row in Rows)
536 {
537 if (First)
538 First = false;
539 else
540 Input.AppendLine();
541
542 Input.Append(Row);
543 }
544 }
545 else
546 Input.Append(Value.ToString());
547
548 Input.AppendLine("</textarea>");
549 }
550 else
551 {
552 Input.Append("<input");
553
554 foreach (KeyValuePair<string, string> P in InputAttributes)
555 {
556 Input.Append(' ');
557 Input.Append(P.Key);
558 Input.Append("=\"");
559 Input.Append(XML.HtmlAttributeEncode(P.Value));
560 Input.Append('"');
561 }
562
563 Input.Append("/>");
564 }
565
566 Markdown.Append("<p>");
567
568 if (LabelFirst)
569 {
570 Markdown.Append(Label.ToString());
571 Markdown.AppendLine(" ");
572 Markdown.AppendLine(Input.ToString());
573 }
574 else
575 {
576 Markdown.AppendLine(Input.ToString());
577 Markdown.AppendLine(Label.ToString());
578 }
579
580 Markdown.AppendLine("</p>");
581 Markdown.AppendLine();
582 }
583
584 Markdown.AppendLine("<button id='OkButton' type='button'>OK</button>");
585 Markdown.AppendLine("<button id='CancelButton' type='button'>Cancel</button>");
586 Markdown.AppendLine("</form>");
587 Markdown.AppendLine();
588
589 return await ReturnHtml(Response, Markdown.ToString());
590 }
591
592 private static async Task<HtmlDocument> CloseForm(HttpResponse Response)
593 {
594 StringBuilder Markdown = new StringBuilder();
595
596 Markdown.AppendLine("Title: User Input");
597 Markdown.AppendLine("Description: Form allowing a user to input elicited information.");
598 Markdown.AppendLine("Javascript: CloseInput.js");
599
600 if (Types.TryGetModuleParameter<OAuth2Environment>("OAUTH2", out OAuth2Environment? Environment) &&
601 Environment.HasLoginMasterFileName)
602 {
603 Markdown.Append("Master: ");
604 Markdown.AppendLine(Environment.LoginMasterFileName);
605 }
606
607 Markdown.Append("Date: ");
608 Markdown.AppendLine(CommonTypes.EncodeRfc822(DateTime.UtcNow));
609 Markdown.AppendLine();
610 Markdown.AppendLine(new string('=', 40));
611 Markdown.AppendLine();
612
613 Markdown.AppendLine("Close Form");
614 Markdown.AppendLine("=============");
615 Markdown.AppendLine();
616
617 Markdown.Append("You can now safely close the form, if it does not close ");
618 Markdown.AppendLine("automatically by itself.");
619
620 return await ReturnHtml(Response, Markdown.ToString());
621 }
622
623 private static async Task<HtmlDocument> ReturnHtml(HttpResponse Response, string Markdown)
624 {
625 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Markdown,
626 new MarkdownSettings()
627 {
628 Variables = new Variables()
629 });
630
631 string Html = await Doc.GenerateHTML();
632
633 Response.SetHeader("Cache-Control", "max-age=0, no-cache, no-store");
634 Response.SetHeader("Pragma", "no-cache");
635 Response.SetHeader("X-Frame-Options", "DENY");
636 Response.SetHeader("Content-Security-Policy", "frame-ancestors 'none'; " +
637 "default-src 'self'; script-src 'self'; object-src 'none'; " +
638 "base-uri 'none'; form-action 'self'");
639
640 return new HtmlDocument(Html);
641 }
642
649 public override async Task POST(HttpRequest Request, HttpResponse Response)
650 {
651 if (string.IsNullOrEmpty(Request.SubPath))
652 {
653 await base.POST(Request, Response);
654 return;
655 }
656
657 if (!Request.HasData)
658 {
659 await Response.SendResponse(new BadRequestException("Missing data."));
660 return;
661 }
662
663 string FormId = Request.SubPath[1..];
664 if (!this.TryGetRequest(FormId, out IJsonRpcClientRequest? ClientRequest))
665 {
666 await Response.SendResponse(new NotFoundException("Request not found: " + FormId));
667 return;
668 }
669
670 ContentResponse Content = await Request.DecodeDataAsync();
671 if (Content.HasError)
672 {
673 await Response.SendResponse(Content.Error);
674 return;
675 }
676
677 if (!(Content.Decoded is Dictionary<string, object> Form))
678 {
679 await Response.SendResponse(new BadRequestException("Expected form data."));
680 return;
681 }
682
683 if (!Form.TryGetValue("_p_", out object Obj) ||
684 !(Obj is string ParametersToken) ||
685 !JwtToken.TryParse(ParametersToken, out JwtToken ParsedToken) ||
686 !this.JwtFactory!.IsValid(ParsedToken) ||
687 ParsedToken.Id != ClientRequest.Id?.ToString() ||
688 ParsedToken.Subject != Request.RemoteEndPoint.RemovePortNumber())
689 {
690 await Response.SendResponse(new BadRequestException("Invalid parameters token."));
691 return;
692 }
693
694 if (!Form.TryGetValue("_r_", out Obj) ||
695 !(Obj is string ResponseString) ||
696 !CommonTypes.TryParse(ResponseString, out bool ResponseValue))
697 {
698 await Response.SendResponse(new BadRequestException("Invalid response."));
699 return;
700 }
701
702 string[] Keys = new string[Form.Count];
703 Form.Keys.CopyTo(Keys, 0);
704
705 foreach (string Key in Keys)
706 {
707 Form.Remove(Key + "_Binary");
708 Form.Remove(Key + "_ContentType");
709 }
710
711 Form.Remove("_p_");
712 Form.Remove("_r_");
713
714 if (ResponseValue)
715 await SetProperties(ClientRequest.Tag!, Form);
716
717 try
718 {
719 await Response.Return(await CloseForm(Response));
720 }
721 catch (Exception ex)
722 {
723 Log.Exception(ex);
724 }
725 finally
726 {
727 await ClientRequest.ReportResult(ResponseValue);
728 }
729 }
730
736 private void RegisterToolNoNotification(MethodInfo Method, McpServerToolAttribute Attributes)
737 {
738 lock (this.tools)
739 {
740 string Name = Method.Name;
741
742 if (this.tools.ContainsKey(Name))
743 throw new Exception("Tool already registered: " + Name);
744
745 Tool Tool = new Tool(Method, Attributes.Title,
746 Attributes.Description, Attributes.IconsMethod,
747 Attributes.CanModifyEnvironment, Attributes.CanDestroyEnvironment,
748 Attributes.Idempotent, Attributes.OpenWorldAccess);
749
750 this.tools[Name] = Tool;
751
752 this.requiresAuthentication |= Tool.RequiresAuthentication;
753 this.hasTools = true;
754 }
755 }
756
762 public Task RegisterTool(MethodInfo Method, McpServerToolAttribute Attributes)
763 {
764 this.RegisterToolNoNotification(Method, Attributes);
765
766 Dictionary<string, object> Notification = new Dictionary<string, object>()
767 {
768 { "jsonrpc", "2.0" },
769 { "method", "notifications/tools/list_changed" }
770 };
771
772 return this.SendNotification(
773 Session =>
774 {
775 if (!(Session is Session McpSession))
776 return false;
777
778 if (this.hasSnifferSet)
779 McpSession.TransmitText(JSON.Encode(Notification, false));
780
781 return true;
782 },
783 Notification);
784 }
785
791 private void RegisterPromptNoNotification(MethodInfo Method, McpServerPromptAttribute Attributes)
792 {
793 lock (this.prompts)
794 {
795 string Name = Method.Name;
796
797 if (this.prompts.ContainsKey(Name))
798 throw new Exception("Prompt already registered: " + Name);
799
800 Prompt Prompt = new Prompt(Method, Attributes.Title,
801 Attributes.Description, Attributes.IconsMethod);
802
803 this.prompts[Name] = Prompt;
804
805 this.requiresAuthentication |= Prompt.RequiresAuthentication;
806 this.hasPrompts = true;
807 }
808 }
809
815 public Task RegisterPrompt(MethodInfo Method, McpServerPromptAttribute Attributes)
816 {
817 this.RegisterPromptNoNotification(Method, Attributes);
818
819 Dictionary<string, object> Notification = new Dictionary<string, object>()
820 {
821 { "jsonrpc", "2.0" },
822 { "method", "notifications/prompts/list_changed" }
823 };
824
825 return this.SendNotification(
826 Session =>
827 {
828 if (!(Session is Session McpSession))
829 return false;
830
831 if (this.hasSnifferSet)
832 McpSession.TransmitText(JSON.Encode(Notification, false));
833
834 return true;
835 },
836 Notification);
837 }
838
843 public static Icon[] GetDefaultIcons()
844 {
845 if (Types.TryGetModuleParameter("FavIcon", out string Url))
846 {
847 return new Icon[]
848 {
849 new Icon(new Uri(Url), ImageCodec.ContentTypeIcon, null, null)
850 };
851 }
852 else
853 return Array.Empty<Icon>();
854 }
855
859 public override string ShortDescription
860 {
861 get
862 {
863 OAuthResourceNameAttribute? Attribute = this.GetType().GetCustomAttribute<OAuthResourceNameAttribute>();
864 return Attribute?.ResourceName ?? "MCP Server: " + this.Name;
865 }
866 }
867
871 public override string MarkdownDescription => MarkdownDocument.Encode(this.description);
872
877 public override void AddReference(HttpServer Server)
878 {
879 base.AddReference(Server);
880
881 Tool[] Tools;
882 Prompt[] Prompts;
883 int c, d;
884
885 lock (this.tools)
886 {
887 c = this.tools.Count;
888 Tools = new Tool[c];
889 this.tools.Values.CopyTo(Tools, 0);
890 }
891
892 lock (this.prompts)
893 {
894 d = this.prompts.Count;
895 Prompts = new Prompt[d];
896 this.prompts.Values.CopyTo(Prompts, 0);
897 }
898
899 ProtectedMethod[] Methods = new ProtectedMethod[c + d];
900 Array.Copy(Tools, 0, Methods, 0, c);
901 Array.Copy(Prompts, 0, Methods, c, d);
902
903 foreach (ProtectedMethod Method in Methods)
904 this.AddAuthenticationMechanisms(Method);
905 }
906
917 [JsonRpcMethod]
918 [JsonRpcDocumentation("MCP `initialize` method. Called by client to initialize " +
919 "connection and exchange information about capabilities.", true)]
920 [JsonRpcDocName("initialize")]
921 [return: JsonRpcDocumentation("Server capabilities and information.")]
922 protected Dictionary<string, object> Initialize(
923 HttpRequest Request, HttpResponse Response,
924
925 [JsonRpcDocumentation("Protocol Version")]
926 string ProtocolVersion,
927
928 [JsonRpcDocumentation("Client capabilities")]
929 Dictionary<string, object> Capabilities,
930
931 [JsonRpcDocumentation("Client information")]
932 Dictionary<string, object> ClientInfo)
933 {
934 if (!ClientCapabilities.TryParse(Capabilities, out ClientCapabilities? CapabilitiesParsed))
935 CapabilitiesParsed = null;
936
937 if (!Implementation.TryParse(ClientInfo, out Implementation? ClientInfoParsed))
938 ClientInfoParsed = null;
939
940 string RemoteEndpoint = Request.RemoteEndPoint.RemovePortNumber();
941 string SessionId;
942
943 do
944 {
946
947 if (this.HasJwtFactory)
948 {
949 SessionId = this.JwtFactory!.Create(
950 new KeyValuePair<string, object>(JwtClaims.JwtId, SessionId),
951 new KeyValuePair<string, object>(JwtClaims.ClientId, RemoteEndpoint));
952 }
953 }
954 while (sessions.ContainsKey(SessionId));
955
956 Session Session = new Session(SessionId, ProtocolVersion,
957 CapabilitiesParsed, ClientInfoParsed, RemoteEndpoint, this.snifferSet);
958
959 sessions[SessionId] = Session;
960 Response.SetHeader("MCP-Session-Id", SessionId);
961
962 if (this.hasSnifferSet)
963 {
964 StringBuilder sb = new StringBuilder();
965
966 sb.Append(this.Name);
967 sb.Append(".Initialize(");
968 sb.Append(ProtocolVersion);
969 sb.Append(',');
970 sb.Append(JSON.Encode(Capabilities, false));
971 sb.Append(',');
972 sb.Append(JSON.Encode(ClientInfo, false));
973 sb.Append(')');
974
975 Session.ReceiveText(sb.ToString());
976 }
977
978 Dictionary<string, object> ServerCapabilities = new Dictionary<string, object>();
979
980 // TODO:
981 //{
982 // { "logging", new Dictionary<string, object>() },
983 // { "completions", new Dictionary<string, object>() },
984 // { "tasks", new Dictionary<string, object>()
985 // {
986 // { "list", new Dictionary<string, object>() },
987 // { "cancel", new Dictionary<string, object>() },
988 // { "requests", new Dictionary<string, object>()
989 // {
990 // { "tools", new Dictionary<string, object>()
991 // {
992 // { "call", new Dictionary<string, object>() }
993 // }
994 // }
995 // }
996 // }
997 // }
998 // },
999 // { "experimental", new Dictionary<string, object>() }
1000 //};
1001
1002 if (this.hasPrompts)
1003 {
1004 ServerCapabilities["prompts"] = new Dictionary<string, object>()
1005 {
1006 { "listChanged", true }
1007 };
1008 }
1009
1010 if (this.hasTools)
1011 {
1012 ServerCapabilities["tools"] = new Dictionary<string, object>()
1013 {
1014 { "listChanged", true }
1015 };
1016 }
1017
1018 if (this.HasResources)
1019 {
1020 ServerCapabilities["resources"] = new Dictionary<string, object>()
1021 {
1022 { "subscribe", true },
1023 { "listChanged", true }
1024 };
1025 }
1026
1027 string? WebSite = this.WebSiteUri?.ToString();
1028 if (string.IsNullOrEmpty(WebSite))
1029 WebSite = Request.Header.GetURL(false, false);
1030
1031 Dictionary<string, object> Result = new Dictionary<string, object>()
1032 {
1033 { "protocolVersion", "2025-11-25" },
1034 { "capabilities", ServerCapabilities },
1035 { "serverInfo", new Dictionary<string,object>()
1036 {
1037 { "name", this.Name },
1038 { "title", this.title },
1039 { "version", this.Version },
1040 { "description", this.description },
1041 { "icons", this.Icons.ToJson() },
1042 { "websiteUrl", WebSite }
1043 }
1044 },
1045 { "instructions", this.Instructions }
1046 };
1047
1048 if (this.hasSnifferSet)
1049 Session.TransmitText(JSON.Encode(Result, false));
1050
1051 return Result;
1052 }
1053
1059 [JsonRpcMethod]
1060 [JsonRpcDocumentation("Notification that the client has completed its " +
1061 "initialization.")]
1062 [JsonRpcDocName("notifications/initialized")]
1063 protected async Task Notifications_Initialized(
1064 HttpRequest Request, HttpResponse Response)
1065 {
1066 Session? Session = await this.TryGetMcpSession(Request, Response);
1067 if (Session is null)
1068 return;
1069
1070 if (this.hasSnifferSet)
1071 Session.ReceiveText(this.Name + ".Initialized()");
1072
1073 Log.Informational("MCP client initialized: " + Request.RemoteEndPoint,
1074 this.ResourceName, Request.RemoteEndPoint, "McpInitialized");
1075
1076 Response.StatusCode = 202;
1077 Response.StatusMessage = "Accepted";
1078
1079 await Response.SendResponse();
1080 }
1081
1088 protected async Task<Session?> TryGetMcpSession(HttpRequest Request, HttpResponse Response)
1089 {
1090 return await this.TryGetSession(Request, Response) as Session;
1091 }
1092
1099 protected override async Task<IJsonRpcSession?> TryGetSession(HttpRequest Request, HttpResponse Response)
1100 {
1101 if (!Request.Header.TryGetHeaderField("MCP-Session-Id", out HttpField SessionHeader))
1102 {
1103 await Response.SendResponse(new BadRequestException("Missing MCP-Session-Id header."));
1104 return null;
1105 }
1106
1107 string SessionId = SessionHeader.Value;
1108
1109 if (this.HasJwtFactory)
1110 {
1111 if (!JwtToken.TryParse(SessionId, out JwtToken Token))
1112 {
1113 await Response.SendResponse(new NotFoundException("Invalid MCP-Session-Id."));
1114 return null;
1115 }
1116
1117 if (!this.JwtFactory!.IsValid(Token))
1118 {
1119 await Response.SendResponse(new NotFoundException("MCP-Session-Id invalid or expired."));
1120 return null;
1121 }
1122 }
1123
1124 if (!sessions.TryGetValue(SessionId, out Session? Session))
1125 {
1126 await Response.SendResponse(new NotFoundException("MCP-Session-Id expired or not found."));
1127 return null;
1128 }
1129
1130 if (Session.RemoteEndpoint != Request.RemoteEndPoint.RemovePortNumber())
1131 {
1132 await Response.SendResponse(new NotFoundException("MCP-Session-Id not found for this endpoint."));
1133 return null;
1134 }
1135
1136 return Session;
1137 }
1138
1146 [JsonRpcMethod]
1147 [JsonRpcDocumentation("Lists available MCP server tools.")]
1148 [JsonRpcDocName("tools/list")]
1149 [return: JsonRpcDocumentation("Dictionary containing the list of tools.")]
1150 protected async Task<Dictionary<string, object>?> Tools_List(
1151 HttpRequest Request, HttpResponse Response,
1152
1153 [JsonRpcDocumentation("Cursor for pagination.")]
1154 string? Cursor = null)
1155 {
1156 Session? Session = await this.TryGetMcpSession(Request, Response);
1157 if (Session is null)
1158 return null;
1159
1160 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1161 if (Response.ResponseSent)
1162 return null;
1163
1164 if (this.hasSnifferSet)
1165 {
1166 StringBuilder sb = new StringBuilder();
1167
1168 sb.Append(this.Name);
1169 sb.Append(".tools/list(");
1170 sb.Append(Cursor);
1171 sb.Append(')');
1172
1173 Session.ReceiveText(sb.ToString());
1174 }
1175
1176 int Offset = 0;
1177 int MaxCount = PageSize;
1178
1179 if (!string.IsNullOrEmpty(Cursor))
1180 {
1181 if (!int.TryParse(Cursor, out Offset) || Offset < 0)
1182 {
1183 if (!this.hasSnifferSet)
1184 Session.Error("Invalid cursor: " + Cursor);
1185
1186 await Response.SendResponse(new BadRequestException("Invalid cursor."));
1187 return null;
1188 }
1189 }
1190
1192 int Next = Offset + MaxCount;
1193
1194 Dictionary<string, object> Result = new Dictionary<string, object>();
1195
1196 lock (this.tools)
1197 {
1198 foreach (Tool Tool in this.tools.Values)
1199 {
1200 if (!Tool.IsAuthorized(User))
1201 continue;
1202
1203 if (!this.CheckScopes(User, this.toolScopes, out _))
1204 continue;
1205
1206 if (MaxCount <= 0)
1207 {
1208 Result["nextCursor"] = Next.ToString();
1209 break;
1210 }
1211
1212 if (Offset > 0)
1213 {
1214 Offset--;
1215 continue;
1216 }
1217
1218 Tools.Add(Tool);
1219 MaxCount--;
1220 }
1221 }
1222
1223 int i = 0;
1224 int c = Tools.Count;
1225
1226 Dictionary<string, object>[] ToolsJson = new Dictionary<string, object>[c];
1227
1228 foreach (Tool Tool in Tools)
1229 ToolsJson[i++] = await Tool.ToJson(this);
1230
1231 Result["tools"] = ToolsJson;
1232
1233 if (this.hasSnifferSet)
1234 Session.TransmitText(JSON.Encode(Result, false));
1235
1236 return Result;
1237 }
1238
1239 private bool CheckScopes(IUser? User, string[] Scopes, out string? MissingPrivilege)
1240 {
1241 if (!this.hasScopes)
1242 {
1243 MissingPrivilege = null;
1244 return true;
1245 }
1246
1247 if (User is null)
1248 {
1249 MissingPrivilege = null;
1250 return false;
1251 }
1252
1253 return OAuthResource.HasScopePrivileges(Scopes, User, out MissingPrivilege);
1254 }
1255
1256 private async Task<IUser?> GetAuthenticatedUser(HttpRequest Request,
1257 HttpResponse Response, Session Session)
1258 {
1259 IUser User = Request.User;
1260 bool Encrypted = Request.Encrypted;
1261 int Strength = Request.CipherStrength;
1262
1263 if ((this.requiresAuthentication || !(Request.Header.Authorization is null)) &&
1264 User is null)
1265 {
1266 if (this.AuthenticationSchemes is null)
1267 {
1268 await Response.SendResponse(new ForbiddenException());
1269
1270 if (this.hasSnifferSet)
1271 Session.Error("Access denied. No authentication schemes available.");
1272
1273 return null;
1274 }
1275
1276 foreach (HttpAuthenticationScheme Scheme in this.AuthenticationSchemes)
1277 {
1278 if (Scheme.RequireEncryption &&
1279 (!Encrypted || Strength < Scheme.MinStrength))
1280 {
1281 continue;
1282 }
1283
1284 if (Scheme.UserSessions && Request.Session is null)
1285 Request.GetSessionFromCookie();
1286
1287 User = await Scheme.IsAuthenticated(Request);
1288 if (!(User is null))
1289 {
1290 Request.User = User;
1291 break;
1292 }
1293 }
1294
1295 if (User is null)
1296 {
1297 List<string> Challenges = new List<string>();
1298
1299 foreach (HttpAuthenticationScheme Scheme in this.AuthenticationSchemes
1300 ?? Array.Empty<HttpAuthenticationScheme>())
1301 {
1302 if (Scheme.RequireEncryption &&
1303 (!Encrypted || Strength < Scheme.MinStrength))
1304 {
1305 continue;
1306 }
1307
1308 foreach (string Challenge in Scheme.GetChallenges(Request))
1309 Challenges.Add(Challenge);
1310 }
1311
1312 await Response.SendResponse(new UnauthorizedException(
1313 Challenges.ToArray()));
1314
1315 if (this.hasSnifferSet)
1316 Session.Error("Access denied. Unauthorized.");
1317
1318 return null;
1319 }
1320 }
1321
1322 if (!Session.IsAuthenticated && !(User is null))
1323 await Session.SetUserName(User.UserName);
1324
1325 return User;
1326 }
1327
1344 [JsonRpcMethod]
1345 [JsonRpcDocumentation("Calls an MCP server tool.")]
1346 [JsonRpcDocName("tools/call")]
1347 [return: JsonRpcDocumentation("Dictionary containing the result of the tool call.")]
1348 protected async Task<Dictionary<string, object?>?> Tools_Call(
1349 [JsonRpcId] object? Id, HttpRequest Request, HttpResponse Response,
1350
1351 [JsonRpcDocumentation("Name of the tool to call.")]
1352 string Name,
1353
1354 [JsonRpcDocumentation("Arguments for the tool.")]
1355 Dictionary<string, object?> Arguments,
1356
1357 [JsonRpcDocumentation("If specified, the caller is requesting task-augmented " +
1358 "execution for this request. The request will return a `CreateTaskResult` " +
1359 "immediately, and the actual result can be retrieved later via tasks/result.\r\n\r\n" +
1360 "Task augmentation is subject to capability negotiation - receivers MUST declare " +
1361 "support for task augmentation of specific request types in their capabilities.", true)]
1362 object? Task = null,
1363
1364 [JsonRpcMetaDataArgument]
1365 [JsonRpcDocumentation("Associated meta-data, if available.")]
1366 object? _Meta = null)
1367 {
1368 Session? Session = await this.TryGetMcpSession(Request, Response);
1369 if (Session is null)
1370 return null;
1371
1372 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1373 if (Response.ResponseSent)
1374 return null;
1375
1376 if (this.hasSnifferSet)
1377 {
1378 StringBuilder sb = new StringBuilder();
1379
1380 sb.Append(this.Name);
1381 sb.Append(".tools/call(");
1382 sb.Append(Name);
1383 sb.Append(',');
1384 sb.Append(JSON.Encode(Arguments, false));
1385
1386 if (!(Task is null))
1387 {
1388 sb.Append(',');
1389 JSON.Encode(Task, false, sb);
1390 }
1391
1392 if (!(_Meta is null))
1393 {
1394 sb.Append(',');
1395 JSON.Encode(_Meta, false, sb);
1396 }
1397
1398 sb.Append(')');
1399
1400 Session.ReceiveText(sb.ToString());
1401 }
1402
1403 Dictionary<string, object?> Result = new Dictionary<string, object?>();
1404 object? ToolResult;
1405
1406 try
1407 {
1408 if (!this.tools.TryGetValue(Name, out Tool? Tool))
1409 {
1410 if (this.hasSnifferSet)
1411 Session.Error("Tool not found: " + Name);
1412
1413 await Response.SendResponse(new NotFoundException("Tool not found."));
1414 return null;
1415 }
1416
1417 if (!Tool.IsAuthorized(User, out string? MissingPrivilege) ||
1418 !this.CheckScopes(User, this.toolScopes, out MissingPrivilege))
1419 {
1420 if (this.hasSnifferSet)
1421 Session.Error("Access denied. Missing privilege: " + MissingPrivilege);
1422
1423 await Response.SendResponse(ForbiddenException.AccessDenied(this.ResourceName,
1424 User?.UserName ?? string.Empty, MissingPrivilege ?? string.Empty));
1425 return null;
1426 }
1427
1428 await RuntimeCounters.IncrementCounter("MCP.Tool." + Name);
1429 await RuntimeCounters.IncrementCounter("MCP.User.Tool." + Session.UserName);
1430
1431 Dictionary<string, object?>? MetaData = _Meta as Dictionary<string, object?>;
1432
1433 if (Tool.TryBuildRequest(Id, Arguments, Request, Response, MetaData,
1434 out string? Reason, out object?[]? Arguments2))
1435 {
1436 ToolResult = await ScriptNode.WaitPossibleTask(
1437 Tool.Method.Invoke(this, Arguments2));
1438 }
1439 else
1440 {
1441 if (this.hasSnifferSet)
1442 Session.Error(Reason);
1443
1444 ToolResult = Reason;
1445 Result["isError"] = true;
1446 }
1447 }
1448 catch (Exception ex)
1449 {
1450 if (this.hasSnifferSet)
1451 Session.Exception(ex);
1452
1453 ToolResult = Log.UnnestException(ex).Message;
1454 Result["isError"] = true;
1455 }
1456
1457 if (ToolResult is null)
1458 Result["content"] = Array.Empty<object>();
1459 else if (ToolResult is Dictionary<string, object> StructuredContent)
1460 {
1461 Result["content"] = new object[]
1462 {
1463 new Dictionary<string, object>()
1464 {
1465 { "type", "text" },
1466 { "text", JSON.Encode(StructuredContent, false) }
1467 }
1468 };
1469 Result["structuredContent"] = new Dictionary<string, object>()
1470 {
1471 { "result", StructuredContent }
1472 };
1473 }
1474 else
1475 {
1476 Type T = ToolResult.GetType();
1477
1478 if (contentBlocks.TryGetValue(T, out IContentBlock Encoder))
1479 {
1480 if (Encoder.IsStructuredContent)
1481 {
1482 StructuredContent = await Encoder.Encode(ToolResult);
1483
1484 Result["content"] = new object[]
1485 {
1486 new Dictionary<string, object>()
1487 {
1488 { "type", "text" },
1489 { "text", JSON.Encode(StructuredContent, false) }
1490 }
1491 };
1492 Result["structuredContent"] = new Dictionary<string, object>()
1493 {
1494 { "result", StructuredContent }
1495 };
1496 }
1497 else
1498 Result["content"] = new object[] { await Encoder.Encode(ToolResult) };
1499 }
1500 else if (T.IsArray && ToolResult is IEnumerable Enumerable)
1501 {
1503 IEnumerator e = Enumerable.GetEnumerator();
1504
1505 while (e.MoveNext())
1506 {
1507 object? Item = e.Current;
1508 if (Item is null)
1509 continue;
1510
1511 Type T2 = Item.GetType();
1512 if (!contentBlocks.TryGetValue(T2, out IContentBlock Encoder2))
1513 Encoder2 = defaultObjectEncoder;
1514
1515 Content.Add(await Encoder2.Encode(Item));
1516 }
1517
1518 Result["content"] = Content.ToArray();
1519 }
1520 else
1521 {
1522 StructuredContent = await defaultObjectEncoder.Encode(ToolResult);
1523
1524 Result["content"] = new object[]
1525 {
1526 new Dictionary<string, object>()
1527 {
1528 { "type", "text" },
1529 { "text", JSON.Encode(StructuredContent, false) }
1530 }
1531 };
1532 Result["structuredContent"] = new Dictionary<string, object>()
1533 {
1534 { "result", StructuredContent }
1535 };
1536 }
1537 }
1538
1539 if (this.hasSnifferSet)
1540 Session.TransmitText(JSON.Encode(Result, false));
1541
1542 return Result;
1543 }
1544
1552 [JsonRpcMethod]
1553 [JsonRpcDocumentation("Lists available MCP server prompts.")]
1554 [JsonRpcDocName("prompts/list")]
1555 [return: JsonRpcDocumentation("Dictionary containing the list of prompts.")]
1556 protected async Task<Dictionary<string, object>?> Prompts_List(
1557 HttpRequest Request, HttpResponse Response,
1558
1559 [JsonRpcDocumentation("Cursor for pagination.")]
1560 string? Cursor = null)
1561 {
1562 Session? Session = await this.TryGetMcpSession(Request, Response);
1563 if (Session is null)
1564 return null;
1565
1566 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1567 if (Response.ResponseSent)
1568 return null;
1569
1570 if (this.hasSnifferSet)
1571 {
1572 StringBuilder sb = new StringBuilder();
1573
1574 sb.Append(this.Name);
1575 sb.Append(".prompts/list(");
1576 sb.Append(Cursor);
1577 sb.Append(')');
1578
1579 Session.ReceiveText(sb.ToString());
1580 }
1581
1582 int Offset = 0;
1583 int MaxCount = PageSize;
1584
1585 if (!string.IsNullOrEmpty(Cursor))
1586 {
1587 if (!int.TryParse(Cursor, out Offset) || Offset < 0)
1588 {
1589 if (!this.hasSnifferSet)
1590 Session.Error("Invalid cursor: " + Cursor);
1591
1592 await Response.SendResponse(new BadRequestException("Invalid cursor."));
1593 return null;
1594 }
1595 }
1596
1598 int Next = Offset + MaxCount;
1599
1600 Dictionary<string, object> Result = new Dictionary<string, object>();
1601
1602 lock (this.prompts)
1603 {
1604 foreach (Prompt Prompt in this.prompts.Values)
1605 {
1606 if (!Prompt.IsAuthorized(User))
1607 continue;
1608
1609 if (!this.CheckScopes(User, this.promptScopes, out _))
1610 continue;
1611
1612 if (MaxCount <= 0)
1613 {
1614 Result["nextCursor"] = Next.ToString();
1615 break;
1616 }
1617
1618 if (Offset > 0)
1619 {
1620 Offset--;
1621 continue;
1622 }
1623
1624 Prompts.Add(Prompt);
1625 MaxCount--;
1626 }
1627 }
1628
1629 int i = 0;
1630 int c = Prompts.Count;
1631
1632 Dictionary<string, object>[] PromptsJson = new Dictionary<string, object>[c];
1633
1634 foreach (Prompt Prompt in Prompts)
1635 PromptsJson[i++] = await Prompt.ToJson(this);
1636
1637 Result["prompts"] = PromptsJson;
1638
1639 if (this.hasSnifferSet)
1640 Session.TransmitText(JSON.Encode(Result, false));
1641
1642 return Result;
1643 }
1644
1655 [JsonRpcMethod]
1656 [JsonRpcDocumentation("Gets an MCP server prompt.")]
1657 [JsonRpcDocName("prompts/get")]
1658 [return: JsonRpcDocumentation("Dictionary containing the prompt.")]
1659 protected async Task<Dictionary<string, object?>?> Prompts_Get(
1660 [JsonRpcId] object? Id, HttpRequest Request, HttpResponse Response,
1661
1662 [JsonRpcDocumentation("Name of the prompt to call.")]
1663 string Name,
1664
1665 [JsonRpcDocumentation("Arguments for the prompt.")]
1666 Dictionary<string, object?> Arguments,
1667
1668 [JsonRpcMetaDataArgument]
1669 [JsonRpcDocumentation("Associated meta-data, if available.")]
1670 object? _Meta = null)
1671 {
1672 Session? Session = await this.TryGetMcpSession(Request, Response);
1673 if (Session is null)
1674 return null;
1675
1676 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1677 if (Response.ResponseSent)
1678 return null;
1679
1680 if (this.hasSnifferSet)
1681 {
1682 StringBuilder sb = new StringBuilder();
1683
1684 sb.Append(this.Name);
1685 sb.Append(".prompts/get(");
1686 sb.Append(Name);
1687 sb.Append(',');
1688 sb.Append(JSON.Encode(Arguments, false));
1689
1690 if (!(_Meta is null))
1691 {
1692 sb.Append(',');
1693 JSON.Encode(_Meta, false, sb);
1694 }
1695
1696 sb.Append(')');
1697
1698 Session.ReceiveText(sb.ToString());
1699 }
1700
1701 Dictionary<string, object?> Result = new Dictionary<string, object?>();
1702 object? PromptResult;
1703
1704 try
1705 {
1706 if (!this.prompts.TryGetValue(Name, out Prompt? Prompt))
1707 {
1708 if (this.hasSnifferSet)
1709 Session.Error("Prompt not found: " + Name);
1710
1711 await Response.SendResponse(new NotFoundException("Prompt not found."));
1712 return null;
1713 }
1714
1715 if (!Prompt.IsAuthorized(User, out string? MissingPrivilege) ||
1716 !this.CheckScopes(User, this.promptScopes, out MissingPrivilege))
1717 {
1718 if (this.hasSnifferSet)
1719 Session.Error("Access denied. Missing privilege: " + MissingPrivilege);
1720
1721 await Response.SendResponse(ForbiddenException.AccessDenied(this.ResourceName,
1722 User?.UserName ?? string.Empty, MissingPrivilege ?? string.Empty));
1723 return null;
1724 }
1725
1726 await RuntimeCounters.IncrementCounter("MCP.Prompt." + Name);
1727 await RuntimeCounters.IncrementCounter("MCP.User.Prompt." + Session.UserName);
1728
1729 Dictionary<string, object?>? MetaData = _Meta as Dictionary<string, object?>;
1730
1731 if (Prompt.TryBuildRequest(Id, Arguments, Request, Response, MetaData,
1732 out string? Reason, out object?[]? Arguments2))
1733 {
1734 PromptResult = await ScriptNode.WaitPossibleTask(
1735 Prompt.Method.Invoke(this, Arguments2));
1736 }
1737 else
1738 {
1739 if (this.hasSnifferSet)
1740 Session.Error(Reason);
1741
1742 PromptResult = Reason;
1743 Result["isError"] = true;
1744 }
1745
1747 }
1748 catch (Exception ex)
1749 {
1750 if (this.hasSnifferSet)
1751 Session.Exception(ex);
1752
1753 PromptResult = Log.UnnestException(ex).Message;
1754 Result["isError"] = true;
1755 }
1756
1758
1759 if (!(PromptResult is null))
1760 {
1761 if (PromptResult is PromptMessage PromptMessage)
1762 Messages.Add(PromptMessage);
1763 else if (PromptResult is IEnumerable<PromptMessage> PromptMessages)
1764 Messages.AddRange(PromptMessages);
1765 else
1766 {
1767 Type T = PromptResult.GetType();
1768
1769 if (contentBlocks.TryGetValue(T, out IContentBlock Encoder))
1770 {
1771 Messages.Add(new PromptMessage(McpRole.Assistant,
1772 await Encoder.Encode(PromptResult)));
1773 }
1774 else if (T.IsArray && PromptResult is IEnumerable Enumerable)
1775 {
1776 IEnumerator e = Enumerable.GetEnumerator();
1777
1778 while (e.MoveNext())
1779 {
1780 object? Item = e.Current;
1781 if (Item is null)
1782 continue;
1783
1784 if (e.Current is PromptMessage PromptMessage2)
1785 Messages.Add(PromptMessage2);
1786 else if (e.Current is IEnumerable<PromptMessage> PromptMessages2)
1787 Messages.AddRange(PromptMessages2);
1788 else
1789 Messages.Add(new PromptMessage(McpRole.Assistant, e.Current));
1790 }
1791 }
1792 else
1793 Messages.Add(new PromptMessage(McpRole.Assistant, PromptResult));
1794 }
1795 }
1796
1797 int i = 0;
1798 int c = Messages.Count;
1799 Dictionary<string, object>[] EncodedMessages = new Dictionary<string, object>[c];
1800
1801 foreach (PromptMessage Message in Messages)
1802 {
1803 Dictionary<string, object> Content;
1804
1805 if (Message.IsEncoded)
1806 Content = Message.Encoded!;
1807 else
1808 {
1809 Type T = Message.Content.GetType();
1810 if (!contentBlocks.TryGetValue(T, out IContentBlock Encoder))
1811 Encoder = defaultObjectEncoder;
1812
1813 Content = await Encoder.Encode(Message.Content);
1814 }
1815
1816 EncodedMessages[i++] = new Dictionary<string, object>()
1817 {
1818 { "role", Message.Role.ToString().ToLower() },
1819 { "content", Content }
1820 };
1821 }
1822
1823 Result["messages"] = EncodedMessages;
1824
1825 if (this.hasSnifferSet)
1826 Session.TransmitText(JSON.Encode(Result, false));
1827
1828 return Result;
1829 }
1830
1835 public virtual bool ResourcesRequireAuthentication => false;
1836
1844 [JsonRpcMethod]
1845 [JsonRpcDocumentation("Lists available MCP server resources.")]
1846 [JsonRpcDocName("resources/list")]
1847 [return: JsonRpcDocumentation("Dictionary containing the list of resources.")]
1848 protected virtual async Task<Dictionary<string, object>?> Resources_List(
1849 HttpRequest Request, HttpResponse Response,
1850
1851 [JsonRpcDocumentation("Cursor for pagination.")]
1852 string? Cursor = null)
1853 {
1854 Session? Session = await this.TryGetMcpSession(Request, Response);
1855 if (Session is null)
1856 return null;
1857
1858 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1859 if (Response.ResponseSent)
1860 return null;
1861
1862 if (this.hasSnifferSet)
1863 {
1864 StringBuilder sb = new StringBuilder();
1865
1866 sb.Append(this.Name);
1867 sb.Append(".resources/list(");
1868 sb.Append(Cursor);
1869 sb.Append(')');
1870
1871 Session.ReceiveText(sb.ToString());
1872 }
1873
1874 int Offset = 0;
1875 int MaxCount = PageSize;
1876
1877 if (!string.IsNullOrEmpty(Cursor))
1878 {
1879 if (!int.TryParse(Cursor, out Offset) || Offset < 0)
1880 {
1881 if (!this.hasSnifferSet)
1882 Session.Error("Invalid cursor: " + Cursor);
1883
1884 await Response.SendResponse(new BadRequestException("Invalid cursor."));
1885 return null;
1886 }
1887 }
1888
1889 Resource[] AllResources = await this.GetResources(Request, User, Session);
1891 Dictionary<string, object> Result = new Dictionary<string, object>();
1892 int Next = Offset + MaxCount;
1893
1894 foreach (Resource Resource in AllResources)
1895 {
1896 if (!Resource.IsAuthorized(User, out _))
1897 continue;
1898
1899 if (!this.CheckScopes(User, this.resourceScopes, out _))
1900 continue;
1901
1902 if (MaxCount <= 0)
1903 {
1904 Result["nextCursor"] = Next.ToString();
1905 break;
1906 }
1907
1908 if (Offset > 0)
1909 {
1910 Offset--;
1911 continue;
1912 }
1913
1914 Resources.Add(Resource);
1915 MaxCount--;
1916 }
1917
1918 int i = 0;
1919 int c = Resources.Count;
1920
1921 Dictionary<string, object>[] ResourcesJson = new Dictionary<string, object>[c];
1922
1923 foreach (Resource Resource in Resources)
1924 ResourcesJson[i++] = Resource.ToJson();
1925
1926 Result["resources"] = ResourcesJson;
1927
1928 if (this.hasSnifferSet)
1929 Session.TransmitText(JSON.Encode(Result, false));
1930
1931 return Result;
1932 }
1933
1942 [JsonRpcMethod]
1943 [JsonRpcDocumentation("Reads an MCP server resource.")]
1944 [JsonRpcDocName("resources/read")]
1945 [return: JsonRpcDocumentation("Dictionary containing the contents of the resource.")]
1946 protected virtual async Task<Dictionary<string, object>?> Resources_Read(
1947 HttpRequest Request, HttpResponse Response,
1948
1949 [JsonRpcDocumentation("URI of the resource to read.")]
1950 Uri Uri,
1951
1952 [JsonRpcMetaDataArgument]
1953 [JsonRpcDocumentation("Associated meta-data, if available.")]
1954 object? _Meta = null)
1955 {
1956 Session? Session = await this.TryGetMcpSession(Request, Response);
1957 if (Session is null)
1958 return null;
1959
1960 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
1961 if (Response.ResponseSent)
1962 return null;
1963
1964 if (this.hasSnifferSet)
1965 {
1966 StringBuilder sb = new StringBuilder();
1967
1968 sb.Append(this.Name);
1969 sb.Append(".resources/read(");
1970 sb.Append(Uri);
1971
1972 if (!(_Meta is null))
1973 {
1974 sb.Append(',');
1975 JSON.Encode(_Meta, false, sb);
1976 }
1977
1978 sb.Append(')');
1979
1980 Session.ReceiveText(sb.ToString());
1981 }
1982
1983 Resource? Resource = await this.TryGetResource(Request, User, Uri, Session);
1984
1985 if (Resource is null)
1986 {
1987 if (this.hasSnifferSet)
1988 Session.Error("Resource not found: " + Uri);
1989
1990 await Response.SendResponse(new NotFoundException("Resource not found."));
1991 return null;
1992 }
1993
1994 if (!Resource.IsAuthorized(User, out string? MissingPrivilege) ||
1995 !this.CheckScopes(User, this.resourceScopes, out MissingPrivilege))
1996 {
1997 if (this.hasSnifferSet)
1998 Session.Error("Access denied. Missing privilege: " + MissingPrivilege);
1999
2000 await Response.SendResponse(ForbiddenException.AccessDenied(this.ResourceName,
2001 User?.UserName ?? string.Empty, MissingPrivilege ?? string.Empty));
2002 return null;
2003 }
2004
2005 await RuntimeCounters.IncrementCounter("MCP.Resource." + Resource.Name);
2006 await RuntimeCounters.IncrementCounter("MCP.User.Resource." + Session.UserName);
2007
2008 Dictionary<string, object>? MetaData = _Meta as Dictionary<string, object>;
2009
2010 IResourceContent[] Content = await Resource.Read(MetaData);
2011 int i, c = Content.Length;
2012
2013 Dictionary<string, object>[] Contents = new Dictionary<string, object>[c];
2014
2015 for (i = 0; i < c; i++)
2016 Contents[i] = Content[i].Encode();
2017
2018 Dictionary<string, object> Result = new Dictionary<string, object>()
2019 {
2020 { "contents",Contents }
2021 };
2022
2023 if (this.hasSnifferSet)
2024 Session.TransmitText(JSON.Encode(Result, false));
2025
2026 return Result;
2027 }
2028
2035 [JsonRpcMethod]
2036 [JsonRpcDocumentation("Subscribes to an MCP server resource.")]
2037 [JsonRpcDocName("resources/subscribe")]
2038 protected virtual async Task Resources_Subscribe(
2039 HttpRequest Request, HttpResponse Response,
2040
2041 [JsonRpcDocumentation("URI of the resource to subscribe to.")]
2042 Uri Uri)
2043 {
2044 Session? Session = await this.TryGetMcpSession(Request, Response);
2045 if (Session is null)
2046 return;
2047
2048 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
2049 if (Response.ResponseSent)
2050 return;
2051
2052 if (this.hasSnifferSet)
2053 {
2054 StringBuilder sb = new StringBuilder();
2055
2056 sb.Append(this.Name);
2057 sb.Append(".resources/subscribe(");
2058 sb.Append(Uri);
2059 sb.Append(')');
2060
2061 Session.ReceiveText(sb.ToString());
2062 }
2063
2064 Resource? Resource = await this.TryGetResource(Request, User, Uri, Session);
2065
2066 if (Resource is null)
2067 {
2068 if (this.hasSnifferSet)
2069 Session.Error("Resource not found: " + Uri);
2070
2071 await Response.SendResponse(new NotFoundException("Resource not found."));
2072 return;
2073 }
2074
2075 if (!Resource.IsAuthorized(User, out string? MissingPrivilege) ||
2076 !this.CheckScopes(User, this.resourceScopes, out MissingPrivilege))
2077 {
2078 if (this.hasSnifferSet)
2079 Session.Error("Access denied. Missing privilege: " + MissingPrivilege);
2080
2081 await Response.SendResponse(ForbiddenException.AccessDenied(this.ResourceName,
2082 User?.UserName ?? string.Empty, MissingPrivilege ?? string.Empty));
2083 return;
2084 }
2085
2086 bool Result = Session.Subscribe(Uri.ToString());
2087
2088 if (this.hasSnifferSet)
2089 {
2090 if (Result)
2091 Session.Information("Subscription to resource successful: " + Uri);
2092 else
2093 Session.Information("Subscription already exists for: " + Uri);
2094 }
2095 }
2096
2103 [JsonRpcMethod]
2104 [JsonRpcDocumentation("Unsubscribes from an MCP server resource.")]
2105 [JsonRpcDocName("resources/unsubscribe")]
2106 protected virtual async Task Resources_Unsubscribe(
2107 HttpRequest Request, HttpResponse Response,
2108
2109 [JsonRpcDocumentation("URI of the resource to unsubscribe from.")]
2110 Uri Uri)
2111 {
2112 Session? Session = await this.TryGetMcpSession(Request, Response);
2113 if (Session is null)
2114 return;
2115
2116 IUser? User = await this.GetAuthenticatedUser(Request, Response, Session);
2117 if (Response.ResponseSent)
2118 return;
2119
2120 if (this.hasSnifferSet)
2121 {
2122 StringBuilder sb = new StringBuilder();
2123
2124 sb.Append(this.Name);
2125 sb.Append(".resources/unsubscribe(");
2126 sb.Append(Uri);
2127 sb.Append(')');
2128
2129 Session.ReceiveText(sb.ToString());
2130 }
2131
2132 Resource? Resource = await this.TryGetResource(Request, User, Uri, Session);
2133
2134 if (Resource is null)
2135 {
2136 if (this.hasSnifferSet)
2137 Session.Error("Resource not found: " + Uri);
2138
2139 await Response.SendResponse(new NotFoundException("Resource not found."));
2140 return;
2141 }
2142
2143 if (!Resource.IsAuthorized(User, out string? MissingPrivilege) ||
2144 !this.CheckScopes(User, this.resourceScopes, out MissingPrivilege))
2145 {
2146 if (this.hasSnifferSet)
2147 Session.Error("Access denied. Missing privilege: " + MissingPrivilege);
2148
2149 await Response.SendResponse(ForbiddenException.AccessDenied(this.ResourceName,
2150 User?.UserName ?? string.Empty, MissingPrivilege ?? string.Empty));
2151 return;
2152 }
2153
2154 bool Result = Session.Unsubscribe(Uri.ToString());
2155
2156 if (this.hasSnifferSet)
2157 {
2158 if (Result)
2159 Session.Information("Unsubscription from resource successful: " + Uri);
2160 else
2161 Session.Information("No subscription found for: " + Uri);
2162 }
2163 }
2164
2172 public virtual Task<Resource[]> GetResources(HttpRequest Request, IUser? User,
2174 {
2175 return Task.FromResult(Array.Empty<Resource>());
2176 }
2177
2181 public virtual bool HasResources => false;
2182
2189 public virtual KeyValuePair<bool, string>[] ResourceDocumentation =>
2190 Array.Empty<KeyValuePair<bool, string>>();
2191
2200 public virtual Task<Resource?> TryGetResource(HttpRequest Request, IUser? User,
2201 Uri Uri, Session? Session)
2202 {
2203 return Task.FromResult<Resource?>(null);
2204 }
2205
2213 public virtual async void ResourcesUpdated(IUser User)
2214 {
2215 try
2216 {
2217 Dictionary<string, object> Notification = new Dictionary<string, object>()
2218 {
2219 { "jsonrpc", "2.0" },
2220 { "method", "notifications/resources/list_changed" }
2221 };
2222
2223 await this.SendNotification(
2224 Session =>
2225 {
2226 if (!(Session is Session McpSession))
2227 return false;
2228
2229 if (this.hasSnifferSet)
2230 McpSession.TransmitText(JSON.Encode(Notification, false));
2231
2232 return true;
2233 },
2234 Notification);
2235 }
2236 catch (Exception ex)
2237 {
2238 Log.Exception(ex);
2239 }
2240 }
2241
2242 private Task SendNotification(Predicate<IJsonRpcSession?> Filter,
2243 Dictionary<string, object> Notification)
2244 {
2245 return this.SendEvent(
2246 Filter,
2247 new KeyValuePair<string, object>("event", "message"),
2248 new KeyValuePair<string, object>("data", JSON.Encode(Notification, false)));
2249 }
2250
2256 public virtual async void ResourceUpdated(IUser User, Uri Uri)
2257 {
2258 try
2259 {
2260 Dictionary<string, object> Notification = new Dictionary<string, object>()
2261 {
2262 { "jsonrpc", "2.0" },
2263 { "method", "notifications/resources/updated" },
2264 { "params", new Dictionary<string, object>()
2265 {
2266 { "uri", Uri.OriginalString }
2267 }
2268 }
2269 };
2270
2271 string s = Uri.ToString();
2272
2273 await this.SendNotification(
2274 Session =>
2275 {
2276 if (!(Session is Session McpSession))
2277 return false;
2278
2279 if (!McpSession.IsSubscribed(s))
2280 return false;
2281
2282 if (this.hasSnifferSet)
2283 McpSession.TransmitText(JSON.Encode(Notification, false));
2284
2285 return true;
2286 },
2287 Notification);
2288 }
2289 catch (Exception ex)
2290 {
2291 Log.Exception(ex);
2292 }
2293 }
2294
2301 public async Task DELETE(HttpRequest Request, HttpResponse Response)
2302 {
2303 Session? Session = await this.TryGetMcpSession(Request, Response);
2304 if (Session is null)
2305 return;
2306
2307 sessions.Remove(Session.SessionId);
2308
2309 try
2310 {
2311 Response.StatusCode = 204;
2312 Response.StatusMessage = "No Content";
2313
2314 await Response.SendResponse();
2315 }
2316 finally
2317 {
2318 await this.SendEvent(
2319 Loop => Session == Loop,
2320 "Terminating session.");
2321 this.Unregister(Session);
2322 }
2323 }
2324
2330 protected override KeyValuePair<bool, string>[] GetParameterDocumentation(
2332 {
2333 Parameter.AdditionalDocumentation ??= GetAdditionalParameterDocumentation(Parameter);
2334 return base.GetParameterDocumentation(Parameter);
2335 }
2336
2341 protected override KeyValuePair<bool, string>[] GetMemberDocumentation(
2342 ICustomAttributeProvider Member)
2343 {
2344 ChunkedList<KeyValuePair<bool, string>>? PropertyDoc = null;
2345
2346 foreach (object Attribute in
2347 Member.GetCustomAttributes(typeof(McpParameterAttribute), true))
2348 {
2349 if (Attribute is McpParameterAttribute TypedAttribute)
2350 {
2351 PropertyDoc ??= new ChunkedList<KeyValuePair<bool, string>>();
2352 PropertyDoc.Add(new KeyValuePair<bool, string>(
2353 true, TypedAttribute.AnnotatedDescription));
2354 }
2355 }
2356
2357 StringBuilder? Values = null;
2358
2359 foreach (object Attribute in
2360 Member.GetCustomAttributes(typeof(McpEnumValueAttribute), true))
2361 {
2362 if (Attribute is McpEnumValueAttribute TypedAttribute)
2363 {
2364 if (Values is null)
2365 {
2366 Values = new StringBuilder();
2367 Values.AppendLine("Possible values:");
2368 Values.AppendLine();
2369 }
2370
2371 Values.Append("* `\"");
2372 Values.Append(TypedAttribute.Value.ToString());
2373 Values.Append("\"` - ");
2374 Values.AppendLine(TypedAttribute.Title);
2375 }
2376 }
2377
2378 if (!(Values is null))
2379 {
2380 PropertyDoc ??= new ChunkedList<KeyValuePair<bool, string>>();
2381 PropertyDoc.Add(new KeyValuePair<bool, string>(true, Values.ToString()));
2382 }
2383
2384 return (PropertyDoc?.ToArray() ?? Array.Empty<KeyValuePair<bool, string>>()).Join(
2385 base.GetMemberDocumentation(Member));
2386 }
2387
2393 private static KeyValuePair<bool, string>[] GetAdditionalParameterDocumentation(
2395 {
2397
2398 foreach (object Attribute in Parameter.Parameter.
2399 GetCustomAttributes(typeof(McpParameterAttribute), true))
2400 {
2402 {
2404 Result.Add(new KeyValuePair<bool, string>(true,
2406 }
2407 }
2408
2409 StringBuilder? Values = null;
2410
2411 foreach (object Attribute in Parameter.Parameter.
2412 GetCustomAttributes(typeof(McpEnumValueAttribute), true))
2413 {
2415 {
2416 if (Values is null)
2417 {
2418 Values = new StringBuilder();
2419 Values.AppendLine("Possible values:");
2420 Values.AppendLine();
2421 }
2422
2423 Values.Append("* `\"");
2424 Values.Append(McpEnumValueAttribute.Value.ToString());
2425 Values.Append("\"` - ");
2426 Values.AppendLine(McpEnumValueAttribute.Title);
2427 }
2428 }
2429
2430 if (!(Values is null))
2431 {
2433 Result.Add(new KeyValuePair<bool, string>(true, Values.ToString()));
2434 }
2435
2436 return Result?.ToArray() ?? Array.Empty<KeyValuePair<bool, string>>();
2437 }
2438
2446 protected override async Task GenerateDocumentationApiDescription(
2447 ChunkedList<string> Notes, HashSet<Type> TypesToDocument,
2448 HttpRequest Request, StringBuilder Markdown)
2449 {
2450 Markdown.AppendLine(new string('=', 80));
2451 Markdown.AppendLine();
2452 Markdown.AppendLine("MCP Server Interface");
2453 Markdown.AppendLine("-----------------------");
2454 Markdown.AppendLine();
2455 Markdown.Append("This [MCP Server](https://modelcontextprotocol.io/specification/2025-11-25) ");
2456 Markdown.AppendLine("is accessible on this endpoint: `");
2457 Markdown.Append(Request.Header.GetURL(false, false));
2458 Markdown.AppendLine("`");
2459 Markdown.AppendLine();
2460
2461 if (this.hasScopes)
2462 {
2463 Markdown.AppendLine("Scopes supported:");
2464 Markdown.AppendLine();
2465
2466 foreach (string Scope in this.scopesSupported)
2467 {
2468 Markdown.Append("* `");
2469 Markdown.Append(Scope);
2470 Markdown.AppendLine("`");
2471 }
2472
2473 Markdown.AppendLine();
2474 }
2475
2476 Markdown.Append("The following subsections list MCP Server interfaces that are ");
2477 Markdown.Append("available on this resource. The MCP protocol is built on ");
2478 Markdown.Append("top of the [JSON-RPC protocol](#jsonRpcInterface). You find ");
2479 Markdown.AppendLine("JSON-RPC interface below.");
2480 Markdown.AppendLine();
2481
2482 if (this.hasTools)
2483 {
2484 await this.GenerateToolDocumentation(Notes, TypesToDocument,
2485 Request, Markdown);
2486 }
2487
2488 if (this.hasPrompts)
2489 {
2490 await this.GeneratePromptDocumentation(Notes, TypesToDocument,
2491 Request, Markdown);
2492 }
2493
2494 if (this.HasResources)
2495 await this.GenerateResourceDocumentation(Notes, Request, Markdown);
2496
2497 await base.GenerateDocumentationApiDescription(Notes, TypesToDocument,
2498 Request, Markdown);
2499 }
2500
2509 HashSet<Type> TypesToDocument, HttpRequest Request, StringBuilder Markdown)
2510 {
2511 Markdown.AppendLine(new string('=', 80));
2512 Markdown.AppendLine();
2513 Markdown.AppendLine("MCP Server Tools");
2514 Markdown.AppendLine("-------------------");
2515 Markdown.AppendLine();
2516 Markdown.Append("Following subsections list ");
2517 Markdown.Append("[MCP Server Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) ");
2518 Markdown.AppendLine("that can be used to interact with the MCP Server.");
2519 Markdown.AppendLine();
2520
2521 Tool[] Tools;
2522
2523 lock (this.tools)
2524 {
2525 Tools = new Tool[this.tools.Count];
2526 this.tools.Values.CopyTo(Tools, 0);
2527 }
2528
2529 foreach (Tool Tool in Tools)
2530 {
2531 Markdown.AppendLine("<section>");
2532 Markdown.AppendLine();
2533 Markdown.Append("### ");
2534 Markdown.AppendLine(Tool.Title);
2535 Markdown.AppendLine();
2536
2537 Markdown.AppendLine(MarkdownDocument.Encode(Tool.Description));
2538 Markdown.AppendLine();
2539
2540 Markdown.AppendLine("| Properties ||");
2541 Markdown.AppendLine("|:-------|:------:|");
2542 Markdown.Append("| Can Modify: | ");
2543 Markdown.Append(YesNo(Tool.CanModifyEnvironment));
2544 Markdown.AppendLine(" |");
2545 Markdown.Append("| Can Destroy: | ");
2546 Markdown.Append(YesNo(Tool.CanDestroyEnvironment));
2547 Markdown.AppendLine(" |");
2548 Markdown.Append("| Is Idempotent: | ");
2549 Markdown.Append(YesNo(Tool.Idempotent));
2550 Markdown.AppendLine(" |");
2551 Markdown.Append("| Open World Access: | ");
2552 Markdown.Append(YesNo(Tool.OpenWorldAccess));
2553 Markdown.AppendLine(" |");
2554 Markdown.AppendLine();
2555
2556 this.AppendDocumentation(Notes, TypesToDocument, Tool, Markdown);
2557 }
2558
2559 return Task.CompletedTask;
2560 }
2561
2570 HashSet<Type> TypesToDocument, HttpRequest Request, StringBuilder Markdown)
2571 {
2572 Markdown.AppendLine(new string('=', 80));
2573 Markdown.AppendLine();
2574 Markdown.AppendLine("MCP Server Prompts");
2575 Markdown.AppendLine("---------------------");
2576 Markdown.AppendLine();
2577 Markdown.Append("Following subsections list ");
2578 Markdown.Append("[MCP Server Prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) ");
2579 Markdown.AppendLine("that can be used to interact with the MCP Server.");
2580 Markdown.AppendLine();
2581
2582 Prompt[] Prompts;
2583
2584 lock (this.prompts)
2585 {
2586 Prompts = new Prompt[this.prompts.Count];
2587 this.prompts.Values.CopyTo(Prompts, 0);
2588 }
2589
2590 foreach (Prompt Prompt in Prompts)
2591 {
2592 Markdown.AppendLine("<section>");
2593 Markdown.AppendLine();
2594 Markdown.Append("### ");
2595 Markdown.AppendLine(Prompt.Title);
2596 Markdown.AppendLine();
2597
2598 Markdown.AppendLine(MarkdownDocument.Encode(Prompt.Description));
2599 Markdown.AppendLine();
2600
2601 this.AppendDocumentation(Notes, TypesToDocument, Prompt, Markdown);
2602 }
2603
2604 return Task.CompletedTask;
2605 }
2606
2614 HttpRequest Request, StringBuilder Markdown)
2615 {
2616 Markdown.AppendLine(new string('=', 80));
2617 Markdown.AppendLine();
2618 Markdown.AppendLine("MCP Server Resources");
2619 Markdown.AppendLine("-----------------------");
2620 Markdown.AppendLine();
2621
2622 Markdown.Append("This MCP Server supports [MCP Server Resources]");
2623 Markdown.Append("(https://modelcontextprotocol.io/specification/2025-11-25/server/resources).");
2624 Markdown.AppendLine();
2625
2626 AppendDocumentation(this.ResourceDocumentation, Markdown);
2627
2628 return Task.CompletedTask;
2629 }
2630
2631
2645 string Message, T InputRequest, bool Sensitive, Session Session, int Timeout)
2646 where T : class
2647 {
2649 throw new ServiceUnavailableException("MCP Client does not support elication of user input.");
2650
2651 Type InputType = typeof(T);
2652 McpParameterAttribute? ParameterInfo = InputType.GetCustomAttribute<McpParameterAttribute>();
2653 IEnumerable<McpEnumValueAttribute> EnumValues = InputType.GetCustomAttributes<McpEnumValueAttribute>();
2654 object InputSchema = Tool.GenerateSchema(InputType, true, InputRequest,
2655 ParameterInfo, EnumValues);
2656 Dictionary<string, object?> ElicitationRequest;
2657
2658 if (Session.ClientCapabilities.Elicitation.Form && !Sensitive)
2659 {
2660 ElicitationRequest = new Dictionary<string, object?>()
2661 {
2662 { "mode", "form" },
2663 { "message", Message },
2664 { "requestedSchema", InputSchema }
2665 };
2666 }
2668 {
2669 ElicitationRequest = new Dictionary<string, object?>()
2670 {
2671 { "mode", "url" },
2672 { "message", Message }
2673 };
2674 }
2675 else
2676 throw new ServiceUnavailableException("Unable to elicit user input via URL.");
2677
2678 using JsonRpcClientRequest<bool?> Request = this.CreateRequest<bool?>(
2679 Message, "elicitation/create", ElicitationRequest, Session,
2680 async Result =>
2681 {
2682 if (!(Result is Dictionary<string, object> ResultObj))
2683 throw new BadRequestException("Invalid response.");
2684
2685 if (!ResultObj.TryGetValue("action", out object Obj) ||
2686 !(Obj is string Action))
2687 {
2688 throw new BadRequestException("Expected action.");
2689 }
2690
2691 switch (Action)
2692 {
2693 case "decline": return false;
2694 case "cancel": return null;
2695
2696 case "accept":
2697 if (!ResultObj.TryGetValue("content", out Obj))
2698 throw new BadRequestException("Missing content.");
2699
2700 if (!(Obj is Dictionary<string, object> Properties))
2701 throw new BadRequestException("Invalid content.");
2702
2703 await SetProperties(InputRequest, Properties);
2704
2705 return true;
2706
2707 default:
2708 throw new Exception("Unexpected action: " + Action);
2709 }
2710 },
2711 HttpRequest);
2712
2713 Request.Tag = InputRequest;
2714
2715 if (!Session.ClientCapabilities.Elicitation.Form || Sensitive)
2716 {
2717 string Url = HttpRequest.Header.GetURL(false, false) + "/" + Request.Id;
2718
2719 ElicitationRequest["elicitationId"] = Request.Id;
2720 ElicitationRequest["url"] = Url;
2721
2722 async Task Completed(object _, EventArgs e)
2723 {
2724 Dictionary<string, object> Notification = new Dictionary<string, object>()
2725 {
2726 { "jsonrpc", "2.0" },
2727 { "method", "notifications/elicitation/complete" },
2728 { "params", new Dictionary<string, object?>()
2729 {
2730 { "elicitationId", Request.Id }
2731 }
2732 }
2733 };
2734
2735 await this.SendNotification(
2736 Session2 =>
2737 {
2738 if (Session.SessionId != Session2?.SessionId)
2739 return false;
2740
2741 Session2.TransmitText(JSON.Encode(Notification, false));
2742
2743 return true;
2744 },
2745 Notification);
2746 }
2747
2748 Request.ResultReturned += Completed;
2749 Request.ErrorReturned += Completed;
2750 Request.Cancelled += Completed;
2751 }
2752
2753 await Request.SendRequest();
2754 return await Request.WaitForResultAsync(Timeout);
2755 }
2756
2757 private void Request_ResultReturned(object sender, EventArgs e)
2758 {
2759 throw new System.NotImplementedException();
2760 }
2761
2762 internal static async Task SetProperties(object Object, Dictionary<string, object> Properties)
2763 {
2764 Type T = Object.GetType();
2765
2766 foreach (KeyValuePair<string, object> P in Properties)
2767 {
2768 object Value = P.Value;
2769 Dictionary<string, object>? SubProperties = Value as Dictionary<string, object>;
2770 bool IsSubProperties = !(SubProperties is null);
2771
2772 FieldInfo? FI = T.GetField(P.Key, BindingFlags.Public | BindingFlags.Instance);
2773 if (!(FI is null))
2774 {
2775 if (IsSubProperties)
2776 {
2777 object? Item = FI.GetValue(Object);
2778
2779 if (Item is null)
2780 {
2781 Item = Types.Create(false, FI.FieldType);
2782 FI.SetValue(Object, Item);
2783 }
2784
2785 await SetProperties(Item, SubProperties!);
2786 }
2787 else if (Value is null || FI.FieldType.IsAssignableFrom(Value.GetType()))
2788 FI.SetValue(Object, Value);
2789 else if (Expression.TryConvert(Value, FI.FieldType, true, out object Value2))
2790 FI.SetValue(Object, Value2);
2791 else
2792 {
2793 throw new InvalidCastException("Unable to convert value of type " +
2794 Value.GetType().FullName + " to " +
2795 FI.FieldType.FullName + ".");
2796 }
2797
2798 continue;
2799 }
2800
2801 PropertyInfo? PI = T.GetProperty(P.Key, BindingFlags.Public | BindingFlags.Instance);
2802 if (!(PI is null))
2803 {
2804 if (IsSubProperties)
2805 {
2806 object? Item = PI.GetValue(Object);
2807
2808 if (Item is null)
2809 {
2810 Item = Types.Create(false, PI.PropertyType);
2811 PI.SetValue(Object, Item);
2812 }
2813
2814 await SetProperties(Item, SubProperties!);
2815 }
2816 else if (Value is null || PI.PropertyType.IsAssignableFrom(Value.GetType()))
2817 PI.SetValue(Object, Value);
2818 else if (Expression.TryConvert(Value, PI.PropertyType, true, out object Value2))
2819 PI.SetValue(Object, Value2);
2820 else
2821 {
2822 throw new InvalidCastException("Unable to convert value of type " +
2823 Value.GetType().FullName + " to " +
2824 PI.PropertyType.FullName + ".");
2825 }
2826
2827 continue;
2828 }
2829
2830 throw new InvalidOperationException("Unrecognized field ro property name: " + P.Key);
2831 }
2832 }
2833 }
2834}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Definition: CommonTypes.cs:669
Contains information about a response to a content request.
override string ToString()
Definition: HtmlElement.cs:230
Image encoder/decoder.
Definition: ImageCodec.cs:14
const string ContentTypeIcon
image/x-icon
Definition: ImageCodec.cs:40
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:211
static string HtmlAttributeEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe.
Definition: XML.cs:121
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
static ForbiddenException AccessDenied(string ObjectId, string ActorId)
Returns a ForbiddenException object, and logs a entry in the event log about the event.
Base class for all HTTP authentication schemes, as defined in RFC-7235: https://datatracker....
virtual bool UserSessions
If the authentication scheme uses user sessions.
int MinStrength
Minimum security strength of algorithms used.
bool RequireEncryption
If scheme requires encryption.
abstract Task< IUser > IsAuthenticated(HttpRequest Request)
Checks if the request is authorized.
abstract string[] GetChallenges(HttpRequest Request)
Gets available challenges for the authenticating client to respond to.
Base class for all HTTP fields.
Definition: HttpField.cs:7
bool TryGetHeaderField(string FieldName, out HttpField Field)
Tries to get a named header field.
Definition: HttpHeader.cs:247
HttpFieldAuthorization Authorization
Authorization HTTP Field header. (RFC 2616, §14.8)
string GetURL()
Gets an absolute URL for the request.
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:243
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
Definition: HttpRequest.cs:212
bool Encrypted
If the connection is encrypted or not.
Definition: HttpRequest.cs:298
string SubPath
Sub-path. If a resource is found handling the request, this property contains the trailing sub-path o...
Definition: HttpRequest.cs:194
IUser User
Authenticated user, if available, or null if not available.
Definition: HttpRequest.cs:203
int CipherStrength
Cipher strength
Definition: HttpRequest.cs:304
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
SessionVariables GetSessionFromCookie()
Gets the session variables from the cookie, if available.
Definition: HttpRequest.cs:350
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
bool ResponseSent
If the response has been sent.
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
Task Return(Exception ex)
Returns an error to the client.
Implements an HTTP server.
Definition: HttpServer.cs:41
Represents a request made to a JSON-RPC client.
Abstract base class for Web Services based on JSON-RPC v2.0.
bool TryGetRequest(string Id, [NotNullWhen(true)] out IJsonRpcClientRequest? Request)
Tries to get a pending client request, given its ID.
Task< int > SendEvent(IDictionary< string, object > Fields)
Sends an event to clients with open subscriptions.
bool Unregister(MethodInfo Method)
Unregisters a method from the JSON-RPC interface.
bool HasJwtFactory
If a JWT Factory is available.
void AddAuthenticationMechanisms(ProtectedMethod Method)
Adds authentication mechanisms to a method, if required.
HttpAuthenticationScheme?[] AuthenticationSchemes
Generic authentication schemes for the resource.
virtual void AppendDocumentation(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, ProtectedMethod Method, StringBuilder Markdown)
Appends Documentation to a Markdown document.
static string YesNo(bool Value)
Returns "Yes" or "No" based on the boolean value provided.
Information about an argument in a protected method.
Information about a protected method.
bool IsAuthorized(IUser? User)
Checks if a user is authorized to call the method.
bool RequiresAuthentication
If authentication of the user is required.
Abstract base class for HTTP-based Model Context Protocol (MCP) server resource, as defined in:
override KeyValuePair< bool, string >[] GetParameterDocumentation(ProtectedMethodArgumentInfo Parameter)
Gets parameter documentation for a method parameter.
virtual Task GenerateResourceDocumentation(ChunkedList< string > Notes, HttpRequest Request, StringBuilder Markdown)
Generates documentation for MCP Server resources.
async Task DELETE(HttpRequest Request, HttpResponse Response)
Executes the DELETE method on the resource.
virtual async Task Resources_Subscribe(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("URI of the resource to subscribe to.")] Uri Uri)
Subscribes to an MCP server resource.
async Task Notifications_Initialized(HttpRequest Request, HttpResponse Response)
Notification that the client has completed its initialization.
string[] McpScopesSupported()
OAUTH scopes supported by resource.
virtual bool HasResources
If the MCP server has resource capabilities.
virtual Task GeneratePromptDocumentation(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, HttpRequest Request, StringBuilder Markdown)
Generates documentation for MCP Server prompts.
virtual async void ResourcesUpdated(IUser User)
Called when the resources have been updated (new resources added, existing resources updated or remov...
override async Task< IJsonRpcSession?> TryGetSession(HttpRequest Request, HttpResponse Response)
Tries to get a session object for the resource, if any.
static Icon[] GetDefaultIcons()
Gets default icons, if any.
override KeyValuePair< bool, string >[] GetMemberDocumentation(ICustomAttributeProvider Member)
Gets documentation for a member.
const string ResourcesScopeSuffix
Scope suffix for MCP server resources is ":resources".
override string MarkdownDescription
Markdown description of web service.
async Task< Dictionary< string, object?>?> Prompts_Get([JsonRpcId] object? Id, HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Name of the prompt to call.")] string Name, [JsonRpcDocumentation("Arguments for the prompt.")] Dictionary< string, object?> Arguments, [JsonRpcMetaDataArgument][JsonRpcDocumentation("Associated meta-data, if available.")] object? _Meta=null)
Gets an MCP server prompt.
virtual async Task< Dictionary< string, object >?> Resources_Read(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("URI of the resource to read.")] Uri Uri, [JsonRpcMetaDataArgument][JsonRpcDocumentation("Associated meta-data, if available.")] object? _Meta=null)
Reads an MCP server resource.
override bool SendSseWelcomeMessage
If a Server-Sent Events (SSE) welcome message should be sent to clients with open subscriptions.
virtual async Task Resources_Unsubscribe(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("URI of the resource to unsubscribe from.")] Uri Uri)
Unsubscribes from an MCP server resource.
HttpMcpServerResource(string ResourceName, string Name, string Title, string Version, string Description, Icon[] Icons, Uri? WebSiteUri, string Instructions)
Abstract base class for HTTP-based Model Context Protocol (MCP) server resource.
const string ToolsScopeSuffix
Scope suffix for MCP server tools is ":tools".
Task RegisterPrompt(MethodInfo Method, McpServerPromptAttribute Attributes)
Registers a MCP Server prompt.
async Task< Dictionary< string, object >?> Prompts_List(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Cursor for pagination.")] string? Cursor=null)
Lists available MCP server prompts.
override bool HandlesSubPaths
If the resource handles sub-paths.
HttpMcpServerResource(string ResourceName, string Name, string Title, string Version, string Description, Icon[] Icons, Uri? WebSiteUri, string Instructions, ISnifferSet? SnifferSet)
Abstract base class for HTTP-based Model Context Protocol (MCP) server resource.
bool AllowsDELETE
If the DELETE method is allowed.
const string PromptsScopeSuffix
Scope suffix for MCP server prompts is ":prompts".
virtual bool ResourcesRequireAuthentication
If resources published by the MCP Server require authentication. If true, the client must authenticat...
override async Task GET(HttpRequest Request, HttpResponse Response)
Executes the GET method on the resource.
async Task< Session?> TryGetMcpSession(HttpRequest Request, HttpResponse Response)
Tries to get an MCP session object for the resource, if any.
override bool SupportsServerSentEvents
If Server-Sent Events (SSE) are supported by the resource.
override void AddReference(HttpServer Server)
Method called when a resource has been registered on a server.
async Task< Dictionary< string, object >?> Tools_List(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Cursor for pagination.")] string? Cursor=null)
Lists available MCP server tools.
async Task< bool?> ElicitUserInput< T >(HttpRequest HttpRequest, string Message, T InputRequest, bool Sensitive, Session Session, int Timeout)
Elicits input from the user, if the client supports elicitation.
override string ShortDescription
Description of MCP server.
virtual Task< Resource?> TryGetResource(HttpRequest Request, IUser? User, Uri Uri, Session? Session)
Tries to get a resource, given its URI.
Task RegisterTool(MethodInfo Method, McpServerToolAttribute Attributes)
Registers a MCP Server tool.
override async Task GenerateDocumentationApiDescription(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, HttpRequest Request, StringBuilder Markdown)
Generates the Markdown ApiDescription for the documentation page.
virtual async Task< Dictionary< string, object >?> Resources_List(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Cursor for pagination.")] string? Cursor=null)
Lists available MCP server resources.
override async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
virtual async void ResourceUpdated(IUser User, Uri Uri)
Called when a single resource has been updated.
Dictionary< string, object > Initialize(HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Protocol Version")] string ProtocolVersion, [JsonRpcDocumentation("Client capabilities")] Dictionary< string, object > Capabilities, [JsonRpcDocumentation("Client information")] Dictionary< string, object > ClientInfo)
MCP initialize method. Called by client to initialize connection and exchange information about capab...
virtual KeyValuePair< bool, string >[] ResourceDocumentation
MCP server resource documentation, as an array of key-value pairs. The Key represents Markdown (true)...
async Task< Dictionary< string, object?>?> Tools_Call([JsonRpcId] object? Id, HttpRequest Request, HttpResponse Response, [JsonRpcDocumentation("Name of the tool to call.")] string Name, [JsonRpcDocumentation("Arguments for the tool.")] Dictionary< string, object?> Arguments, [JsonRpcDocumentation("If specified, the caller is requesting task-augmented "+"execution for this request. The request will return a `CreateTaskResult` "+"immediately, and the actual result can be retrieved later via tasks/result.\r\n\r\n"+"Task augmentation is subject to capability negotiation - receivers MUST declare "+"support for task augmentation of specific request types in their capabilities.", true)] object? Task=null, [JsonRpcMetaDataArgument][JsonRpcDocumentation("Associated meta-data, if available.")] object? _Meta=null)
Calls an MCP server tool.
virtual Task GenerateToolDocumentation(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, HttpRequest Request, StringBuilder Markdown)
Generates documentation for MCP Server tools.
virtual Task< Resource[]> GetResources(HttpRequest Request, IUser? User, Session? Session)
Gets available resources.
virtual void Annotate(Dictionary< string, object?> Schema)
Annotates a schema object with information in the attribute.
virtual void GetHtmlInputAttributes(Dictionary< string, string > Attributes)
Gets HTML input attributes for the parameter, if any.
virtual string AnnotatedDescription
Annotated description of parameter.
virtual string GetHtmlAttributeValue(object Value)
Gets the HTML attribute value for a parameter, if any.
Defines a scope root for an MCP server web resource.
Defines a method in an HttpMcpServerResource implementation as a recipient of an MCP Server Prompt in...
string IconsMethod
Name of method that returns an Icon?, an an Icon[]? or an Icons? resource representing the prompt....
Defines a method in an HttpMcpServerResource implementation as a recipient of an MCP Server Tool invo...
bool OpenWorldAccess
If true, this tool may interact with an "open world" of external entities.If false,...
bool CanModifyEnvironment
If the tool can modify the environment. If false, the tool is expected to be read-only and not cause ...
string Description
A human-readable description of the tool.
string IconsMethod
Name of method that returns an Icon?, an an Icon[]? or an Icons? resource representing the prompt....
bool CanDestroyEnvironment
If true, the tool may perform destructive updates to its environment. If false, the tool performs onl...
bool Idempotent
If true, calling the tool repeatedly with the same arguments will have no additional effect on its en...
static bool TryParse(Dictionary< string, object > Generic, out ClientCapabilities Typed)
Tries to parse a generic structure into a typed structure.
ElicitationCapabilities? Elicitation
Present if the client supports elicitation from the server.
static bool TryParse(Dictionary< string, object > Generic, out Implementation Typed)
Tries to parse a generic structure into a typed structure.
Abstract base class for an MCP Content Block.
Definition: ContentBlock.cs:11
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
Dictionary< string, object >[] ToJson()
Converts object to a generic representation.
Definition: Icons.cs:81
Describes a message returned as part of a prompt.
Dictionary< string, object >? Encoded
Encoded content.
McpRole Role
Role of recipient of message.
bool IsEncoded
If the content has been encoded.
Contains information about an MCP Server Prompt
Definition: Prompt.cs:15
string Title
A human-readable title for the prompt.
Definition: Prompt.cs:45
McpParameterAttribute? ReturnAttributes
Any MCP attributes declared for the return value.
Definition: Prompt.cs:70
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: Prompt.cs:148
async Task< Dictionary< string, object > > ToJson(HttpMcpServerResource Resource)
Converts object to a generic representation.
Definition: Prompt.cs:77
string Description
A human-readable description of the prompt.
Definition: Prompt.cs:53
Contains information about an MCP Server Resource
Definition: Resource.cs:14
bool IsAuthorized(IUser? User, [NotNullWhen(false)] out string? MissingPrivilege)
Checks if a user is authorized to call the method.
Definition: Resource.cs:260
abstract Task< IResourceContent[]> Read(Dictionary< string, object >? MetaData)
Reads the resource.
Dictionary< string, object > ToJson()
Converts object to a generic representation.
Definition: Resource.cs:282
bool Unsubscribe(string Uri)
Unsubscribes from a resource.
Definition: Session.cs:254
string UserName
User name used for session.
Definition: Session.cs:76
void TransmitText(string Text)
Text has been transmitted to the client.
Definition: Session.cs:152
string RemoteEndpoint
Client remote endpoint.
Definition: Session.cs:71
bool Subscribe(string Uri)
Subscribes to a resource.
Definition: Session.cs:241
bool IsAuthenticated
If client has been authenticated in the session.
Definition: Session.cs:81
ClientCapabilities? ClientCapabilities
Client capabilities, if available.
Definition: Session.cs:61
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 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 Idempotent
If true, calling the tool repeatedly with the same arguments will have no additional effect on its en...
Definition: Tool.cs:139
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
The server has not found anything matching the Request-URI. No indication is given of whether the con...
The server does not support the functionality required to fulfill the request. This is the appropriat...
static string GenerateRandomCode(int NrBytes)
Generates a random unique code.
bool HasLoginMasterFileName
If a login master file name has been registered
Abstract base class for OAUTH resources.
static bool HasScopePrivileges(string Scopes, IUser User, [NotNullWhen(false)] out string? MissingPrivilege)
Checks if a user has the privileges associated with a set of scopes.
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or ...
Maintains a set of sniffers.
Definition: SnifferSet.cs:12
Implements an in-memory cache.
Definition: Cache.cs:17
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void AddRange(IEnumerable< T > Collection)
Adds a range of elements (last) to the list.
int Count
Number of elements in collection.
Definition: ChunkedList.cs:68
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 managing persistent counters.
static Task< long > IncrementCounter(CaseInsensitiveString Key)
Increments a counter.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:13
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
static object Create(bool ReturnNullIfFail, Type Type, params object[] Arguments)
Returns an instance of the type Type . Creates an instance of a type. If the constructor requires arg...
Definition: Types.cs:1338
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:85
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Definition: Types.cs:1742
Class managing a script expression.
Definition: Expression.cs:41
static bool TryConvert(object Value, Type DesiredType, bool AcceptInformationLoss, out object Result)
Tries to convert an object Value to an object of type DesiredType .
Definition: Expression.cs:5530
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
Collection of variables.
Definition: Variables.cs:25
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string JwtId
Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only...
Definition: JwtClaims.cs:44
const string Subject
Subject of the JWT (the user)
Definition: JwtClaims.cs:19
const string ClientId
Client identifier
Definition: JwtClaims.cs:154
A factory that can create and validate JWT tokens.
Definition: JwtFactory.cs:66
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Definition: JwtFactory.cs:379
Contains information about a Java Web Token (JWT). JWT is defined in RFC 7519: https://tools....
Definition: JwtToken.cs:22
static bool TryParse(string Token, out JwtToken ParsedToken)
Tries to parse a JWT token.
Definition: JwtToken.cs:68
DELETE Interface for HTTP resources.
Interface for JSON-RPC client request objects.
Task ReportResult(object? Result)
Called when a result is received for the request.
object? Tag
Property that can be used to store user-defined data associated with the request.
Type[] Encodes
What types the content block encodes.
Interface for sets of sniffers.
Definition: ISnifferSet.cs:9
Basic interface for a user.
Definition: IUser.cs:7
string UserName
User Name.
Definition: IUser.cs:12
Definition: ImplTypes.g.cs:58
McpRole
Identifies a Role in a Model Context Protocol (MCP) context.
Definition: McpRole.cs:7
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15