Skip to content

Commit c90afd6

Browse files
Reinstate tests and fix CreatePrimitiveSchema to extract defaults from AttributeProvider
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
1 parent ed0d9d3 commit c90afd6

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

src/ModelContextProtocol.Core/Server/McpServer.Methods.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,52 @@ private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, J
307307
foreach (JsonPropertyInfo pi in typeInfo.Properties)
308308
{
309309
var def = CreatePrimitiveSchema(pi.PropertyType, serializerOptions);
310+
311+
// Extract default value from DefaultValueAttribute if present
312+
if (pi.AttributeProvider != null)
313+
{
314+
var attrs = pi.AttributeProvider.GetCustomAttributes(typeof(System.ComponentModel.DefaultValueAttribute), false);
315+
if (attrs.Length > 0 && attrs[0] is System.ComponentModel.DefaultValueAttribute defaultValueAttr)
316+
{
317+
SetDefaultValue(def, defaultValueAttr.Value);
318+
}
319+
}
320+
310321
props[pi.Name] = def;
311322
}
312323

313324
return schema;
314325
}
315326

327+
/// <summary>
328+
/// Sets the default value on a primitive schema definition based on the value type.
329+
/// </summary>
330+
/// <param name="schema">The schema to set the default value on.</param>
331+
/// <param name="value">The default value.</param>
332+
private static void SetDefaultValue(ElicitRequestParams.PrimitiveSchemaDefinition schema, object? value)
333+
{
334+
if (value == null)
335+
{
336+
return;
337+
}
338+
339+
switch (schema)
340+
{
341+
case ElicitRequestParams.StringSchema stringSchema:
342+
stringSchema.Default = value.ToString();
343+
break;
344+
case ElicitRequestParams.NumberSchema numberSchema:
345+
numberSchema.Default = Convert.ToDouble(value);
346+
break;
347+
case ElicitRequestParams.BooleanSchema booleanSchema:
348+
booleanSchema.Default = Convert.ToBoolean(value);
349+
break;
350+
case ElicitRequestParams.EnumSchema enumSchema:
351+
enumSchema.Default = value.ToString();
352+
break;
353+
}
354+
}
355+
316356
/// <summary>
317357
/// Creates a primitive schema definition for the specified type, if supported.
318358
/// </summary>

tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ await request.Server.ElicitAsync<string>(
8686
Content = [new TextContentBlock { Text = "unexpected" }],
8787
};
8888
}
89+
else if (request.Params!.Name == "TestElicitationWithDefaults")
90+
{
91+
var result = await request.Server.ElicitAsync<FormWithDefaults>(
92+
message: "Please provide information.",
93+
serializerOptions: ElicitationDefaultsJsonContext.Default.Options,
94+
cancellationToken: CancellationToken.None);
95+
96+
// The test will validate the schema in the client handler
97+
return new CallToolResult
98+
{
99+
Content = [new TextContentBlock { Text = "success" }],
100+
};
101+
}
89102
else
90103
{
91104
Assert.Fail($"Unexpected tool name: {request.Params!.Name}");
@@ -360,4 +373,89 @@ public sealed class Nested
360373
[JsonSerializable(typeof(UnsupportedForm.Nested))]
361374
[JsonSerializable(typeof(JsonElement))]
362375
internal partial class ElicitationUnsupportedJsonContext : JsonSerializerContext;
376+
377+
public sealed class FormWithDefaults
378+
{
379+
[System.ComponentModel.DefaultValue("John Doe")]
380+
public string Name { get; set; } = "John Doe";
381+
382+
[System.ComponentModel.DefaultValue(30)]
383+
public int Age { get; set; } = 30;
384+
385+
[System.ComponentModel.DefaultValue(85.5)]
386+
public double Score { get; set; } = 85.5;
387+
388+
[System.ComponentModel.DefaultValue(true)]
389+
public bool IsActive { get; set; } = true;
390+
391+
[System.ComponentModel.DefaultValue("active")]
392+
public string Status { get; set; } = "active";
393+
}
394+
395+
[JsonSerializable(typeof(FormWithDefaults))]
396+
[JsonSerializable(typeof(JsonElement))]
397+
internal partial class ElicitationDefaultsJsonContext : JsonSerializerContext;
398+
399+
[Fact]
400+
public async Task Elicit_Typed_With_Defaults_Maps_To_Schema_Defaults()
401+
{
402+
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
403+
{
404+
Handlers = new()
405+
{
406+
ElicitationHandler = async (request, cancellationToken) =>
407+
{
408+
Assert.NotNull(request);
409+
Assert.Equal("Please provide information.", request.Message);
410+
411+
Assert.Equal(5, request.RequestedSchema.Properties.Count);
412+
413+
// Verify that default values from the type are mapped to the schema
414+
foreach (var entry in request.RequestedSchema.Properties)
415+
{
416+
switch (entry.Key)
417+
{
418+
case nameof(FormWithDefaults.Name):
419+
var nameSchema = Assert.IsType<ElicitRequestParams.StringSchema>(entry.Value);
420+
Assert.Equal("John Doe", nameSchema.Default);
421+
break;
422+
423+
case nameof(FormWithDefaults.Age):
424+
var ageSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(entry.Value);
425+
Assert.Equal(30, ageSchema.Default);
426+
break;
427+
428+
case nameof(FormWithDefaults.Score):
429+
var scoreSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(entry.Value);
430+
Assert.Equal(85.5, scoreSchema.Default);
431+
break;
432+
433+
case nameof(FormWithDefaults.IsActive):
434+
var activeSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(entry.Value);
435+
Assert.True(activeSchema.Default);
436+
break;
437+
438+
case nameof(FormWithDefaults.Status):
439+
var statusSchema = Assert.IsType<ElicitRequestParams.StringSchema>(entry.Value);
440+
Assert.Equal("active", statusSchema.Default);
441+
break;
442+
443+
default:
444+
Assert.Fail($"Unexpected property: {entry.Key}");
445+
break;
446+
}
447+
}
448+
449+
return new ElicitResult
450+
{
451+
Action = "accept",
452+
Content = new Dictionary<string, JsonElement>()
453+
};
454+
},
455+
}
456+
});
457+
458+
var result = await client.CallToolAsync("TestElicitationWithDefaults", cancellationToken: TestContext.Current.CancellationToken);
459+
Assert.Equal("success", (result.Content[0] as TextContentBlock)?.Text);
460+
}
363461
}

0 commit comments

Comments
 (0)