using System.Text.Json; using System.Text.Json.Serialization; using McpDotNet.Protocol.Messages; using Microsoft.Extensions.Logging; using McpDotNet.Logging; namespace McpDotNet.Utils.Json; /// /// JSON converter for IJsonRpcMessage that handles polymorphic deserialization of different message types. /// internal sealed class JsonRpcMessageConverter : JsonConverter { /// public override IJsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException("Expected StartObject token"); } using var doc = JsonDocument.ParseValue(ref reader); var root = doc.RootElement; // All JSON-RPC messages must have a jsonrpc property with value "2.0" if (!root.TryGetProperty("jsonrpc", out var versionProperty) || versionProperty.GetString() != "2.0") { throw new JsonException("Invalid or missing jsonrpc version"); } // Determine the message type based on the presence of id, method, and error properties bool hasId = root.TryGetProperty("id", out _); bool hasMethod = root.TryGetProperty("method", out _); bool hasError = root.TryGetProperty("error", out _); var rawText = root.GetRawText(); // Messages with an id but no method are responses if (hasId && !hasMethod) { // Messages with an error property are error responses if (hasError) { return JsonSerializer.Deserialize(rawText, options); } // Messages with a result property are success responses else if (root.TryGetProperty("result", out _)) { return JsonSerializer.Deserialize(rawText, options); } throw new JsonException("Response must have either result or error"); } // Messages with a method but no id are notifications else if (hasMethod && !hasId) { return JsonSerializer.Deserialize(rawText, options); } // Messages with both method and id are requests else if (hasMethod && hasId) { return JsonSerializer.Deserialize(rawText, options); } throw new JsonException("Invalid JSON-RPC message format"); } /// public override void Write(Utf8JsonWriter writer, IJsonRpcMessage value, JsonSerializerOptions options) { switch (value) { case JsonRpcRequest request: JsonSerializer.Serialize(writer, request, options); break; case JsonRpcNotification notification: JsonSerializer.Serialize(writer, notification, options); break; case JsonRpcResponse response: JsonSerializer.Serialize(writer, response, options); break; case JsonRpcError error: JsonSerializer.Serialize(writer, error, options); break; default: throw new JsonException($"Unknown JSON-RPC message type: {value.GetType()}"); } } }