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

namespace Stardust.McpClientTests;

/// <summary>轻量MCP客户端。直接发送JSON-RPC 2.0 over HTTP,不依赖MCP SDK</summary>
public class RawMcpClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly String _endpoint;
    private readonly String? _token;

    /// <summary>构造</summary>
    /// <param name="httpClient">HTTP客户端(通常来自TestServer)</param>
    /// <param name="endpoint">MCP端点地址(如 http://localhost/mcp)</param>
    /// <param name="token">Bearer Token(sdmcp_xxx),initialize不需要</param>
    public RawMcpClient(HttpClient httpClient, String endpoint, String? token = null)
    {
        _httpClient = httpClient;
        _endpoint = endpoint;
        _token = token;
    }

    /// <summary>发送JSON-RPC请求</summary>
    public async Task<JsonObject> SendAsync(String method, Object? @params = null, Object? id = null)
    {
        var request = new Dictionary<String, Object?>
        {
            ["jsonrpc"] = "2.0",
            ["method"] = method,
            ["id"] = id ?? 1,
        };
        if (@params != null) request["params"] = @params;

        var json = JsonSerializer.Serialize(request);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        var httpRequest = new HttpRequestMessage(HttpMethod.Post, _endpoint) { Content = content };

        if (!_token.IsNullOrEmpty())
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);

        var response = await _httpClient.SendAsync(httpRequest);
        var responseBody = await response.Content.ReadAsStringAsync();

        try
        {
            return JsonSerializer.Deserialize<JsonObject>(responseBody) ?? new JsonObject();
        }
        catch (JsonException)
        {
            // 服务端返回非JSON响应(如HTML错误页),包装为JSON-RPC错误
            var snippet = responseBody.Length > 200 ? responseBody.Substring(0, 200) + "..." : responseBody;
            return new JsonObject
            {
                ["jsonrpc"] = "2.0",
                ["error"] = new JsonObject
                {
                    ["code"] = -32603,
                    ["message"] = $"Non-JSON response (HTTP {(Int32)response.StatusCode}): {snippet}",
                },
                ["id"] = JsonValue.Create(id ?? 1),
            };
        }
    }

    /// <summary>initialize握手(默认使用1.0协议版本2024-11-05)</summary>
    public async Task<JsonObject> InitializeAsync(String protocolVersion = "2024-11-05")
    {
        return await SendAsync("initialize", new
        {
            protocolVersion,
            capabilities = new { },
            clientInfo = new { name = "test-client", version = "1.0.0" }
        }, id: 1);
    }

    /// <summary>获取工具列表</summary>
    public async Task<JsonObject> ListToolsAsync()
    {
        return await SendAsync("tools/list", new { }, id: 2);
    }

    /// <summary>调用工具</summary>
    public async Task<JsonObject> CallToolAsync(String toolName, Object arguments)
    {
        return await SendAsync("tools/call", new { name = toolName, arguments }, id: 3);
    }

    /// <summary>检查响应是否包含error字段</summary>
    public static Boolean HasError(JsonObject response) => response.ContainsKey("error");

    /// <summary>获取错误码</summary>
    public static Int32 GetErrorCode(JsonObject response) =>
        response["error"]?["code"]?.GetValue<Int32>() ?? 0;

    /// <summary>获取错误消息</summary>
    public static String? GetErrorMessage(JsonObject response) =>
        response["error"]?["message"]?.GetValue<String>();

    /// <summary>获取result字段</summary>
    public static JsonNode? GetResult(JsonObject response) => response["result"];

    /// <summary>获取tools/call返回的text内容(解析content[0].text为JSON)</summary>
    public static JsonNode? GetToolContent(JsonObject response)
    {
        var text = response["result"]?["content"]?[0]?["text"]?.GetValue<String>();
        if (text.IsNullOrEmpty()) return null;
        return JsonSerializer.Deserialize<JsonNode>(text!);
    }

    public void Dispose()
    {
        // 不 dispose httpClient,由 TestServer 管理
    }
}