Files
ColaFlow/colaflow-api/src/Modules/Mcp/ColaFlow.Modules.Mcp.Application/Handlers/ResourcesReadMethodHandler.cs
Yaojia Wang 8c51fa392b
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Refactoring
2025-11-23 23:40:10 +01:00

130 lines
4.3 KiB
C#

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;
/// <summary>
/// Handler for the 'resources/read' MCP method
/// Uses scoped IMcpResource instances from DI to avoid DbContext disposal issues
/// </summary>
public class ResourcesReadMethodHandler(
ILogger<ResourcesReadMethodHandler> logger,
IMcpResourceRegistry resourceRegistry,
IEnumerable<IMcpResource> scopedResources)
: IMcpMethodHandler
{
public string MethodName => "resources/read";
public async Task<object?> HandleAsync(object? @params, CancellationToken cancellationToken)
{
logger.LogDebug("Handling resources/read request");
// Parse parameters
var paramsJson = JsonSerializer.Serialize(@params);
var request = JsonSerializer.Deserialize<ResourceReadParams>(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;
}
/// <summary>
/// Parse resource URI and extract path/query parameters
/// </summary>
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}", @"(?<id>[^/]+)")
.Replace("{projectId}", @"(?<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;
}
}