1
+ using System . ComponentModel ;
2
+ using System . Diagnostics ;
1
3
using System . Diagnostics . CodeAnalysis ;
4
+ using System . Text . Json ;
2
5
using System . Text . Json . Serialization ;
3
6
4
7
namespace ModelContextProtocol . Protocol ;
@@ -54,39 +57,273 @@ public IDictionary<string, PrimitiveSchemaDefinition> Properties
54
57
public IList < string > ? Required { get ; set ; }
55
58
}
56
59
57
-
58
60
/// <summary>
59
61
/// Represents restricted subset of JSON Schema:
60
62
/// <see cref="StringSchema"/>, <see cref="NumberSchema"/>, <see cref="BooleanSchema"/>, or <see cref="EnumSchema"/>.
61
63
/// </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.
66
65
public abstract class PrimitiveSchemaDefinition
67
66
{
68
67
/// <summary>Prevent external derivations.</summary>
69
68
protected private PrimitiveSchemaDefinition ( )
70
69
{
71
70
}
72
- }
73
71
74
- /// <summary>Represents a schema for a string type.</summary>
75
- public sealed class StringSchema : PrimitiveSchemaDefinition
76
- {
77
72
/// <summary>Gets the type of the schema.</summary>
78
- /// <remarks>This is always "string".</remarks>
79
73
[ JsonPropertyName ( "type" ) ]
80
- public string Type => "string" ;
74
+ public abstract string Type { get ; set ; }
81
75
82
- /// <summary>Gets or sets a title for the string .</summary>
76
+ /// <summary>Gets or sets a title for the schema .</summary>
83
77
[ JsonPropertyName ( "title" ) ]
84
78
public string ? Title { get ; set ; }
85
79
86
- /// <summary>Gets or sets a description for the string .</summary>
80
+ /// <summary>Gets or sets a description for the schema .</summary>
87
81
[ JsonPropertyName ( "description" ) ]
88
82
public string ? Description { get ; set ; }
89
83
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
+
90
327
/// <summary>Gets or sets the minimum length for the string.</summary>
91
328
[ JsonPropertyName ( "minLength" ) ]
92
329
public int ? MinLength
@@ -139,11 +376,9 @@ public string? Format
139
376
/// <summary>Represents a schema for a number or integer type.</summary>
140
377
public sealed class NumberSchema : PrimitiveSchemaDefinition
141
378
{
142
- /// <summary>Gets the type of the schema.</summary>
143
- /// <remarks>This should be "number" or "integer".</remarks>
144
- [ JsonPropertyName ( "type" ) ]
379
+ /// <inheritdoc/>
145
380
[ field: MaybeNull ]
146
- public string Type
381
+ public override string Type
147
382
{
148
383
get => field ??= "number" ;
149
384
set
@@ -157,14 +392,6 @@ public string Type
157
392
}
158
393
}
159
394
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
-
168
395
/// <summary>Gets or sets the minimum allowed value.</summary>
169
396
[ JsonPropertyName ( "minimum" ) ]
170
397
public double ? Minimum { get ; set ; }
@@ -177,18 +404,19 @@ public string Type
177
404
/// <summary>Represents a schema for a Boolean type.</summary>
178
405
public sealed class BooleanSchema : PrimitiveSchemaDefinition
179
406
{
180
- /// <summary>Gets the type of the schema.</summary>
181
- /// <remarks>This is always "boolean".</remarks>
407
+ /// <inheritdoc/>
182
408
[ 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
+ }
192
420
193
421
/// <summary>Gets or sets the default value for the Boolean.</summary>
194
422
[ JsonPropertyName ( "default" ) ]
@@ -198,18 +426,19 @@ public sealed class BooleanSchema : PrimitiveSchemaDefinition
198
426
/// <summary>Represents a schema for an enum type.</summary>
199
427
public sealed class EnumSchema : PrimitiveSchemaDefinition
200
428
{
201
- /// <summary>Gets the type of the schema.</summary>
202
- /// <remarks>This is always "string".</remarks>
429
+ /// <inheritdoc/>
203
430
[ 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
+ }
213
442
214
443
/// <summary>Gets or sets the list of allowed string values for the enum.</summary>
215
444
[ JsonPropertyName ( "enum" ) ]
0 commit comments