|
1 | 1 | using ModelContextProtocol.Server; |
2 | 2 | using System.ComponentModel; |
3 | 3 | using System.Text.Json; |
| 4 | +using System.Text.Json.Nodes; |
4 | 5 | using System.Text.Json.Serialization; |
5 | 6 |
|
6 | 7 | namespace ModelContextProtocol.Protocol; |
@@ -70,59 +71,150 @@ private protected JsonRpcMessage() |
70 | 71 | [EditorBrowsable(EditorBrowsableState.Never)] |
71 | 72 | public sealed class Converter : JsonConverter<JsonRpcMessage> |
72 | 73 | { |
73 | | - /// <inheritdoc/> |
74 | | - public override JsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
| 74 | + /// <summary> |
| 75 | + /// Private struct to hold parsed JSON-RPC message data during deserialization. |
| 76 | + /// </summary> |
| 77 | + private struct Union |
75 | 78 | { |
76 | | - if (reader.TokenType != JsonTokenType.StartObject) |
| 79 | + public string JsonRpc; |
| 80 | + public RequestId Id; |
| 81 | + public string? Method; |
| 82 | + public JsonNode? Params; |
| 83 | + public JsonRpcErrorDetail? Error; |
| 84 | + public JsonNode? Result; |
| 85 | + public bool HasId; |
| 86 | + public bool HasMethod; |
| 87 | + public bool HasError; |
| 88 | + public bool HasResult; |
| 89 | + |
| 90 | + /// <summary> |
| 91 | + /// Manually parses a JSON-RPC message from the reader into the Union struct. |
| 92 | + /// </summary> |
| 93 | + public static Union Parse(ref Utf8JsonReader reader, JsonSerializerOptions options) |
77 | 94 | { |
78 | | - throw new JsonException("Expected StartObject token"); |
| 95 | + var union = new Union(); |
| 96 | + |
| 97 | + if (reader.TokenType != JsonTokenType.StartObject) |
| 98 | + { |
| 99 | + throw new JsonException("Expected StartObject token"); |
| 100 | + } |
| 101 | + |
| 102 | + while (reader.Read()) |
| 103 | + { |
| 104 | + if (reader.TokenType == JsonTokenType.EndObject) |
| 105 | + { |
| 106 | + break; |
| 107 | + } |
| 108 | + |
| 109 | + if (reader.TokenType != JsonTokenType.PropertyName) |
| 110 | + { |
| 111 | + throw new JsonException("Expected PropertyName token"); |
| 112 | + } |
| 113 | + |
| 114 | + string propertyName = reader.GetString()!; |
| 115 | + reader.Read(); |
| 116 | + |
| 117 | + switch (propertyName) |
| 118 | + { |
| 119 | + case "jsonrpc": |
| 120 | + union.JsonRpc = reader.GetString()!; |
| 121 | + break; |
| 122 | + |
| 123 | + case "id": |
| 124 | + union.Id = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo<RequestId>()); |
| 125 | + union.HasId = true; |
| 126 | + break; |
| 127 | + |
| 128 | + case "method": |
| 129 | + union.Method = reader.GetString(); |
| 130 | + union.HasMethod = true; |
| 131 | + break; |
| 132 | + |
| 133 | + case "params": |
| 134 | + union.Params = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo<JsonNode>()); |
| 135 | + break; |
| 136 | + |
| 137 | + case "error": |
| 138 | + union.Error = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo<JsonRpcErrorDetail>()); |
| 139 | + union.HasError = true; |
| 140 | + break; |
| 141 | + |
| 142 | + case "result": |
| 143 | + union.Result = JsonSerializer.Deserialize(ref reader, options.GetTypeInfo<JsonNode>()); |
| 144 | + union.HasResult = true; |
| 145 | + break; |
| 146 | + |
| 147 | + default: |
| 148 | + // Skip unknown properties |
| 149 | + reader.Skip(); |
| 150 | + break; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + return union; |
79 | 155 | } |
| 156 | + } |
80 | 157 |
|
81 | | - using var doc = JsonDocument.ParseValue(ref reader); |
82 | | - var root = doc.RootElement; |
| 158 | + /// <inheritdoc/> |
| 159 | + public override JsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
| 160 | + { |
| 161 | + var union = Union.Parse(ref reader, options); |
83 | 162 |
|
84 | 163 | // All JSON-RPC messages must have a jsonrpc property with value "2.0" |
85 | | - if (!root.TryGetProperty("jsonrpc", out var versionProperty) || |
86 | | - versionProperty.GetString() != "2.0") |
| 164 | + if (union.JsonRpc != "2.0") |
87 | 165 | { |
88 | 166 | throw new JsonException("Invalid or missing jsonrpc version"); |
89 | 167 | } |
90 | 168 |
|
91 | | - // Determine the message type based on the presence of id, method, and error properties |
92 | | - bool hasId = root.TryGetProperty("id", out _); |
93 | | - bool hasMethod = root.TryGetProperty("method", out _); |
94 | | - bool hasError = root.TryGetProperty("error", out _); |
95 | | - |
96 | | - var rawText = root.GetRawText(); |
97 | | - |
98 | 169 | // Messages with an id but no method are responses |
99 | | - if (hasId && !hasMethod) |
| 170 | + if (union.HasId && !union.HasMethod) |
100 | 171 | { |
101 | 172 | // Messages with an error property are error responses |
102 | | - if (hasError) |
| 173 | + if (union.HasError) |
103 | 174 | { |
104 | | - return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcError>()); |
| 175 | + return new JsonRpcError |
| 176 | + { |
| 177 | + JsonRpc = union.JsonRpc, |
| 178 | + Id = union.Id, |
| 179 | + Error = union.Error! |
| 180 | + }; |
105 | 181 | } |
106 | 182 |
|
107 | 183 | // Messages with a result property are success responses |
108 | | - if (root.TryGetProperty("result", out _)) |
| 184 | + if (union.HasResult) |
109 | 185 | { |
110 | | - return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcResponse>()); |
| 186 | + return new JsonRpcResponse |
| 187 | + { |
| 188 | + JsonRpc = union.JsonRpc, |
| 189 | + Id = union.Id, |
| 190 | + Result = union.Result |
| 191 | + }; |
111 | 192 | } |
112 | 193 |
|
113 | 194 | throw new JsonException("Response must have either result or error"); |
114 | 195 | } |
115 | 196 |
|
116 | 197 | // Messages with a method but no id are notifications |
117 | | - if (hasMethod && !hasId) |
| 198 | + if (union.HasMethod && !union.HasId) |
118 | 199 | { |
119 | | - return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcNotification>()); |
| 200 | + return new JsonRpcNotification |
| 201 | + { |
| 202 | + JsonRpc = union.JsonRpc, |
| 203 | + Method = union.Method!, |
| 204 | + Params = union.Params |
| 205 | + }; |
120 | 206 | } |
121 | 207 |
|
122 | 208 | // Messages with both method and id are requests |
123 | | - if (hasMethod && hasId) |
| 209 | + if (union.HasMethod && union.HasId) |
124 | 210 | { |
125 | | - return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcRequest>()); |
| 211 | + return new JsonRpcRequest |
| 212 | + { |
| 213 | + JsonRpc = union.JsonRpc, |
| 214 | + Id = union.Id, |
| 215 | + Method = union.Method!, |
| 216 | + Params = union.Params |
| 217 | + }; |
126 | 218 | } |
127 | 219 |
|
128 | 220 | throw new JsonException("Invalid JSON-RPC message format"); |
|
0 commit comments