1+ using System . ComponentModel ;
2+ using System . Diagnostics ;
13using System . Diagnostics . CodeAnalysis ;
4+ using System . Text . Json ;
25using System . Text . Json . Serialization ;
36
47namespace ModelContextProtocol . Protocol ;
@@ -54,39 +57,273 @@ public IDictionary<string, PrimitiveSchemaDefinition> Properties
5457 public IList < string > ? Required { get ; set ; }
5558 }
5659
57-
5860 /// <summary>
5961 /// Represents restricted subset of JSON Schema:
6062 /// <see cref="StringSchema"/>, <see cref="NumberSchema"/>, <see cref="BooleanSchema"/>, or <see cref="EnumSchema"/>.
6163 /// </summary>
62- [ JsonDerivedType ( typeof ( BooleanSchema ) ) ]
63- [ JsonDerivedType ( typeof ( EnumSchema ) ) ]
64- [ JsonDerivedType ( typeof ( NumberSchema ) ) ]
65- [ JsonDerivedType ( typeof ( StringSchema ) ) ]
64+ [ JsonConverter ( typeof ( Converter ) ) ] // TODO: This converter exists due to the lack of downlevel support for AllowOutOfOrderMetadataProperties.
6665 public abstract class PrimitiveSchemaDefinition
6766 {
6867 /// <summary>Prevent external derivations.</summary>
6968 protected private PrimitiveSchemaDefinition ( )
7069 {
7170 }
72- }
7371
74- /// <summary>Represents a schema for a string type.</summary>
75- public sealed class StringSchema : PrimitiveSchemaDefinition
76- {
7772 /// <summary>Gets the type of the schema.</summary>
78- /// <remarks>This is always "string".</remarks>
7973 [ JsonPropertyName ( "type" ) ]
80- public string Type => "string" ;
74+ public abstract string Type { get ; set ; }
8175
82- /// <summary>Gets or sets a title for the string .</summary>
76+ /// <summary>Gets or sets a title for the schema .</summary>
8377 [ JsonPropertyName ( "title" ) ]
8478 public string ? Title { get ; set ; }
8579
86- /// <summary>Gets or sets a description for the string .</summary>
80+ /// <summary>Gets or sets a description for the schema .</summary>
8781 [ JsonPropertyName ( "description" ) ]
8882 public string ? Description { get ; set ; }
8983
84+ /// <summary>
85+ /// Provides a <see cref="JsonConverter"/> for <see cref="ResourceContents"/>.
86+ /// </summary>
87+ [ EditorBrowsable ( EditorBrowsableState . Never ) ]
88+ public class Converter : JsonConverter < PrimitiveSchemaDefinition >
89+ {
90+ /// <inheritdoc/>
91+ public override PrimitiveSchemaDefinition ? Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
92+ {
93+ if ( reader . TokenType == JsonTokenType . Null )
94+ {
95+ return null ;
96+ }
97+
98+ if ( reader . TokenType != JsonTokenType . StartObject )
99+ {
100+ throw new JsonException ( ) ;
101+ }
102+
103+ string ? type = null ;
104+ string ? title = null ;
105+ string ? description = null ;
106+ int ? minLength = null ;
107+ int ? maxLength = null ;
108+ string ? format = null ;
109+ double ? minimum = null ;
110+ double ? maximum = null ;
111+ bool ? defaultBool = null ;
112+ IList < string > ? enumValues = null ;
113+ IList < string > ? enumNames = null ;
114+
115+ while ( reader . Read ( ) && reader . TokenType != JsonTokenType . EndObject )
116+ {
117+ if ( reader . TokenType != JsonTokenType . PropertyName )
118+ {
119+ continue ;
120+ }
121+
122+ string ? propertyName = reader . GetString ( ) ;
123+ bool success = reader . Read ( ) ;
124+ Debug . Assert ( success , "STJ must have buffered the entire object for us." ) ;
125+
126+ switch ( propertyName )
127+ {
128+ case "type" :
129+ type = reader . GetString ( ) ;
130+ break ;
131+
132+ case "title" :
133+ title = reader . GetString ( ) ;
134+ break ;
135+
136+ case "description" :
137+ description = reader . GetString ( ) ;
138+ break ;
139+
140+ case "minLength" :
141+ minLength = reader . GetInt32 ( ) ;
142+ break ;
143+
144+ case "maxLength" :
145+ maxLength = reader . GetInt32 ( ) ;
146+ break ;
147+
148+ case "format" :
149+ format = reader . GetString ( ) ;
150+ break ;
151+
152+ case "minimum" :
153+ minimum = reader . GetDouble ( ) ;
154+ break ;
155+
156+ case "maximum" :
157+ maximum = reader . GetDouble ( ) ;
158+ break ;
159+
160+ case "default" :
161+ defaultBool = reader . GetBoolean ( ) ;
162+ break ;
163+
164+ case "enum" :
165+ enumValues = JsonSerializer . Deserialize ( ref reader , McpJsonUtilities . JsonContext . Default . IListString ) ;
166+ break ;
167+
168+ case "enumNames" :
169+ enumNames = JsonSerializer . Deserialize ( ref reader , McpJsonUtilities . JsonContext . Default . IListString ) ;
170+ break ;
171+
172+ default :
173+ break ;
174+ }
175+ }
176+
177+ if ( type is null )
178+ {
179+ throw new JsonException ( "The 'type' property is required." ) ;
180+ }
181+
182+ PrimitiveSchemaDefinition ? psd = null ;
183+ switch ( type )
184+ {
185+ case "string" :
186+ if ( enumValues is not null )
187+ {
188+ psd = new EnumSchema
189+ {
190+ Enum = enumValues ,
191+ EnumNames = enumNames
192+ } ;
193+ }
194+ else
195+ {
196+ psd = new StringSchema
197+ {
198+ MinLength = minLength ,
199+ MaxLength = maxLength ,
200+ Format = format ,
201+ } ;
202+ }
203+ break ;
204+
205+ case "integer" :
206+ case "number" :
207+ psd = new NumberSchema
208+ {
209+ Minimum = minimum ,
210+ Maximum = maximum ,
211+ } ;
212+ break ;
213+
214+ case "boolean" :
215+ psd = new BooleanSchema
216+ {
217+ Default = defaultBool ,
218+ } ;
219+ break ;
220+ }
221+
222+ if ( psd is not null )
223+ {
224+ psd . Type = type ;
225+ psd . Title = title ;
226+ psd . Description = description ;
227+ }
228+
229+ return psd ;
230+ }
231+
232+ /// <inheritdoc/>
233+ public override void Write ( Utf8JsonWriter writer , PrimitiveSchemaDefinition value , JsonSerializerOptions options )
234+ {
235+ if ( value is null )
236+ {
237+ writer . WriteNullValue ( ) ;
238+ return ;
239+ }
240+
241+ writer . WriteStartObject ( ) ;
242+
243+ writer . WriteString ( "type" , value . Type ) ;
244+ if ( value . Title is not null )
245+ {
246+ writer . WriteString ( "title" , value . Title ) ;
247+ }
248+ if ( value . Description is not null )
249+ {
250+ writer . WriteString ( "description" , value . Description ) ;
251+ }
252+
253+ switch ( value )
254+ {
255+ case StringSchema stringSchema :
256+ if ( stringSchema . MinLength . HasValue )
257+ {
258+ writer . WriteNumber ( "minLength" , stringSchema . MinLength . Value ) ;
259+ }
260+ if ( stringSchema . MaxLength . HasValue )
261+ {
262+ writer . WriteNumber ( "maxLength" , stringSchema . MaxLength . Value ) ;
263+ }
264+ if ( stringSchema . Format is not null )
265+ {
266+ writer . WriteString ( "format" , stringSchema . Format ) ;
267+ }
268+ break ;
269+
270+ case NumberSchema numberSchema :
271+ if ( numberSchema . Minimum . HasValue )
272+ {
273+ writer . WriteNumber ( "minimum" , numberSchema . Minimum . Value ) ;
274+ }
275+ if ( numberSchema . Maximum . HasValue )
276+ {
277+ writer . WriteNumber ( "maximum" , numberSchema . Maximum . Value ) ;
278+ }
279+ break ;
280+
281+ case BooleanSchema booleanSchema :
282+ if ( booleanSchema . Default . HasValue )
283+ {
284+ writer . WriteBoolean ( "default" , booleanSchema . Default . Value ) ;
285+ }
286+ break ;
287+
288+ case EnumSchema enumSchema :
289+ if ( enumSchema . Enum is not null )
290+ {
291+ writer . WritePropertyName ( "enum" ) ;
292+ JsonSerializer . Serialize ( writer , enumSchema . Enum , McpJsonUtilities . JsonContext . Default . IListString ) ;
293+ }
294+ if ( enumSchema . EnumNames is not null )
295+ {
296+ writer . WritePropertyName ( "enumNames" ) ;
297+ JsonSerializer . Serialize ( writer , enumSchema . EnumNames , McpJsonUtilities . JsonContext . Default . IListString ) ;
298+ }
299+ break ;
300+
301+ default :
302+ throw new JsonException ( $ "Unexpected schema type: { value . GetType ( ) . Name } ") ;
303+ }
304+
305+ writer . WriteEndObject ( ) ;
306+ }
307+ }
308+ }
309+
310+ /// <summary>Represents a schema for a string type.</summary>
311+ public sealed class StringSchema : PrimitiveSchemaDefinition
312+ {
313+ /// <inheritdoc/>
314+ [ JsonPropertyName ( "type" ) ]
315+ public override string Type
316+ {
317+ get => "string" ;
318+ set
319+ {
320+ if ( value is not "string" )
321+ {
322+ throw new ArgumentException ( "Type must be 'string'." , nameof ( value ) ) ;
323+ }
324+ }
325+ }
326+
90327 /// <summary>Gets or sets the minimum length for the string.</summary>
91328 [ JsonPropertyName ( "minLength" ) ]
92329 public int ? MinLength
@@ -139,11 +376,9 @@ public string? Format
139376 /// <summary>Represents a schema for a number or integer type.</summary>
140377 public sealed class NumberSchema : PrimitiveSchemaDefinition
141378 {
142- /// <summary>Gets the type of the schema.</summary>
143- /// <remarks>This should be "number" or "integer".</remarks>
144- [ JsonPropertyName ( "type" ) ]
379+ /// <inheritdoc/>
145380 [ field: MaybeNull ]
146- public string Type
381+ public override string Type
147382 {
148383 get => field ??= "number" ;
149384 set
@@ -157,14 +392,6 @@ public string Type
157392 }
158393 }
159394
160- /// <summary>Gets or sets a title for the number input.</summary>
161- [ JsonPropertyName ( "title" ) ]
162- public string ? Title { get ; set ; }
163-
164- /// <summary>Gets or sets a description for the number input.</summary>
165- [ JsonPropertyName ( "description" ) ]
166- public string ? Description { get ; set ; }
167-
168395 /// <summary>Gets or sets the minimum allowed value.</summary>
169396 [ JsonPropertyName ( "minimum" ) ]
170397 public double ? Minimum { get ; set ; }
@@ -177,18 +404,19 @@ public string Type
177404 /// <summary>Represents a schema for a Boolean type.</summary>
178405 public sealed class BooleanSchema : PrimitiveSchemaDefinition
179406 {
180- /// <summary>Gets the type of the schema.</summary>
181- /// <remarks>This is always "boolean".</remarks>
407+ /// <inheritdoc/>
182408 [ JsonPropertyName ( "type" ) ]
183- public string Type => "boolean" ;
184-
185- /// <summary>Gets or sets a title for the Boolean.</summary>
186- [ JsonPropertyName ( "title" ) ]
187- public string ? Title { get ; set ; }
188-
189- /// <summary>Gets or sets a description for the Boolean.</summary>
190- [ JsonPropertyName ( "description" ) ]
191- public string ? Description { get ; set ; }
409+ public override string Type
410+ {
411+ get => "boolean" ;
412+ set
413+ {
414+ if ( value is not "boolean" )
415+ {
416+ throw new ArgumentException ( "Type must be 'boolean'." , nameof ( value ) ) ;
417+ }
418+ }
419+ }
192420
193421 /// <summary>Gets or sets the default value for the Boolean.</summary>
194422 [ JsonPropertyName ( "default" ) ]
@@ -198,18 +426,19 @@ public sealed class BooleanSchema : PrimitiveSchemaDefinition
198426 /// <summary>Represents a schema for an enum type.</summary>
199427 public sealed class EnumSchema : PrimitiveSchemaDefinition
200428 {
201- /// <summary>Gets the type of the schema.</summary>
202- /// <remarks>This is always "string".</remarks>
429+ /// <inheritdoc/>
203430 [ JsonPropertyName ( "type" ) ]
204- public string Type => "string" ;
205-
206- /// <summary>Gets or sets a title for the enum.</summary>
207- [ JsonPropertyName ( "title" ) ]
208- public string ? Title { get ; set ; }
209-
210- /// <summary>Gets or sets a description for the enum.</summary>
211- [ JsonPropertyName ( "description" ) ]
212- public string ? Description { get ; set ; }
431+ public override string Type
432+ {
433+ get => "string" ;
434+ set
435+ {
436+ if ( value is not "string" )
437+ {
438+ throw new ArgumentException ( "Type must be 'string'." , nameof ( value ) ) ;
439+ }
440+ }
441+ }
213442
214443 /// <summary>Gets or sets the list of allowed string values for the enum.</summary>
215444 [ JsonPropertyName ( "enum" ) ]
0 commit comments