11using ModelContextProtocol . Server ;
22using System . ComponentModel ;
3+ using System . Diagnostics ;
34using System . Text . Json ;
5+ using System . Text . Json . Nodes ;
46using System . Text . Json . Serialization ;
57
68namespace ModelContextProtocol . Protocol ;
@@ -70,59 +72,83 @@ private protected JsonRpcMessage()
7072 [ EditorBrowsable ( EditorBrowsableState . Never ) ]
7173 public sealed class Converter : JsonConverter < JsonRpcMessage >
7274 {
75+ private const string JsonRpcVersion = "2.0" ;
76+
7377 /// <inheritdoc/>
7478 public override JsonRpcMessage ? Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
7579 {
76- if ( reader . TokenType != JsonTokenType . StartObject )
77- {
78- throw new JsonException ( "Expected StartObject token" ) ;
79- }
80-
81- using var doc = JsonDocument . ParseValue ( ref reader ) ;
82- var root = doc . RootElement ;
80+ var union = ParseUnion ( ref reader , options ) ;
8381
8482 // 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" )
83+ if ( union . JsonRpc != JsonRpcVersion )
8784 {
8885 throw new JsonException ( "Invalid or missing jsonrpc version" ) ;
8986 }
9087
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-
9888 // Messages with an id but no method are responses
99- if ( hasId && ! hasMethod )
89+ if ( union . HasId && ! union . HasMethod )
10090 {
10191 // Messages with an error property are error responses
102- if ( hasError )
92+ if ( union . HasError )
10393 {
104- return JsonSerializer . Deserialize ( rawText , options . GetTypeInfo < JsonRpcError > ( ) ) ;
94+ if ( union . Error is null )
95+ {
96+ throw new JsonException ( "Error property cannot be null" ) ;
97+ }
98+
99+ return new JsonRpcError
100+ {
101+ JsonRpc = union . JsonRpc ,
102+ Id = union . Id ,
103+ Error = union . Error
104+ } ;
105105 }
106106
107107 // Messages with a result property are success responses
108- if ( root . TryGetProperty ( "result" , out _ ) )
108+ if ( union . HasResult )
109109 {
110- return JsonSerializer . Deserialize ( rawText , options . GetTypeInfo < JsonRpcResponse > ( ) ) ;
110+ return new JsonRpcResponse
111+ {
112+ JsonRpc = union . JsonRpc ,
113+ Id = union . Id ,
114+ Result = union . Result
115+ } ;
111116 }
112117
113118 throw new JsonException ( "Response must have either result or error" ) ;
114119 }
115120
116121 // Messages with a method but no id are notifications
117- if ( hasMethod && ! hasId )
122+ if ( union . HasMethod && ! union . HasId )
118123 {
119- return JsonSerializer . Deserialize ( rawText , options . GetTypeInfo < JsonRpcNotification > ( ) ) ;
124+ if ( union . Method is null )
125+ {
126+ throw new JsonException ( "Method property cannot be null" ) ;
127+ }
128+
129+ return new JsonRpcNotification
130+ {
131+ JsonRpc = union . JsonRpc ,
132+ Method = union . Method ,
133+ Params = union . Params
134+ } ;
120135 }
121136
122137 // Messages with both method and id are requests
123- if ( hasMethod && hasId )
138+ if ( union . HasMethod && union . HasId )
124139 {
125- return JsonSerializer . Deserialize ( rawText , options . GetTypeInfo < JsonRpcRequest > ( ) ) ;
140+ if ( union . Method is null )
141+ {
142+ throw new JsonException ( "Method property cannot be null" ) ;
143+ }
144+
145+ return new JsonRpcRequest
146+ {
147+ JsonRpc = union . JsonRpc ,
148+ Id = union . Id ,
149+ Method = union . Method ,
150+ Params = union . Params
151+ } ;
126152 }
127153
128154 throw new JsonException ( "Invalid JSON-RPC message format" ) ;
@@ -149,5 +175,103 @@ public override void Write(Utf8JsonWriter writer, JsonRpcMessage value, JsonSeri
149175 throw new JsonException ( $ "Unknown JSON-RPC message type: { value . GetType ( ) } ") ;
150176 }
151177 }
178+
179+ /// <summary>
180+ /// Manually parses a JSON-RPC message from the reader into the Union struct.
181+ /// </summary>
182+ private static Union ParseUnion ( ref Utf8JsonReader reader , JsonSerializerOptions options )
183+ {
184+ var union = new Union
185+ {
186+ JsonRpc = string . Empty // Initialize to avoid null reference warnings
187+ } ;
188+
189+ if ( reader . TokenType != JsonTokenType . StartObject )
190+ {
191+ throw new JsonException ( "Expected StartObject token" ) ;
192+ }
193+
194+ while ( true )
195+ {
196+ bool success = reader . Read ( ) ;
197+ Debug . Assert ( success , "custom converters are guaranteed to be passed fully buffered objects" ) ;
198+
199+ if ( reader . TokenType is JsonTokenType . EndObject )
200+ {
201+ break ;
202+ }
203+
204+ Debug . Assert ( reader . TokenType is JsonTokenType . PropertyName ) ;
205+ string propertyName = reader . GetString ( ) ! ;
206+
207+ success = reader . Read ( ) ;
208+ Debug . Assert ( success , "custom converters are guaranteed to be passed fully buffered objects" ) ;
209+
210+ switch ( propertyName )
211+ {
212+ case "jsonrpc" :
213+ union . JsonRpc = reader . GetString ( ) ?? string . Empty ;
214+ break ;
215+
216+ case "id" :
217+ union . Id = JsonSerializer . Deserialize ( ref reader , options . GetTypeInfo < RequestId > ( ) ) ;
218+ union . HasId = true ;
219+ break ;
220+
221+ case "method" :
222+ union . Method = reader . GetString ( ) ;
223+ union . HasMethod = union . Method is not null ;
224+ break ;
225+
226+ case "params" :
227+ union . Params = JsonSerializer . Deserialize ( ref reader , options . GetTypeInfo < JsonNode > ( ) ) ;
228+ break ;
229+
230+ case "error" :
231+ union . Error = JsonSerializer . Deserialize ( ref reader , options . GetTypeInfo < JsonRpcErrorDetail > ( ) ) ;
232+ union . HasError = union . Error is not null ;
233+ break ;
234+
235+ case "result" :
236+ union . Result = JsonSerializer . Deserialize ( ref reader , options . GetTypeInfo < JsonNode > ( ) ) ;
237+ union . HasResult = true ;
238+ break ;
239+
240+ default :
241+ // Skip unknown properties
242+ reader . Skip ( ) ;
243+ break ;
244+ }
245+ }
246+
247+ return union ;
248+ }
249+
250+ /// <summary>
251+ /// Private struct to hold parsed JSON-RPC message data during deserialization.
252+ /// </summary>
253+ private struct Union
254+ {
255+ /// <summary>The JSON-RPC protocol version (must be "2.0").</summary>
256+ public string JsonRpc ;
257+ /// <summary>The message identifier for requests and responses.</summary>
258+ public RequestId Id ;
259+ /// <summary>The method name for requests and notifications.</summary>
260+ public string ? Method ;
261+ /// <summary>The parameters for requests and notifications.</summary>
262+ public JsonNode ? Params ;
263+ /// <summary>The error details for error responses.</summary>
264+ public JsonRpcErrorDetail ? Error ;
265+ /// <summary>The result for successful responses.</summary>
266+ public JsonNode ? Result ;
267+ /// <summary>Indicates whether an 'id' property was present.</summary>
268+ public bool HasId ;
269+ /// <summary>Indicates whether a 'method' property was present.</summary>
270+ public bool HasMethod ;
271+ /// <summary>Indicates whether an 'error' property was present.</summary>
272+ public bool HasError ;
273+ /// <summary>Indicates whether a 'result' property was present.</summary>
274+ public bool HasResult ;
275+ }
152276 }
153277}
0 commit comments