You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Dotnet implementation silently drop parameters not used by tool during tool execution #1508
Describe the bug
Assume there is one tool called Respond_To_Task (Guid taskId, String update, Boolean markComplete = false), in current implementation, if AI decided to call Respond_To_Task(taskId = guidA, update = "Done", phase = "completed"), the call will succeed but the markComplete passed to tool backend is false. Both AI and Backend will not able to detect such gap until someone outside of the system checking the behavior issue.
To Reproduce
Steps to reproduce the behavior:
Define some mcp client with such tool definition, and test it with the instructed call pattern, it will succeed.
Expected behavior
Error out or warning out when there is parameters mismatch instead of silently drop or ignore.
Logs
If applicable, add logs to help explain your problem.
Additional context
Add any other context about the problem here.
The concern here is that there is no signal indicate this happened and both end believe the intended operation been executed correctly while what actually been executed is not what should happen. Understand this can be tool implementation practice change but it is not obvious such behavior is what been implemented today.
I don't understand. Your "expected behavior" was to error out or warning; if that's the behavior you want, just don't add the default value. The property in the tool schema is either required or optional, and if you specify optional (which is what you get with a default value), you're explicitly telling the model it's fine to not provide... it is not erroneous or warning-worthy.
It's no different in this regard from calling a method in C# with an optional parameter that has a default value: if the caller doesn't specify one, the callee gets the default value. It's not an error or a warning.
Agree adjust the tool implementation pattern can get the expected behavior as suggested. The risk is that when put the parameter as is, tool developer not aware of ["default implementation" will silently drop the not used parameter without warning or failure], to get reminded that "Shouldn't use parameter with default" because model may pass something different intended for that parameter but with wrong name, then execution succeed but result is wrong. Maybe the attribute can allow customization of the parameters mapping. Or the document of MCP dotnet sdk can call out this specifically.
@mikekistler, @halter73, and I were chatting with @softnado offline about this and thought it would be valuable to assess the behavior across the SDKs.
Only 2 of 7 implementations reject unmatched arguments: The Go SDK and FastMCP. Both do so by generating schemas with additionalProperties: falseand validating at runtime.
The Rust SDK is a notable near-miss: It generates schemas with additionalProperties: false but does not validate against them at runtime — serde silently drops unknown fields.
The Java SDK is unique in passing through: Unlike other SDKs that silently drop extra arguments, the Java SDK preserves them in the arguments map passed to the handler. The handler could theoretically detect and act on them, but the SDK provides no signal.
The Go SDK's opt-out is per-tool only: There is no global configuration to disable strict validation. Users must either supply a custom InputSchema per tool or use the raw Server.AddTool API.
5 of 7 implementations silently ignore/pass through: C#, TypeScript, Python, Java, and Rust all allow unrecognized arguments without any signal — each using their language's default deserialization behavior.
Relevant Specification Work
modelcontextprotocol/modelcontextprotocol#1898 — Open issue about additionalProperties: false on protocol types. Comment from @sirkirby (2026-04-08) explicitly raises the tool argument case and notes it has security implications. Comment from @pja-ant argues servers should not break on unknown fields for inter-version compatibility. This is the most active spec-level discussion about this topic.
SEP-1303 (accepted) — Established that input validation errors should be tool execution errors (isError: true), which is relevant to how rejections should be communicated even if the spec doesn't yet mandate that they happen.
The issue here is specifically that undefined tool arguments are silently ignored.
If a client sends a tools/call request with argument names that are not part of the tool’s declared input schema, these arguments are silently dropped and the invocation may "succeed" instead of surfacing that mismatch. That can make it hard for both the tool author and the caller to detect that the request did not actually match the tool contract.
Exploring this with Copilot revealed that the actual binding of tool call arguments to method parameters, the natural place to detect undefined tool arguments, happens inside MEAI and not in the MCP C# SDK.
It is possible to implement a fix completely in the MCP C# SDK but that will duplicate functionality currently in MEAI.
I think my preference would be to make this an option in MEAI that the MCP C# SDK threads through.
MEAI specifically allows other things in the arguments dictionary because:
you might be passing in state to pass down through a chain or a pipeline with different invocations picking out different things
you might be accepting an AIFunctionArguments into your method such that your method can pull out whatever it wants
there's nothing fundamentally wrong with an LLM choosing to supply additional information
If we want to add an option that makes the AIFunctions created by AIFunctionFactory.Create stricter, we can. It would need to be opt-in, though. And even at the MCP level, it would really need to be opt-in.
Alternatively, if the goal is just to be able to get some insight into when this happens, MCP tools created by McpServerTool.Create (or the attribute) could log to an ILogger when passed more information than they'll use).
I'm still not convinced there's a problem to be solved here, though.
I completely agree that this would be strictly opt-in at any level where it is added.
For MCP, there is already "_meta" for sending "extra" parameters/context, so thinking only about MCP tools it seems like there isn't really a need to send undefined parameters. So it would be nice to be able to warn or fail when they are present.
We could do this completely in the MCP C# SDK -- it just seems a little gross since IMHO it really should be done within the parameter binding logic.
I should add that the current behavior on tool parameters reflects the defaults of STJ in when it comes to property binding. In the context of MCP tool invocation, these do materialize when tool parameter types are themselves objects. So it's generally a good idea to keep the two mutually consistent.
So it would be nice to be able to warn or fail when they are present.
If an LLM satisfies all the requirements of the method but happens to also include additional values, I don't see why it's worth incurring the additional expense and token cost of roundtrips back to the LLM. Log? Sure. Fail? Why.
Also note that if this is something folks actually care about, they should use LLM features that guarantee it'll never happen, like OpenAI's strict function calling which uses constrained decoding to guarantee it'll never send a payload that doesn't match the schema.
it just seems a little gross since IMHO it really should be done within the parameter binding logic.
The MCP implementation has parameter binding logic.
opt-in
If you're really keen on adding new surface area here, I'll drop it, but this really feels to me like we're spending time on a non-problem.
It's also something someone can add on their own if they really want this behavior, in multiple ways. For example, writing a delegating McpServerTool that does the validation e.g.:
I should add that the current behavior on tool parameters reflects the defaults of STJ in when it comes to property binding. In the context of MCP tool invocation, these do materialize when tool parameter types are themselves objects. So it's generally a good idea to keep the two mutually consistent.
I forgot to add that this behavior in properties is configurable via the JsonSerializerOptions.UnmappedMemberHandling, so it is conceivable that we could make AIFunction parameters also honor that global setting.
I investigated using JsonSerializerOptions.UnmappedMemberHandling as a way to "opt-in" to strict checking of tool arguments. Unfortunately, this does not seem to work. Copilot's explanation for why it doesn't work is:
because UnmappedMemberHandling only applies when System.Text.Json is deserializing a JSON object into a .NET object contract. That is not what’s happening for the top-level tool arguments.
In this path, MCP first copies the incoming arguments object into an AIFunctionArguments dictionary. Then MEAI binds each declared parameter one-by-one by name: it looks for taskId, update, and markComplete. If markComplete is missing and has a default, it uses the default. The extra phase key is never bound, but it’s also never treated as an error,
because nothing deserializes the whole arguments payload against a single schema/object where “unknown member” detection would fire.
So the option can help for cases like a parameter typed as MyOptions where the JSON for that object parameter contains extra properties, but it does not catch stray top-level tool argument names in the current binding model.
I investigated using JsonSerializerOptions.UnmappedMemberHandling as a way to "opt-in" to strict checking of tool arguments. Unfortunately, this does not seem to work. Copilot's explanation for why it doesn't work is:
Right, I'm suggesting we update AIFunctionFactory in MEAI so that this setting is honored in parameter binding. It's technically a breaking change but perhaps it's worth it.
I'm unclear as to what the concern is. You specified a default value for markComplete, stating that it's optional for the AI to provide a value for.
If you want markComplete to be required, don't specify a default value.
Say there's search tool with a bunch of optional parameters for filtering down the returned information and one of the optional parameters is a boolean named "include_unverified" which defaults to false. We might want this tool to default to false because in the majority of cases we don't want unverified information returned.
If a human talks to an agent and they ask for unverified information and the agent calls our tool, but with a parameter named "unverified" set to true, then the tool will return only verified information, and no warning will be given to the agent or the human that the intended results weren't given. It is now up to the agent and/or the human to make sure that information marked unverified was actually included. However, both expect information marked as unverified to be included because that is what they asked the tool to do.
Also note that if this is something folks actually care about, they should use LLM features that guarantee it'll never happen, like OpenAI's strict function calling which uses constrained decoding to guarantee it'll never send a payload that doesn't match the schema.
As far as I understand, the only way to use strict function calling is if you disallow optional properties.
I'm going to reclassify this as a feature request instead of a bug. There's no clear violation of the spec, and even if we did decide that this was desirable (which is still in debate), we wouldn't change the default behavior.
If an LLM satisfies all the requirements of the method but happens to also include additional values, I don't see why it's worth incurring the additional expense and token cost of roundtrips back to the LLM. Log? Sure. Fail? Why.
Logging happens too late to catch an LLM tool-call mistake if the tool acts externally/destructively. Failing is the only thing that allows the LLM to understand that it is about to do something unreversible in a way that it did not intend.
Just to add my two cents from my experience and observations, LLMs don't seem to read the tool signature or description, they just hallucinate something and call in the hopes it makes sense.
I had to add argument validation in all my MCPs myself and be very strict, and return detailed error messages about non-existing arguments, and list all actual parameters.
The model then quickly corrects itself and re-calls the tool with the right arguments. This to me is the best workaround (fail fast) to the problem that it confabulates invalid tool calls in the first place. Very strange to me that invalid tool calls even make it to the MCP in the first place.
I have NO IDEA how anything would work (at least in the 3 MCPs that I maintain) if I hadn't had arg validation. The tool call would just silently not do what it was expected to do and the model would spin in circles trying things and wondering why nothing works and going off on random side quests and tangents chasing red-herrings.
Activity
stephentoub commentedon Apr 8, 2026
I'm unclear as to what the concern is. You specified a default value for markComplete, stating that it's optional for the AI to provide a value for.
If you want markComplete to be required, don't specify a default value.
softnado commentedon Apr 8, 2026
The concern here is that there is no signal indicate this happened and both end believe the intended operation been executed correctly while what actually been executed is not what should happen. Understand this can be tool implementation practice change but it is not obvious such behavior is what been implemented today.
stephentoub commentedon Apr 8, 2026
I don't understand. Your "expected behavior" was to error out or warning; if that's the behavior you want, just don't add the default value. The property in the tool schema is either required or optional, and if you specify optional (which is what you get with a default value), you're explicitly telling the model it's fine to not provide... it is not erroneous or warning-worthy.
It's no different in this regard from calling a method in C# with an optional parameter that has a default value: if the caller doesn't specify one, the callee gets the default value. It's not an error or a warning.
softnado commentedon Apr 8, 2026
Agree adjust the tool implementation pattern can get the expected behavior as suggested. The risk is that when put the parameter as is, tool developer not aware of ["default implementation" will silently drop the not used parameter without warning or failure], to get reminded that "Shouldn't use parameter with default" because model may pass something different intended for that parameter but with wrong name, then execution succeed but result is wrong. Maybe the attribute can allow customization of the parameters mapping. Or the document of MCP dotnet sdk can call out this specifically.
jeffhandley commentedon Apr 9, 2026
@mikekistler, @halter73, and I were chatting with @softnado offline about this and thought it would be valuable to assess the behavior across the SDKs.
Unmatched Tool Arguments: Cross-SDK Behavior Report (MCP SDKs)
Key Takeaways
Only 2 of 7 implementations reject unmatched arguments: The Go SDK and FastMCP. Both do so by generating schemas with
additionalProperties: falseand validating at runtime.The Rust SDK is a notable near-miss: It generates schemas with
additionalProperties: falsebut does not validate against them at runtime — serde silently drops unknown fields.The Java SDK is unique in passing through: Unlike other SDKs that silently drop extra arguments, the Java SDK preserves them in the arguments map passed to the handler. The handler could theoretically detect and act on them, but the SDK provides no signal.
The Go SDK's opt-out is per-tool only: There is no global configuration to disable strict validation. Users must either supply a custom
InputSchemaper tool or use the rawServer.AddToolAPI.5 of 7 implementations silently ignore/pass through: C#, TypeScript, Python, Java, and Rust all allow unrecognized arguments without any signal — each using their language's default deserialization behavior.
Relevant Specification Work
additionalProperties: falseon protocol types. Comment from @sirkirby (2026-04-08) explicitly raises the tool argument case and notes it has security implications. Comment from @pja-ant argues servers should not break on unknown fields for inter-version compatibility. This is the most active spec-level discussion about this topic.isError: true), which is relevant to how rejections should be communicated even if the spec doesn't yet mandate that they happen.mikekistler commentedon Apr 13, 2026
The issue here is specifically that undefined tool arguments are silently ignored.
If a client sends a tools/call request with argument names that are not part of the tool’s declared input schema, these arguments are silently dropped and the invocation may "succeed" instead of surfacing that mismatch. That can make it hard for both the tool author and the caller to detect that the request did not actually match the tool contract.
Exploring this with Copilot revealed that the actual binding of tool call arguments to method parameters, the natural place to detect undefined tool arguments, happens inside MEAI and not in the MCP C# SDK.
It is possible to implement a fix completely in the MCP C# SDK but that will duplicate functionality currently in MEAI.
I think my preference would be to make this an option in MEAI that the MCP C# SDK threads through.
I'd appreciate other folks thoughts on this.
cc: @jeffhandley @stephentoub @halter73
stephentoub commentedon Apr 13, 2026
MEAI specifically allows other things in the arguments dictionary because:
If we want to add an option that makes the AIFunctions created by AIFunctionFactory.Create stricter, we can. It would need to be opt-in, though. And even at the MCP level, it would really need to be opt-in.
Alternatively, if the goal is just to be able to get some insight into when this happens, MCP tools created by McpServerTool.Create (or the attribute) could log to an ILogger when passed more information than they'll use).
I'm still not convinced there's a problem to be solved here, though.
mikekistler commentedon Apr 13, 2026
I completely agree that this would be strictly opt-in at any level where it is added.
For MCP, there is already "_meta" for sending "extra" parameters/context, so thinking only about MCP tools it seems like there isn't really a need to send undefined parameters. So it would be nice to be able to warn or fail when they are present.
We could do this completely in the MCP C# SDK -- it just seems a little gross since IMHO it really should be done within the parameter binding logic.
eiriktsarpalis commentedon Apr 15, 2026
I should add that the current behavior on tool parameters reflects the defaults of STJ in when it comes to property binding. In the context of MCP tool invocation, these do materialize when tool parameter types are themselves objects. So it's generally a good idea to keep the two mutually consistent.
stephentoub commentedon Apr 15, 2026
If an LLM satisfies all the requirements of the method but happens to also include additional values, I don't see why it's worth incurring the additional expense and token cost of roundtrips back to the LLM. Log? Sure. Fail? Why.
Also note that if this is something folks actually care about, they should use LLM features that guarantee it'll never happen, like OpenAI's strict function calling which uses constrained decoding to guarantee it'll never send a payload that doesn't match the schema.
The MCP implementation has parameter binding logic.
If you're really keen on adding new surface area here, I'll drop it, but this really feels to me like we're spending time on a non-problem.
It's also something someone can add on their own if they really want this behavior, in multiple ways. For example, writing a delegating McpServerTool that does the validation e.g.:
or more broadly as a filter they can drop into their server and have it apply to all tool calls, e.g.
eiriktsarpalis commentedon Apr 15, 2026
I forgot to add that this behavior in properties is configurable via the
JsonSerializerOptions.UnmappedMemberHandling, so it is conceivable that we could make AIFunction parameters also honor that global setting.mikekistler commentedon Apr 16, 2026
I investigated using
JsonSerializerOptions.UnmappedMemberHandlingas a way to "opt-in" to strict checking of tool arguments. Unfortunately, this does not seem to work. Copilot's explanation for why it doesn't work is:eiriktsarpalis commentedon Apr 17, 2026
Right, I'm suggesting we update
AIFunctionFactoryin MEAI so that this setting is honored in parameter binding. It's technically a breaking change but perhaps it's worth it.Honor UnmappedMemberHandling in AIFunctionFactory parameter binding
dominicdill commentedon Apr 23, 2026
Say there's search tool with a bunch of optional parameters for filtering down the returned information and one of the optional parameters is a boolean named "include_unverified" which defaults to false. We might want this tool to default to false because in the majority of cases we don't want unverified information returned.
If a human talks to an agent and they ask for unverified information and the agent calls our tool, but with a parameter named "unverified" set to true, then the tool will return only verified information, and no warning will be given to the agent or the human that the intended results weren't given. It is now up to the agent and/or the human to make sure that information marked unverified was actually included. However, both expect information marked as unverified to be included because that is what they asked the tool to do.
As far as I understand, the only way to use strict function calling is if you disallow optional properties.
openai_function_calling
mikekistler commentedon Apr 24, 2026
I'm going to reclassify this as a feature request instead of a bug. There's no clear violation of the spec, and even if we did decide that this was desirable (which is still in debate), we wouldn't change the default behavior.
Honor UnmappedMemberHandling in AIFunctionFactory parameter binding (#…
Honor UnmappedMemberHandling in AIFunctionFactory parameter binding (#…
jnm2 commentedon May 12, 2026
Logging happens too late to catch an LLM tool-call mistake if the tool acts externally/destructively. Failing is the only thing that allows the LLM to understand that it is about to do something unreversible in a way that it did not intend.
KirillOsenkov commentedon May 12, 2026
Just to add my two cents from my experience and observations, LLMs don't seem to read the tool signature or description, they just hallucinate something and call in the hopes it makes sense.
I had to add argument validation in all my MCPs myself and be very strict, and return detailed error messages about non-existing arguments, and list all actual parameters.
The model then quickly corrects itself and re-calls the tool with the right arguments. This to me is the best workaround (fail fast) to the problem that it confabulates invalid tool calls in the first place. Very strange to me that invalid tool calls even make it to the MCP in the first place.
I have NO IDEA how anything would work (at least in the 3 MCPs that I maintain) if I hadn't had arg validation. The tool call would just silently not do what it was expected to do and the model would spin in circles trying things and wondering why nothing works and going off on random side quests and tangents chasing red-herrings.
My 2 cents!
eiriktsarpalis commentedon May 12, 2026
I think this can be closed given dotnet/extensions#7474 has been merged.