-
Notifications
You must be signed in to change notification settings - Fork 574
Add request options bag to high level requests and include Meta #970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a RequestOptions class to consolidate common optional parameters (Meta, JsonSerializerOptions, and ProgressToken) across MCP client and server request methods. This refactoring reduces method signature complexity and enables setting the _meta field in requests, which is needed for new MCP spec features like SEP-1686 (Tasks).
Key Changes
- Added new
RequestOptionsclass to hold optional request parameters - Updated all client and server request method signatures to accept
RequestOptionsinstead of individual parameters - Created
PingRequestParamsprotocol class for structured ping requests - Updated tests to use the new method signatures with named parameters or null for options
Reviewed Changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ModelContextProtocol.Core/RequestOptions.cs | New options bag class with Meta, JsonSerializerOptions, and ProgressToken properties |
| src/ModelContextProtocol.Core/Protocol/PingRequestParams.cs | New protocol parameter class for ping requests |
| src/ModelContextProtocol.Core/Client/McpClient.Methods.cs | Updated all client methods to accept RequestOptions and properly set Meta in request params |
| src/ModelContextProtocol.Core/Server/McpServer.Methods.cs | Updated SampleAsync and RequestRootsAsync to accept RequestOptions |
| src/ModelContextProtocol.Core/Client/McpClientExtensions.cs | Updated extension methods to wrap individual parameters in RequestOptions |
| src/ModelContextProtocol.Core/Server/McpServerExtensions.cs | Updated extension methods to forward calls with named parameters |
| src/ModelContextProtocol.Core/Client/McpClient*.cs | Updated client wrapper classes to construct RequestOptions when calling client methods |
| src/ModelContextProtocol.Core/McpJsonUtilities.cs | Added PingRequestParams to source generation context |
| tests/**/*.cs | Updated all test call sites to use new signatures with null or named parameters |
| samples/**/*.cs | Updated sample code to use named parameters for cancellationToken |
| { | ||
| ThrowIfRootsUnsupported(); | ||
|
|
||
| if (options?.Meta is not null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this kind of merging will need to be done in more than one place, we should have a helper for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is currently done in two places, though I'm not quite sure why it doesn't also occur for ElicitAsync. What would a helper look like for this code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this exposes a bug - the ElicitRequestParams should extend RequestParams, just as ListRootsRequestParams and CreateMessageRequestParams do. And ElicitAsync should accept RequestOptions to set Meta. I'll work on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm doing meta merging on the client side in the PR to extend McpClientTool with a WithMeta method. I think this merging meta objects might become more and more common, as meta seems to be a popular place to put new fields, and remain backwards compatible even with strict schema validation.
See also the MCP Apps extension in review, it uses meta (on the tool definition) to provide the app UI resource. So, I think a shared helper would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this exposes a bug - the ElicitRequestParams should extend RequestParams
Agreed. I think this was just an oversight which will be fixed as part of #1021
dc24e39 to
4ed55e6
Compare
| public JsonObject? Meta { | ||
| get; | ||
| set | ||
| { | ||
| // If progressToken is already set in Meta and not present in the new value, preserve it. | ||
| if (field?["progressToken"] is JsonValue existingProgressToken && | ||
| (value is null || !value.ContainsKey("progressToken"))) | ||
| { | ||
| // Create a copy to avoid modifying the input parameter | ||
| field = value is null ? [] : new JsonObject(value); | ||
| field["progressToken"] = existingProgressToken; | ||
| } | ||
| else | ||
| { | ||
| field = value; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephentoub This is my attempt to address the footgun of possibly setting Meta after setting ProgressToken. I'm not sure I love it but I think it's better than what we had previously.
| /// <exception cref="InvalidOperationException">The client does not support sampling.</exception> | ||
| public async Task<ChatResponse> SampleAsync( | ||
| IEnumerable<ChatMessage> messages, ChatOptions? options = default, CancellationToken cancellationToken = default) | ||
| IEnumerable<ChatMessage> messages, ChatOptions? chatOptions = default, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to rename this parameter to chatOptions to disambiguate with the RequestOptions option parameter that I've added on all the requests.
| get; | ||
| set | ||
| { | ||
| // If progressToken is already set in Meta and not present in the new value, preserve it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it help or hurt if we removed the setter from ProgressToken? Setting the PT would require setting Meta, and the ProgressToken getter would just be there as an accelerator. Presumably the 99.9% case of using the setter is in our own infrastructure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard to know if there are uses of the setter outside of the SDK. Within the SDK there wre only two places I had to change.
Even though this might break some users, I think this is a much better change as it eliminates the footgun that currently exists. I'll push this up shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for removing the ProgressToken setter. Having multiple property setters conditionally configuring the same fields is a recipe for confusion. With the current code, the following wouldn't reset _meta back to null even though it looks like it should.
callToolParams.Meta = new JsonObject()
{
["progressToken"] = "foo",
};
// The following is surprisingly a no-op. The "progressToken" stays.
callToolParams.Meta = null;Another option would be to store ProgressToken in its own field and only merge it into the _meta object or read it from the _meta object during JSON (de)serialization using a custom converter, but the downside is that you couldn't see the real value of _meta after setting the ProgressToken, and the ProgressToken property might be null even after someone manually sets it via the Meta property.
I think I prefer just removing the ProgressToken setter. In the unlikely event that non-SDK code is broken by the setter removal, at least it should be obvious what's going on with the compile time failure.
| /// <exception cref="InvalidOperationException">The client does not support sampling.</exception> | ||
| public ValueTask<CreateMessageResult> SampleAsync( | ||
| CreateMessageRequestParams request, CancellationToken cancellationToken = default) | ||
| CreateMessageRequestParams request, RequestOptions? options = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added RequestOptions here but I'm only using the Meta field from this. The JsonSerializationOptions and ProgressToken, if set, are ignored. That's because the existing code did not use either of these. But should it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eiriktsarpalis Should we be accepting / using JsonSerializationOptions on the server-to-client request methods (ListRootsAsync, SampleAsync, and ElicitAsync)? How would we use these options?
Motivation and Context
This PR adds the ability to set fields of the
_metaparameter in requests with the high-level request methods.It also refactored these methods to move several parameters common in request methods -- JsonSerializerOptions, ProgressToken, and the new Meta field -- to an options bag to make these signatures more concise.
How Has This Been Tested?
All tests pass with the revised method signatures.
Breaking Changes
This will be breaking (as currently implemented) for users that specify any of these (uncommonly used) parameters.
Types of changes
Checklist
Additional context
Some new features in the 2025-11-25 version of the MCP spec require setting and reading fields of
_meta, e.g. SEP-1686: Tasks. This change will make it easier for users to access these new features.