节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
2.10 KiB
Stardust
using System.Text;
using Microsoft.AspNetCore.Http;
using NewLife;

namespace Stardust.Mcp;

/// <summary>MCP协议中间件。在宿主自身中间件之前短路 /mcp 请求,避免Web中间件逻辑影响机器API。</summary>
public class McpMiddleware
{
    private readonly RequestDelegate _next;

    public McpMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, McpEngine engine, StardustMcpOptions options)
    {
        // 仅处理 /mcp 路径,其余请求照常走后续管道
        if (!context.Request.Path.Equals("/mcp", StringComparison.OrdinalIgnoreCase))
        {
            await _next(context);
            return;
        }

        // MCP 总开关关闭时返回 404
        if (!options.Enabled())
        {
            context.Response.StatusCode = 404;
            return;
        }

        // 本服务为无状态,按 Streamable HTTP 规范对 GET/DELETE 返回 405
        if (HttpMethods.IsGet(context.Request.Method) || HttpMethods.IsDelete(context.Request.Method))
        {
            context.Response.StatusCode = 405;
            return;
        }

        // 读取请求体
        String body;
        using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8))
            body = await reader.ReadToEndAsync();

        if (body.IsNullOrEmpty())
        {
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(McpEngine.BuildError(null, -32700, "解析错误:请求体为空"));
            return;
        }

        var ip = context.Connection.RemoteIpAddress?.ToString() ?? "";
        var ua = context.Request.Headers.UserAgent.ToString();
        var authorization = context.Request.Headers.Authorization.ToString();

        var reply = await engine.HandleAsync(body, ip, ua, authorization, context.RequestServices);

        context.Response.StatusCode = reply.StatusCode;
        if (!reply.Body.IsNullOrEmpty())
        {
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(reply.Body);
        }
    }
}