using System.Text.Json; using System.Text.RegularExpressions; using ColaFlow.Modules.Mcp.Application.Services; using ColaFlow.Modules.Mcp.Contracts.Resources; using ColaFlow.Modules.Mcp.Domain.Exceptions; using Microsoft.Extensions.Logging; namespace ColaFlow.Modules.Mcp.Application.Handlers; /// /// Handler for the 'resources/read' MCP method /// Uses scoped IMcpResource instances from DI to avoid DbContext disposal issues /// public class ResourcesReadMethodHandler( ILogger logger, IMcpResourceRegistry resourceRegistry, IEnumerable scopedResources) : IMcpMethodHandler { public string MethodName => "resources/read"; public async Task HandleAsync(object? @params, CancellationToken cancellationToken) { logger.LogDebug("Handling resources/read request"); // Parse parameters var paramsJson = JsonSerializer.Serialize(@params); var request = JsonSerializer.Deserialize(paramsJson); if (request == null || string.IsNullOrWhiteSpace(request.Uri)) { throw new McpInvalidParamsException("Missing required parameter: uri"); } logger.LogInformation("Reading resource: {Uri}", request.Uri); // Find resource descriptor from registry (for URI template matching) var registryResource = resourceRegistry.GetResourceByUri(request.Uri); if (registryResource == null) { throw new McpNotFoundException($"Resource not found: {request.Uri}"); } // Get the scoped resource instance from DI (fresh DbContext) var resource = scopedResources.FirstOrDefault(r => r.Uri == registryResource.Uri); if (resource == null) { throw new McpNotFoundException($"Resource implementation not found: {registryResource.Uri}"); } // Parse URI and extract parameters var resourceRequest = ParseResourceRequest(request.Uri, resource.Uri); // Get resource content var content = await resource.GetContentAsync(resourceRequest, cancellationToken); // Return MCP response var response = new { contents = new[] { new { uri = content.Uri, mimeType = content.MimeType, text = content.Text } } }; return response; } /// /// Parse resource URI and extract path/query parameters /// private McpResourceRequest ParseResourceRequest(string requestUri, string templateUri) { var request = new McpResourceRequest { Uri = requestUri }; // Split URI and query string var uriParts = requestUri.Split('?', 2); var path = uriParts[0]; var queryString = uriParts.Length > 1 ? uriParts[1] : string.Empty; // Extract path parameters from template // Example: "colaflow://projects.get/123" with template "colaflow://projects.get/{id}" var pattern = "^" + Regex.Escape(templateUri) .Replace(@"\{", "{") .Replace(@"\}", "}") .Replace("{id}", @"(?[^/]+)") .Replace("{projectId}", @"(?[^/]+)") + "$"; var match = Regex.Match(path, pattern); if (match.Success) { foreach (Group group in match.Groups) { if (!int.TryParse(group.Name, out _) && group.Name != "0") { request.UriParams[group.Name] = group.Value; } } } // Parse query parameters if (!string.IsNullOrEmpty(queryString)) { var queryPairs = queryString.Split('&'); foreach (var pair in queryPairs) { var keyValue = pair.Split('=', 2); if (keyValue.Length == 2) { request.QueryParams[keyValue[0]] = Uri.UnescapeDataString(keyValue[1]); } } } return request; } private class ResourceReadParams { [System.Text.Json.Serialization.JsonPropertyName("uri")] public string Uri { get; set; } = string.Empty; } }