// Copyright (c) Microsoft. All rights reserved. using System.Text.RegularExpressions; using Microsoft.SemanticKernel; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; namespace MCPServer.Resources; /// /// Represents a resource template definition. /// public sealed class ResourceTemplateDefinition { /// /// The regular expression to match the resource template. /// private Regex? _regex = null; /// /// The kernel function to invoke the resource template handler. /// private KernelFunction? _kernelFunction = null; /// /// Gets or sets the MCP resource template. /// public required ResourceTemplate ResourceTemplate { get; init; } /// /// Gets or sets the handler for the MCP resource template. /// public required Delegate Handler { get; init; } /// /// Gets or sets the kernel instance to invoke the resource template handler. /// If not provided, an instance registered in DI container will be used. /// public Kernel? Kernel { get; set; } /// /// Checks if the given Uri matches the resource template. /// /// The Uri to check for match. public bool IsMatch(string uri) { return this.GetRegex().IsMatch(uri); } /// /// Invokes the resource template handler. /// /// The MCP server context. /// The cancellation token. /// The result of the invocation. public async ValueTask InvokeHandlerAsync(RequestContext context, CancellationToken cancellationToken) { this._kernelFunction ??= KernelFunctionFactory.CreateFromMethod(this.Handler); this.Kernel ??= context.Server.Services?.GetRequiredService() ?? throw new InvalidOperationException("Kernel is not available."); KernelArguments args = new(source: this.GetArguments(context.Params!.Uri!)) { { "context", context }, }; FunctionResult result = await this._kernelFunction.InvokeAsync(kernel: this.Kernel, arguments: args, cancellationToken: cancellationToken); return result.GetValue() ?? throw new InvalidOperationException("The handler did not return a valid result."); } private Regex GetRegex() { if (this._regex != null) { return this._regex; } var pattern = "^" + Regex.Escape(this.ResourceTemplate.UriTemplate) .Replace("\\{", "(?<") .Replace("}", ">[^/]+)") + "$"; return this._regex = new(pattern, RegexOptions.Compiled); } private Dictionary GetArguments(string uri) { var match = this.GetRegex().Match(uri); if (!match.Success) { throw new ArgumentException($"The uri '{uri}' does not match the template '{this.ResourceTemplate.UriTemplate}'."); } return match.Groups.Cast().Where(g => g.Name != "0").ToDictionary(g => g.Name, g => (object?)g.Value); } }