5using System.Reflection;
7using System.Threading.Tasks;
46 [OAuthScopesSupported(
true,
"McpScopesSupported")]
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>();
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;
88 TimeSpan.MaxValue, TimeSpan.FromHours(1));
90 Result.Removed += (sender, e) => e.Value.DisposeAsync();
95 private static Dictionary<Type, IContentBlock> GetContentBlocksFirstTime()
97 Types.OnInvalidated += (
_, e) => contentBlocks = GetContentBlocks();
98 return GetContentBlocks();
101 private static Dictionary<Type, IContentBlock> GetContentBlocks()
103 Dictionary<Type, IContentBlock> Result =
new Dictionary<Type, IContentBlock>();
106 foreach (Type T
in ContentBlockTypes)
119 foreach (Type T2
in Encoder.
Encodes)
120 Result[T2] = Encoder;
133 return contentBlocks.TryGetValue(Type, out
ContentBlock);
175 this.description = Description;
183 if (this.Icons.Empty)
186 if (DefaultIcons.Length > 0)
187 this.Icons =
new Icons(DefaultIcons);
192 foreach (
McpScopeRootAttribute ScopeRoot
in this.GetType().GetCustomAttributes<McpScopeRootAttribute>())
195 this.hasScopes = ScopeRoots.Count > 0;
196 this.rootScopes = ScopeRoots.ToArray();
198 int i, j, c = this.rootScopes.Length;
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];
206 for (i = j = 0; i < c; i++)
208 this.toolScopes[i] = this.scopesSupported[j++] = this.rootScopes[i] +
ToolsScopeSuffix;
209 this.promptScopes[i] = this.scopesSupported[j++] = this.rootScopes[i] +
PromptsScopeSuffix;
213 foreach (MethodInfo Method
in this.GetType().GetMethods(BindingFlags.Instance |
214 BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
249 public override string Title => this.title;
282 return this.scopesSupported;
298 if (
string.IsNullOrEmpty(Request.
SubPath))
300 await base.GET(Request, Response);
304 string FormId = Request.
SubPath[1..];
306 if (FormId ==
"UserInput.js")
308 StringBuilder Javascript =
new StringBuilder();
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);");
324 Response.
SetHeader(
"Cache-Control",
"max-age=0, no-cache, no-store");
325 Response.
SetHeader(
"Pragma",
"no-cache");
329 else if (FormId ==
"CloseInput.js")
331 StringBuilder Javascript =
new StringBuilder();
333 Javascript.AppendLine(
"window.close();");
335 Response.
SetHeader(
"Cache-Control",
"max-age=0, no-cache, no-store");
336 Response.
SetHeader(
"Pragma",
"no-cache");
343 ClientRequest.Tag is
null)
345 await Response.
Return(await CloseForm(Response));
349 await Response.
Return(await this.GenerateInputForm(Request, Response,
355 private async Task<HtmlDocument> GenerateInputForm(
HttpRequest Request,
358 StringBuilder Markdown =
new StringBuilder();
360 Markdown.AppendLine(
"Title: User Input");
361 Markdown.AppendLine(
"Description: Form allowing a user to input elicited information.");
362 Markdown.AppendLine(
"Javascript: UserInput.js");
367 Markdown.Append(
"Master: ");
368 Markdown.AppendLine(Environment.LoginMasterFileName);
371 Markdown.Append(
"Date: ");
373 Markdown.AppendLine();
374 Markdown.AppendLine(
new string(
'=', 40));
375 Markdown.AppendLine();
377 Markdown.AppendLine(
"Requested Information");
378 Markdown.AppendLine(
"========================");
379 Markdown.AppendLine();
382 Markdown.AppendLine();
385 new KeyValuePair<string, object>(
JwtClaims.
JwtId, ClientRequest.
Id?.ToString() ??
string.Empty),
389 Markdown.Append(
"<form id='InputForm' action='");
391 Markdown.AppendLine(
"' method='post' enctype='multipart/form-data'>");
392 Markdown.Append(
"<input type='hidden' name='_p_' value='");
394 Markdown.AppendLine(
"'/>");
395 Markdown.AppendLine(
"<input type='hidden' id='_r_' name='_r_' value=''/>");
396 Markdown.AppendLine();
398 Type T = ClientRequest.
Tag!.GetType();
400 Dictionary<string, string> InputAttributes =
new Dictionary<string, string>();
403 StringBuilder
Label =
new StringBuilder();
404 StringBuilder
Input =
new StringBuilder();
407 foreach (MemberInfo MI
in T.GetMembers(BindingFlags.Instance | BindingFlags.Public))
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);
418 InputAttributes.Clear();
421 if (MI.Name ==
"_p_" ||
423 MI.Name.EndsWith(
"_Binary") ||
424 MI.Name.EndsWith(
"_ContentType"))
426 throw new Exception(
"Reserved name: " + MI.Name);
429 InputAttributes[
"id"] = MI.Name;
430 InputAttributes[
"name"] = MI.Name;
432 ?? Value?.ToString() ??
string.Empty;
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)
438 InputAttributes[
"type"] =
"number";
439 InputAttributes[
"step"] =
"any";
441 else if (Value is
bool)
443 InputAttributes.Remove(
"value");
444 InputAttributes[
"type"] =
"checkbox";
446 if (Value is
bool b && b)
447 InputAttributes[
"checked"] =
"checked";
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";
462 InputAttributes[
"type"] =
"text";
469 Label.Append(
"<label for=\"");
470 Label.Append(MI.Name);
472 Label.Append(ParameterInfo?.
Title ?? MI.Name);
473 Label.Append(
"</label>");
475 if (Value is Enum EnumValue)
477 Type EnumType = EnumValue.GetType();
480 if (EnumType.IsDefined(typeof(FlagsAttribute)))
484 InputAttributes.Remove(
"value");
485 Input.Append(
"<select");
487 foreach (KeyValuePair<string, string>
P in InputAttributes)
496 Input.AppendLine(
">");
500 Input.Append(
"<option value=\"");
503 if (
Option.Value.Equals(EnumValue))
504 Input.Append(
"\" selected=\"selected");
508 Input.AppendLine(
"</option>");
511 Input.Append(
"</select>");
514 else if (Value is
string[] ||
517 InputAttributes.Remove(
"value");
518 Input.Append(
"<textarea");
520 foreach (KeyValuePair<string, string>
P in InputAttributes)
531 if (Value is
string[] Rows)
535 foreach (
string Row
in Rows)
546 Input.Append(Value.ToString());
548 Input.AppendLine(
"</textarea>");
552 Input.Append(
"<input");
554 foreach (KeyValuePair<string, string>
P in InputAttributes)
566 Markdown.Append(
"<p>");
571 Markdown.AppendLine(
" ");
580 Markdown.AppendLine(
"</p>");
581 Markdown.AppendLine();
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();
589 return await ReturnHtml(Response, Markdown.ToString());
592 private static async Task<HtmlDocument> CloseForm(
HttpResponse Response)
594 StringBuilder Markdown =
new StringBuilder();
596 Markdown.AppendLine(
"Title: User Input");
597 Markdown.AppendLine(
"Description: Form allowing a user to input elicited information.");
598 Markdown.AppendLine(
"Javascript: CloseInput.js");
603 Markdown.Append(
"Master: ");
604 Markdown.AppendLine(Environment.LoginMasterFileName);
607 Markdown.Append(
"Date: ");
609 Markdown.AppendLine();
610 Markdown.AppendLine(
new string(
'=', 40));
611 Markdown.AppendLine();
613 Markdown.AppendLine(
"Close Form");
614 Markdown.AppendLine(
"=============");
615 Markdown.AppendLine();
617 Markdown.Append(
"You can now safely close the form, if it does not close ");
618 Markdown.AppendLine(
"automatically by itself.");
620 return await ReturnHtml(Response, Markdown.ToString());
623 private static async Task<HtmlDocument> ReturnHtml(
HttpResponse Response,
string Markdown)
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'");
651 if (
string.IsNullOrEmpty(Request.
SubPath))
653 await base.POST(Request, Response);
663 string FormId = Request.
SubPath[1..];
677 if (!(
Content.Decoded is Dictionary<string, object>
Form))
683 if (!
Form.TryGetValue(
"_p_", out
object Obj) ||
684 !(Obj is
string ParametersToken) ||
686 !
this.JwtFactory!.IsValid(ParsedToken) ||
687 ParsedToken.Id != ClientRequest.Id?.ToString() ||
694 if (!
Form.TryGetValue(
"_r_", out Obj) ||
695 !(Obj is
string ResponseString) ||
702 string[] Keys =
new string[
Form.Count];
703 Form.Keys.CopyTo(Keys, 0);
705 foreach (
string Key
in Keys)
707 Form.Remove(Key +
"_Binary");
708 Form.Remove(Key +
"_ContentType");
715 await SetProperties(ClientRequest.
Tag!,
Form);
719 await Response.
Return(await CloseForm(Response));
740 string Name = Method.Name;
742 if (this.tools.ContainsKey(
Name))
743 throw new Exception(
"Tool already registered: " +
Name);
753 this.hasTools =
true;
764 this.RegisterToolNoNotification(Method, Attributes);
766 Dictionary<string, object> Notification =
new Dictionary<string, object>()
768 {
"jsonrpc",
"2.0" },
769 {
"method",
"notifications/tools/list_changed" }
772 return this.SendNotification(
778 if (this.hasSnifferSet)
795 string Name = Method.Name;
797 if (this.prompts.ContainsKey(
Name))
798 throw new Exception(
"Prompt already registered: " +
Name);
806 this.hasPrompts =
true;
817 this.RegisterPromptNoNotification(Method, Attributes);
819 Dictionary<string, object> Notification =
new Dictionary<string, object>()
821 {
"jsonrpc",
"2.0" },
822 {
"method",
"notifications/prompts/list_changed" }
825 return this.SendNotification(
831 if (this.hasSnifferSet)
853 return Array.Empty<
Icon>();
879 base.AddReference(Server);
887 c = this.tools.Count;
889 this.tools.Values.CopyTo(Tools, 0);
894 d = this.prompts.Count;
896 this.prompts.Values.CopyTo(Prompts, 0);
900 Array.Copy(Tools, 0, Methods, 0, c);
901 Array.Copy(Prompts, 0, Methods, c, d);
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.")]
925 [JsonRpcDocumentation(
"Protocol Version")]
926 string ProtocolVersion,
928 [JsonRpcDocumentation(
"Client capabilities")]
929 Dictionary<string, object> Capabilities,
931 [JsonRpcDocumentation(
"Client information")]
932 Dictionary<string, object> ClientInfo)
935 CapabilitiesParsed =
null;
938 ClientInfoParsed =
null;
940 string RemoteEndpoint = Request.
RemoteEndPoint.RemovePortNumber();
954 while (sessions.ContainsKey(SessionId));
957 CapabilitiesParsed, ClientInfoParsed, RemoteEndpoint, this.snifferSet);
960 Response.
SetHeader(
"MCP-Session-Id", SessionId);
962 if (this.hasSnifferSet)
964 StringBuilder sb =
new StringBuilder();
966 sb.Append(this.
Name);
967 sb.Append(
".Initialize(");
968 sb.Append(ProtocolVersion);
975 Session.ReceiveText(sb.ToString());
978 Dictionary<string, object> ServerCapabilities =
new Dictionary<string, object>();
1002 if (this.hasPrompts)
1004 ServerCapabilities[
"prompts"] =
new Dictionary<string, object>()
1006 {
"listChanged",
true }
1012 ServerCapabilities[
"tools"] =
new Dictionary<string, object>()
1014 {
"listChanged",
true }
1020 ServerCapabilities[
"resources"] =
new Dictionary<string, object>()
1022 {
"subscribe",
true },
1023 {
"listChanged",
true }
1027 string? WebSite = this.
WebSiteUri?.ToString();
1028 if (
string.IsNullOrEmpty(WebSite))
1031 Dictionary<string, object> Result =
new Dictionary<string, object>()
1033 {
"protocolVersion",
"2025-11-25" },
1034 {
"capabilities", ServerCapabilities },
1035 {
"serverInfo",
new Dictionary<string,object>()
1037 {
"name", this.Name },
1038 {
"title", this.title },
1039 {
"version", this.Version },
1040 {
"description", this.description },
1042 {
"websiteUrl", WebSite }
1045 {
"instructions", this.Instructions }
1048 if (this.hasSnifferSet)
1060 [JsonRpcDocumentation(
"Notification that the client has completed its " +
1062 [JsonRpcDocName(
"notifications/initialized")]
1070 if (this.hasSnifferSet)
1071 Session.ReceiveText(this.
Name +
".Initialized()");
1076 Response.StatusCode = 202;
1077 Response.StatusMessage =
"Accepted";
1107 string SessionId = SessionHeader.Value;
1147 [JsonRpcDocumentation(
"Lists available MCP server tools.")]
1148 [JsonRpcDocName(
"tools/list")]
1149 [
return: JsonRpcDocumentation(
"Dictionary containing the list of tools.")]
1153 [JsonRpcDocumentation(
"Cursor for pagination.")]
1154 string? Cursor =
null)
1160 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1164 if (this.hasSnifferSet)
1166 StringBuilder sb =
new StringBuilder();
1168 sb.Append(this.
Name);
1169 sb.Append(
".tools/list(");
1173 Session.ReceiveText(sb.ToString());
1177 int MaxCount = PageSize;
1179 if (!
string.IsNullOrEmpty(Cursor))
1181 if (!
int.TryParse(Cursor, out Offset) || Offset < 0)
1183 if (!this.hasSnifferSet)
1184 Session.Error(
"Invalid cursor: " + Cursor);
1192 int Next = Offset + MaxCount;
1194 Dictionary<string, object> Result =
new Dictionary<string, object>();
1198 foreach (
Tool Tool in this.tools.Values)
1203 if (!this.CheckScopes(User, this.toolScopes, out
_))
1208 Result[
"nextCursor"] = Next.ToString();
1224 int c = Tools.
Count;
1226 Dictionary<string, object>[] ToolsJson =
new Dictionary<string, object>[c];
1231 Result[
"tools"] = ToolsJson;
1233 if (this.hasSnifferSet)
1239 private bool CheckScopes(
IUser? User,
string[] Scopes, out
string? MissingPrivilege)
1241 if (!this.hasScopes)
1243 MissingPrivilege =
null;
1249 MissingPrivilege =
null;
1256 private async Task<IUser?> GetAuthenticatedUser(
HttpRequest Request,
1270 if (this.hasSnifferSet)
1271 Session.Error(
"Access denied. No authentication schemes available.");
1288 if (!(User is
null))
1290 Request.User = User;
1297 List<string> Challenges =
new List<string>();
1309 Challenges.Add(Challenge);
1313 Challenges.ToArray()));
1315 if (this.hasSnifferSet)
1316 Session.Error(
"Access denied. Unauthorized.");
1345 [JsonRpcDocumentation(
"Calls an MCP server tool.")]
1346 [JsonRpcDocName(
"tools/call")]
1347 [
return: JsonRpcDocumentation(
"Dictionary containing the result of the tool call.")]
1351 [JsonRpcDocumentation(
"Name of the tool to call.")]
1354 [JsonRpcDocumentation(
"Arguments for the tool.")]
1355 Dictionary<string, object?> Arguments,
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,
1364 [JsonRpcMetaDataArgument]
1365 [JsonRpcDocumentation(
"Associated meta-data, if available.")]
1366 object? _Meta =
null)
1372 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1376 if (this.hasSnifferSet)
1378 StringBuilder sb =
new StringBuilder();
1380 sb.Append(this.Name);
1381 sb.Append(
".tools/call(");
1386 if (!(Task is
null))
1392 if (!(_Meta is
null))
1400 Session.ReceiveText(sb.ToString());
1403 Dictionary<string, object?> Result =
new Dictionary<string, object?>();
1410 if (this.hasSnifferSet)
1418 !
this.CheckScopes(User,
this.toolScopes, out MissingPrivilege))
1420 if (this.hasSnifferSet)
1421 Session.Error(
"Access denied. Missing privilege: " + MissingPrivilege);
1424 User?.
UserName ??
string.Empty, MissingPrivilege ??
string.Empty));
1431 Dictionary<string, object?>? MetaData = _Meta as Dictionary<string, object?>;
1434 out
string?
Reason, out
object?[]? Arguments2))
1441 if (this.hasSnifferSet)
1445 Result[
"isError"] =
true;
1448 catch (Exception ex)
1450 if (this.hasSnifferSet)
1454 Result[
"isError"] =
true;
1457 if (ToolResult is
null)
1458 Result[
"content"] = Array.Empty<
object>();
1459 else if (ToolResult is Dictionary<string, object> StructuredContent)
1461 Result[
"content"] =
new object[]
1463 new Dictionary<string, object>()
1466 {
"text",
JSON.
Encode(StructuredContent,
false) }
1469 Result[
"structuredContent"] =
new Dictionary<string, object>()
1471 {
"result", StructuredContent }
1476 Type T = ToolResult.GetType();
1478 if (contentBlocks.TryGetValue(T, out
IContentBlock Encoder))
1480 if (Encoder.IsStructuredContent)
1482 StructuredContent = await Encoder.Encode(ToolResult);
1484 Result[
"content"] =
new object[]
1486 new Dictionary<string, object>()
1489 {
"text",
JSON.
Encode(StructuredContent,
false) }
1492 Result[
"structuredContent"] =
new Dictionary<string, object>()
1494 {
"result", StructuredContent }
1498 Result[
"content"] =
new object[] { await Encoder.Encode(ToolResult) };
1500 else if (T.IsArray && ToolResult is IEnumerable Enumerable)
1503 IEnumerator e = Enumerable.GetEnumerator();
1505 while (e.MoveNext())
1507 object? Item = e.Current;
1511 Type T2 = Item.GetType();
1512 if (!contentBlocks.TryGetValue(T2, out
IContentBlock Encoder2))
1513 Encoder2 = defaultObjectEncoder;
1515 Content.Add(await Encoder2.Encode(Item));
1518 Result[
"content"] =
Content.ToArray();
1522 StructuredContent = await defaultObjectEncoder.Encode(ToolResult);
1524 Result[
"content"] =
new object[]
1526 new Dictionary<string, object>()
1529 {
"text",
JSON.
Encode(StructuredContent,
false) }
1532 Result[
"structuredContent"] =
new Dictionary<string, object>()
1534 {
"result", StructuredContent }
1539 if (this.hasSnifferSet)
1553 [JsonRpcDocumentation(
"Lists available MCP server prompts.")]
1554 [JsonRpcDocName(
"prompts/list")]
1555 [
return: JsonRpcDocumentation(
"Dictionary containing the list of prompts.")]
1559 [JsonRpcDocumentation(
"Cursor for pagination.")]
1560 string? Cursor =
null)
1566 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1570 if (this.hasSnifferSet)
1572 StringBuilder sb =
new StringBuilder();
1574 sb.Append(this.Name);
1575 sb.Append(
".prompts/list(");
1579 Session.ReceiveText(sb.ToString());
1583 int MaxCount = PageSize;
1585 if (!
string.IsNullOrEmpty(Cursor))
1587 if (!
int.TryParse(Cursor, out Offset) || Offset < 0)
1589 if (!this.hasSnifferSet)
1590 Session.Error(
"Invalid cursor: " + Cursor);
1598 int Next = Offset + MaxCount;
1600 Dictionary<string, object> Result =
new Dictionary<string, object>();
1609 if (!this.CheckScopes(User, this.promptScopes, out
_))
1614 Result[
"nextCursor"] = Next.ToString();
1630 int c = Prompts.
Count;
1632 Dictionary<string, object>[] PromptsJson =
new Dictionary<string, object>[c];
1637 Result[
"prompts"] = PromptsJson;
1639 if (this.hasSnifferSet)
1656 [JsonRpcDocumentation(
"Gets an MCP server prompt.")]
1657 [JsonRpcDocName(
"prompts/get")]
1658 [
return: JsonRpcDocumentation(
"Dictionary containing the prompt.")]
1662 [JsonRpcDocumentation(
"Name of the prompt to call.")]
1665 [JsonRpcDocumentation(
"Arguments for the prompt.")]
1666 Dictionary<string, object?> Arguments,
1668 [JsonRpcMetaDataArgument]
1669 [JsonRpcDocumentation(
"Associated meta-data, if available.")]
1670 object? _Meta =
null)
1676 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1680 if (this.hasSnifferSet)
1682 StringBuilder sb =
new StringBuilder();
1684 sb.Append(this.Name);
1685 sb.Append(
".prompts/get(");
1690 if (!(_Meta is
null))
1698 Session.ReceiveText(sb.ToString());
1701 Dictionary<string, object?> Result =
new Dictionary<string, object?>();
1702 object? PromptResult;
1708 if (this.hasSnifferSet)
1716 !
this.CheckScopes(User,
this.promptScopes, out MissingPrivilege))
1718 if (this.hasSnifferSet)
1719 Session.Error(
"Access denied. Missing privilege: " + MissingPrivilege);
1722 User?.
UserName ??
string.Empty, MissingPrivilege ??
string.Empty));
1729 Dictionary<string, object?>? MetaData = _Meta as Dictionary<string, object?>;
1732 out
string?
Reason, out
object?[]? Arguments2))
1739 if (this.hasSnifferSet)
1743 Result[
"isError"] =
true;
1748 catch (Exception ex)
1750 if (this.hasSnifferSet)
1754 Result[
"isError"] =
true;
1759 if (!(PromptResult is
null))
1763 else if (PromptResult is IEnumerable<PromptMessage> PromptMessages)
1767 Type T = PromptResult.GetType();
1769 if (contentBlocks.TryGetValue(T, out
IContentBlock Encoder))
1772 await Encoder.Encode(PromptResult)));
1774 else if (T.IsArray && PromptResult is IEnumerable Enumerable)
1776 IEnumerator e = Enumerable.GetEnumerator();
1778 while (e.MoveNext())
1780 object? Item = e.Current;
1785 Messages.
Add(PromptMessage2);
1786 else if (e.Current is IEnumerable<PromptMessage> PromptMessages2)
1787 Messages.
AddRange(PromptMessages2);
1798 int c = Messages.
Count;
1799 Dictionary<string, object>[] EncodedMessages =
new Dictionary<string, object>[c];
1803 Dictionary<string, object>
Content;
1809 Type T = Message.
Content.GetType();
1810 if (!contentBlocks.TryGetValue(T, out
IContentBlock Encoder))
1811 Encoder = defaultObjectEncoder;
1816 EncodedMessages[i++] =
new Dictionary<string, object>()
1818 {
"role", Message.
Role.ToString().ToLower() },
1823 Result[
"messages"] = EncodedMessages;
1825 if (this.hasSnifferSet)
1845 [JsonRpcDocumentation(
"Lists available MCP server resources.")]
1846 [JsonRpcDocName(
"resources/list")]
1847 [
return: JsonRpcDocumentation(
"Dictionary containing the list of resources.")]
1851 [JsonRpcDocumentation(
"Cursor for pagination.")]
1852 string? Cursor =
null)
1858 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1862 if (this.hasSnifferSet)
1864 StringBuilder sb =
new StringBuilder();
1866 sb.Append(this.Name);
1867 sb.Append(
".resources/list(");
1871 Session.ReceiveText(sb.ToString());
1875 int MaxCount = PageSize;
1877 if (!
string.IsNullOrEmpty(Cursor))
1879 if (!
int.TryParse(Cursor, out Offset) || Offset < 0)
1881 if (!this.hasSnifferSet)
1882 Session.Error(
"Invalid cursor: " + Cursor);
1891 Dictionary<string, object> Result =
new Dictionary<string, object>();
1892 int Next = Offset + MaxCount;
1899 if (!this.CheckScopes(User, this.resourceScopes, out
_))
1904 Result[
"nextCursor"] = Next.ToString();
1921 Dictionary<string, object>[] ResourcesJson =
new Dictionary<string, object>[c];
1926 Result[
"resources"] = ResourcesJson;
1928 if (this.hasSnifferSet)
1943 [JsonRpcDocumentation(
"Reads an MCP server resource.")]
1944 [JsonRpcDocName(
"resources/read")]
1945 [
return: JsonRpcDocumentation(
"Dictionary containing the contents of the resource.")]
1949 [JsonRpcDocumentation(
"URI of the resource to read.")]
1952 [JsonRpcMetaDataArgument]
1953 [JsonRpcDocumentation(
"Associated meta-data, if available.")]
1954 object? _Meta =
null)
1960 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
1964 if (this.hasSnifferSet)
1966 StringBuilder sb =
new StringBuilder();
1968 sb.Append(this.Name);
1969 sb.Append(
".resources/read(");
1972 if (!(_Meta is
null))
1980 Session.ReceiveText(sb.ToString());
1987 if (this.hasSnifferSet)
1988 Session.Error(
"Resource not found: " + Uri);
1995 !
this.CheckScopes(User,
this.resourceScopes, out MissingPrivilege))
1997 if (this.hasSnifferSet)
1998 Session.Error(
"Access denied. Missing privilege: " + MissingPrivilege);
2001 User?.
UserName ??
string.Empty, MissingPrivilege ??
string.Empty));
2008 Dictionary<string, object>? MetaData = _Meta as Dictionary<string, object>;
2013 Dictionary<string, object>[] Contents =
new Dictionary<string, object>[c];
2015 for (i = 0; i < c; i++)
2016 Contents[i] =
Content[i].Encode();
2018 Dictionary<string, object> Result =
new Dictionary<string, object>()
2020 {
"contents",Contents }
2023 if (this.hasSnifferSet)
2036 [JsonRpcDocumentation(
"Subscribes to an MCP server resource.")]
2037 [JsonRpcDocName(
"resources/subscribe")]
2041 [JsonRpcDocumentation(
"URI of the resource to subscribe to.")]
2048 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
2052 if (this.hasSnifferSet)
2054 StringBuilder sb =
new StringBuilder();
2056 sb.Append(this.Name);
2057 sb.Append(
".resources/subscribe(");
2061 Session.ReceiveText(sb.ToString());
2068 if (this.hasSnifferSet)
2069 Session.Error(
"Resource not found: " + Uri);
2076 !
this.CheckScopes(User,
this.resourceScopes, out MissingPrivilege))
2078 if (this.hasSnifferSet)
2079 Session.Error(
"Access denied. Missing privilege: " + MissingPrivilege);
2082 User?.
UserName ??
string.Empty, MissingPrivilege ??
string.Empty));
2088 if (this.hasSnifferSet)
2091 Session.Information(
"Subscription to resource successful: " + Uri);
2093 Session.Information(
"Subscription already exists for: " + Uri);
2104 [JsonRpcDocumentation(
"Unsubscribes from an MCP server resource.")]
2105 [JsonRpcDocName(
"resources/unsubscribe")]
2109 [JsonRpcDocumentation(
"URI of the resource to unsubscribe from.")]
2116 IUser? User = await this.GetAuthenticatedUser(Request, Response,
Session);
2120 if (this.hasSnifferSet)
2122 StringBuilder sb =
new StringBuilder();
2124 sb.Append(this.Name);
2125 sb.Append(
".resources/unsubscribe(");
2129 Session.ReceiveText(sb.ToString());
2136 if (this.hasSnifferSet)
2137 Session.Error(
"Resource not found: " + Uri);
2144 !
this.CheckScopes(User,
this.resourceScopes, out MissingPrivilege))
2146 if (this.hasSnifferSet)
2147 Session.Error(
"Access denied. Missing privilege: " + MissingPrivilege);
2150 User?.
UserName ??
string.Empty, MissingPrivilege ??
string.Empty));
2156 if (this.hasSnifferSet)
2159 Session.Information(
"Unsubscription from resource successful: " + Uri);
2161 Session.Information(
"No subscription found for: " + Uri);
2175 return Task.FromResult(Array.Empty<
Resource>());
2190 Array.Empty<KeyValuePair<bool, string>>();
2203 return Task.FromResult<
Resource?>(
null);
2217 Dictionary<string, object> Notification =
new Dictionary<string, object>()
2219 {
"jsonrpc",
"2.0" },
2220 {
"method",
"notifications/resources/list_changed" }
2223 await this.SendNotification(
2229 if (this.hasSnifferSet)
2236 catch (Exception ex)
2242 private Task SendNotification(Predicate<IJsonRpcSession?> Filter,
2243 Dictionary<string, object> Notification)
2247 new KeyValuePair<string, object>(
"event",
"message"),
2248 new KeyValuePair<string, object>(
"data",
JSON.
Encode(Notification,
false)));
2260 Dictionary<string, object> Notification =
new Dictionary<string, object>()
2262 {
"jsonrpc",
"2.0" },
2263 {
"method",
"notifications/resources/updated" },
2264 {
"params",
new Dictionary<string, object>()
2266 {
"uri", Uri.OriginalString }
2271 string s = Uri.ToString();
2273 await this.SendNotification(
2279 if (!McpSession.IsSubscribed(s))
2282 if (this.hasSnifferSet)
2289 catch (Exception ex)
2311 Response.StatusCode = 204;
2312 Response.StatusMessage =
"No Content";
2320 "Terminating session.");
2333 Parameter.AdditionalDocumentation ??= GetAdditionalParameterDocumentation(Parameter);
2334 return base.GetParameterDocumentation(Parameter);
2342 ICustomAttributeProvider Member)
2346 foreach (
object Attribute
in
2352 PropertyDoc.
Add(
new KeyValuePair<bool, string>(
2353 true, TypedAttribute.AnnotatedDescription));
2357 StringBuilder? Values =
null;
2359 foreach (
object Attribute
in
2366 Values =
new StringBuilder();
2367 Values.AppendLine(
"Possible values:");
2368 Values.AppendLine();
2371 Values.Append(
"* `\"");
2372 Values.Append(TypedAttribute.Value.ToString());
2373 Values.Append(
"\"` - ");
2374 Values.AppendLine(TypedAttribute.Title);
2378 if (!(Values is
null))
2381 PropertyDoc.
Add(
new KeyValuePair<bool, string>(
true, Values.ToString()));
2384 return (PropertyDoc?.ToArray() ?? Array.Empty<KeyValuePair<bool, string>>()).Join(
2385 base.GetMemberDocumentation(Member));
2393 private static KeyValuePair<bool, string>[] GetAdditionalParameterDocumentation(
2398 foreach (
object Attribute
in Parameter.
Parameter.
2404 Result.
Add(
new KeyValuePair<bool, string>(
true,
2409 StringBuilder? Values =
null;
2411 foreach (
object Attribute
in Parameter.
Parameter.
2418 Values =
new StringBuilder();
2419 Values.AppendLine(
"Possible values:");
2420 Values.AppendLine();
2423 Values.Append(
"* `\"");
2425 Values.Append(
"\"` - ");
2430 if (!(Values is
null))
2433 Result.
Add(
new KeyValuePair<bool, string>(
true, Values.ToString()));
2436 return Result?.
ToArray() ?? Array.Empty<KeyValuePair<bool, string>>();
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: `");
2458 Markdown.AppendLine(
"`");
2459 Markdown.AppendLine();
2463 Markdown.AppendLine(
"Scopes supported:");
2464 Markdown.AppendLine();
2466 foreach (
string Scope
in this.scopesSupported)
2468 Markdown.Append(
"* `");
2469 Markdown.Append(Scope);
2470 Markdown.AppendLine(
"`");
2473 Markdown.AppendLine();
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();
2488 if (this.hasPrompts)
2494 if (this.HasResources)
2497 await base.GenerateDocumentationApiDescription(Notes, TypesToDocument,
2509 HashSet<Type> TypesToDocument,
HttpRequest Request, StringBuilder Markdown)
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();
2525 Tools =
new Tool[this.tools.Count];
2526 this.tools.Values.CopyTo(Tools, 0);
2531 Markdown.AppendLine(
"<section>");
2532 Markdown.AppendLine();
2533 Markdown.Append(
"### ");
2535 Markdown.AppendLine();
2538 Markdown.AppendLine();
2540 Markdown.AppendLine(
"| Properties ||");
2541 Markdown.AppendLine(
"|:-------|:------:|");
2542 Markdown.Append(
"| Can Modify: | ");
2544 Markdown.AppendLine(
" |");
2545 Markdown.Append(
"| Can Destroy: | ");
2547 Markdown.AppendLine(
" |");
2548 Markdown.Append(
"| Is Idempotent: | ");
2550 Markdown.AppendLine(
" |");
2551 Markdown.Append(
"| Open World Access: | ");
2553 Markdown.AppendLine(
" |");
2554 Markdown.AppendLine();
2559 return Task.CompletedTask;
2570 HashSet<Type> TypesToDocument,
HttpRequest Request, StringBuilder Markdown)
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();
2586 Prompts =
new Prompt[this.prompts.Count];
2587 this.prompts.Values.CopyTo(Prompts, 0);
2592 Markdown.AppendLine(
"<section>");
2593 Markdown.AppendLine();
2594 Markdown.Append(
"### ");
2596 Markdown.AppendLine();
2599 Markdown.AppendLine();
2604 return Task.CompletedTask;
2616 Markdown.AppendLine(
new string(
'=', 80));
2617 Markdown.AppendLine();
2618 Markdown.AppendLine(
"MCP Server Resources");
2619 Markdown.AppendLine(
"-----------------------");
2620 Markdown.AppendLine();
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();
2628 return Task.CompletedTask;
2645 string Message, T InputRequest,
bool Sensitive,
Session Session,
int Timeout)
2651 Type InputType = typeof(T);
2653 IEnumerable<McpEnumValueAttribute> EnumValues = InputType.GetCustomAttributes<
McpEnumValueAttribute>();
2654 object InputSchema =
Tool.GenerateSchema(InputType,
true, InputRequest,
2655 ParameterInfo, EnumValues);
2656 Dictionary<string, object?> ElicitationRequest;
2660 ElicitationRequest =
new Dictionary<string, object?>()
2663 {
"message", Message },
2664 {
"requestedSchema", InputSchema }
2669 ElicitationRequest =
new Dictionary<string, object?>()
2672 {
"message", Message }
2679 Message,
"elicitation/create", ElicitationRequest,
Session,
2682 if (!(Result is Dictionary<string, object> ResultObj))
2685 if (!ResultObj.TryGetValue(
"action", out
object Obj) ||
2686 !(Obj is
string Action))
2688 throw new BadRequestException(
"Expected action.");
2693 case "decline":
return false;
2694 case "cancel":
return null;
2697 if (!ResultObj.TryGetValue(
"content", out Obj))
2700 if (!(Obj is Dictionary<string, object> Properties))
2703 await SetProperties(InputRequest, Properties);
2708 throw new Exception(
"Unexpected action: " + Action);
2713 Request.Tag = InputRequest;
2719 ElicitationRequest[
"elicitationId"] = Request.Id;
2720 ElicitationRequest[
"url"] = Url;
2722 async Task Completed(
object _, EventArgs e)
2724 Dictionary<string, object> Notification =
new Dictionary<string, object>()
2726 {
"jsonrpc",
"2.0" },
2727 {
"method",
"notifications/elicitation/complete" },
2728 {
"params",
new Dictionary<string, object?>()
2730 {
"elicitationId", Request.Id }
2735 await this.SendNotification(
2741 Session2.TransmitText(
JSON.
Encode(Notification,
false));
2748 Request.ResultReturned += Completed;
2749 Request.ErrorReturned += Completed;
2750 Request.Cancelled += Completed;
2753 await Request.SendRequest();
2754 return await Request.WaitForResultAsync(Timeout);
2757 private void Request_ResultReturned(
object sender, EventArgs e)
2759 throw new System.NotImplementedException();
2762 internal static async Task SetProperties(
object Object, Dictionary<string, object> Properties)
2764 Type T =
Object.GetType();
2766 foreach (KeyValuePair<string, object>
P in Properties)
2768 object Value =
P.Value;
2769 Dictionary<string, object>? SubProperties = Value as Dictionary<string, object>;
2770 bool IsSubProperties = !(SubProperties is
null);
2772 FieldInfo? FI = T.GetField(
P.Key, BindingFlags.Public | BindingFlags.Instance);
2775 if (IsSubProperties)
2777 object? Item = FI.GetValue(
Object);
2782 FI.SetValue(
Object, Item);
2785 await SetProperties(Item, SubProperties!);
2787 else if (Value is
null || FI.FieldType.IsAssignableFrom(Value.GetType()))
2788 FI.SetValue(
Object, Value);
2790 FI.SetValue(
Object, Value2);
2793 throw new InvalidCastException(
"Unable to convert value of type " +
2794 Value.GetType().FullName +
" to " +
2795 FI.FieldType.FullName +
".");
2801 PropertyInfo? PI = T.GetProperty(
P.Key, BindingFlags.Public | BindingFlags.Instance);
2804 if (IsSubProperties)
2806 object? Item = PI.GetValue(
Object);
2811 PI.SetValue(
Object, Item);
2814 await SetProperties(Item, SubProperties!);
2816 else if (Value is
null || PI.PropertyType.IsAssignableFrom(Value.GetType()))
2817 PI.SetValue(
Object, Value);
2819 PI.SetValue(
Object, Value2);
2822 throw new InvalidCastException(
"Unable to convert value of type " +
2823 Value.GetType().FullName +
" to " +
2824 PI.PropertyType.FullName +
".");
2830 throw new InvalidOperationException(
"Unrecognized field ro property name: " +
P.Key);
Helps with parsing of commong data types.
static readonly char[] CRLF
Contains the CR LF character sequence.
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Contains information about a response to a content request.
override string ToString()
Encapsulates a JavaScript Document
const string ContentTypeIcon
image/x-icon
Helps with common JSON-related tasks.
static string Encode(string s)
Encodes a string for inclusion in JSON.
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.
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
static string HtmlAttributeEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe.
Static class managing the application event log. Applications and services log events on this static ...
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.
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.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
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.
Represents an HTTP request.
HttpRequestHeader Header
Request header.
string RemoteEndPoint
Remote end-point.
bool HasData
If the request has data.
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
bool Encrypted
If the connection is encrypted or not.
string SubPath
Sub-path. If a resource is found handling the request, this property contains the trailing sub-path o...
IUser User
Authenticated user, if available, or null if not available.
int CipherStrength
Cipher strength
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
SessionVariables GetSessionFromCookie()
Gets the session variables from the cookie, if available.
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
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.
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.
ParameterInfo Parameter
Parameter information.
Information about a protected method.
bool IsAuthorized(IUser? User)
Checks if a user is authorized to call the method.
MethodInfo Method
Method information.
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.
Icons Icons
Icons of server.
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".
string Name
Name of server.
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.
Uri? WebSiteUri
Website URI of server.
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...
string Instructions
Instructions for server.
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.
string Version
Version of server.
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.
override string Title
Title of MCP server.
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.
Provides a title to an enumeration value.
string? Title
Title of enumeration value.
Enum Value
Enumeration value
Provides meta-data about a parameter.
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.
Provides meta-data about a masked string-valued parameter.
Defines a scope root for an MCP server web resource.
string ScopeRoot
Scope root for the MCP server.
Defines a method in an HttpMcpServerResource implementation as a recipient of an MCP Server Prompt in...
string Description
A human-readable description of the prompt.
string Title
A human-readable title for the prompt.
string IconsMethod
Name of method that returns an Icon?, an an Icon[]? or an Icons? resource representing the prompt....
Capabilities of the client.
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.
Describes the MCP implementation.
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.
An optionally-sized icon that can be displayed in a user interface.
Base interface to add icons property.
Dictionary< string, object >[] ToJson()
Converts object to a generic representation.
Describes a message returned as part of a prompt.
Dictionary< string, object >? Encoded
Encoded content.
McpRole Role
Role of recipient of message.
object Content
Message content.
bool IsEncoded
If the content has been encoded.
Contains information about an MCP Server Prompt
string Title
A human-readable title for the prompt.
McpParameterAttribute? ReturnAttributes
Any MCP attributes declared for the return value.
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.
async Task< Dictionary< string, object > > ToJson(HttpMcpServerResource Resource)
Converts object to a generic representation.
string Description
A human-readable description of the prompt.
Contains information about an MCP Server Resource
bool IsAuthorized(IUser? User, [NotNullWhen(false)] out string? MissingPrivilege)
Checks if a user is authorized to call the method.
abstract Task< IResourceContent[]> Read(Dictionary< string, object >? MetaData)
Reads the resource.
string Name
Name of resource.
Dictionary< string, object > ToJson()
Converts object to a generic representation.
string SessionId
MCP Session ID
bool Unsubscribe(string Uri)
Unsubscribes from a resource.
string UserName
User name used for session.
void TransmitText(string Text)
Text has been transmitted to the client.
string RemoteEndpoint
Client remote endpoint.
bool Subscribe(string Uri)
Subscribes to a resource.
bool IsAuthenticated
If client has been authenticated in the session.
ClientCapabilities? ClientCapabilities
Client capabilities, if available.
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...
Manages the OAuth 2 environment.
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.
Implements an in-memory cache.
A chunked list is a linked list of chunks of objects of type T .
void AddRange(IEnumerable< T > Collection)
Adds a range of elements (last) to the list.
int Count
Number of elements in collection.
void Add(T Item)
Adds an item to the collection.
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.
Static class that dynamically manages types and interfaces available in the runtime environment.
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
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...
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Class managing a script expression.
static bool TryConvert(object Value, Type DesiredType, bool AcceptInformationLoss, out object Result)
Tries to convert an object Value to an object of type DesiredType .
Base class for all nodes in a parsed script tree.
static async Task< object > WaitPossibleTask(object Result)
Waits for any asynchronous process to terminate.
Static class containing predefined JWT claim names.
const string JwtId
Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only...
const string Subject
Subject of the JWT (the user)
const string ClientId
Client identifier
A factory that can create and validate JWT tokens.
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Contains information about a Java Web Token (JWT). JWT is defined in RFC 7519: https://tools....
static bool TryParse(string Token, out JwtToken ParsedToken)
Tries to parse a JWT token.
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.
string Message
Message to user.
object? Tag
Property that can be used to store user-defined data associated with the request.
Interface for MCP Content Block.
Type[] Encodes
What types the content block encodes.
Interface for MCP Resource Content.
Interface for sets of sniffers.
Basic interface for a user.
string UserName
User Name.
McpRole
Identifies a Role in a Model Context Protocol (MCP) context.
Reason
Reason a token is not valid.