-
Notifications
You must be signed in to change notification settings - Fork 657
Description
Summary
When a C# MCP tool method has nullable parameters (e.g. string? filter), the SDK generates a JSON schema that marks them as both "required" and nullable via a type union ["string", "null"]. However, AIFunctionFactory.ReflectionAIFunctionDescriptor.GetParameterMarshaller independently enforces that all "required" parameters must be present in the arguments dictionary — throwing ArgumentException when the client omits them.
This means clients cannot omit nullable parameters, even though the schema correctly signals they accept null.
Steps to reproduce
- Define an MCP tool with a nullable parameter without a default value:
[McpServerTool(Name = "my_tool")]
public static string MyTool(
[Description("Required param")] string name,
[Description("Optional filter")] string? filter,
CancellationToken ct)
{
return $"name={name}, filter={filter ?? "(none)"}";
}Note: Adding
= nullas a default value is the intuitive fix, but C# requires optional parameters to appear after all required parameters. When DI-injected services (McpServer,CancellationToken, etc.) follow the nullable param,= nullcauses compiler error CS1737: Optional parameters must appear after all required parameters.
- The SDK generates this inputSchema:
{
"type": "object",
"required": ["name", "filter"],
"properties": {
"name": { "type": "string" },
"filter": { "type": ["string", "null"] }
}
}- A client calls the tool omitting
filter:
{
"method": "tools/call",
"params": {
"name": "my_tool",
"arguments": { "name": "test" }
}
}AIFunctionFactorythrows:
System.ArgumentException: The arguments dictionary is missing a value for the required parameter 'filter'.
at Microsoft.Extensions.AI.AIFunctionFactory.ReflectionAIFunctionDescriptor.<GetParameterMarshaller>...
Expected behavior
One of the following:
- Option A (preferred): Don't mark nullable parameters as
"required"in the generated schema. A nullable param without a default value should be treated as optional in the JSON schema. - Option B: In
ReflectionAIFunctionDescriptor.GetParameterMarshaller, treat parameters whose schema type includes"null"as optional — supplynullwhen they're missing from the arguments dictionary, instead of throwing.
Current workaround
We added a CallToolFilter that inspects the cached tool schema before invocation, finds nullable properties missing from the arguments, and injects explicit JsonValueKind.Null values:
mcpOptions.Filters.Request.CallToolFilters.Add(next => async (context, ct) =>
{
if (context.Params is { } p)
{
// For each property in the schema that allows null but is missing
// from arguments, inject an explicit null value
InjectMissingNullableArguments(schemaCache, p);
}
return await next(context, ct);
});This works but shouldn't be necessary — the SDK should handle its own schema semantics consistently.
Environment
- SDK version: ModelContextProtocol 1.1.0 / ModelContextProtocol.AspNetCore 1.1.0
- Runtime: .NET 9.0
- Transport: Streamable HTTP
- Client: Claude Code (CLI)