130 lines
4.3 KiB
C#
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;
|
|
}
|
|
}
|