Skip to content

Add SEP-1577 sampling with tools support#1018

Closed
chemicL wants to merge 1 commit into
mainfrom
sep-1577-sampling-with-tools
Closed

Add SEP-1577 sampling with tools support#1018
chemicL wants to merge 1 commit into
mainfrom
sep-1577-sampling-with-tools

Conversation

@chemicL

@chemicL chemicL commented Jun 10, 2026

Copy link
Copy Markdown
Member

Introduces parallel V2 sampling types (SamplingMessageV2, CreateMessageWithToolsRequest/Result, ToolUseContent, ToolResultContent, ToolChoice) alongside the existing V1 types, leaving the legacy wire format byte-identical. Servers call createMessageWithTools on the exchange; a version gate refuses multi-content or tools payloads when the negotiated protocol version is older than 2025-11-25. Clients opt in via samplingWithTools(handler) on the builder, which advertises sampling.tools in the capability handshake. StopReason gains TOOL_USE.

Introduces parallel V2 sampling types (SamplingMessageV2,
CreateMessageWithToolsRequest/Result, ToolUseContent, ToolResultContent,
ToolChoice) alongside the existing V1 types, leaving the legacy wire format
byte-identical. Servers call createMessageWithTools on the exchange; a version
gate refuses multi-content or tools payloads when the negotiated protocol
version is older than 2025-11-25. Clients opt in via samplingWithTools(handler)
on the builder, which advertises sampling.tools in the capability handshake.
StopReason gains TOOL_USE.

Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>

@Kehrlann Kehrlann left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a "MUST" about tool use / result balance that seems to be missing ; but I'm not fully sure where this should even be enforced. I guess this can be handled by the implementer?

Also, it'd be really neat to have at least one happy-path integration test.

@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true)
public record Sampling() {
public record Sampling(@JsonProperty("tools") SamplingTools tools) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEP-1577 also defines a context property for sampling (schema)

is this ignored on purpose?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this property more closely made me realize that it wasn't included because it might be removed.. Looking more closely I realized that Sampling itself is being deprecated and will be removed. Thank you for your careful review, but with the information from the upcoming spec I will close this PR...

return Mono.error(
new IllegalStateException("Received sampling request, but no sampling handler is registered."));
}
return this.samplingHandler.apply(v1Request).cast(Object.class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the incoming request has no tools or toolChoice (both of which are optional), then we route to v2. Otherwise, we route to v1.

A server could send a request with or without tools, but the constructor only allows us to define one of samplingWithToolsHandler / samplingHandler (gated with if (withTools) / else). So if the server sends a request without tools to a v2-enabled client, we'll throw IllegalStateException because we're missing samplingHandler

if (this.clientCapabilities.sampling() == null) {
return Mono.error(new IllegalStateException("Client must be configured with sampling capabilities"));
}
if (this.clientCapabilities.sampling().tools() == null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tools is null, users cannot use this method. But this is the only way to create multi-content sampling requests, even without tools.

* {@link McpSchema#PROTOCOL_VERSION_SAMPLING_WITH_TOOLS}, or when the version is
* {@code null} (unknown — treated as pre-SEP-1577).
*/
private static boolean isOlderThanSamplingWithToolsVersion(String negotiatedVersion) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to scratch my head a bit at the name.
Consider renaming to supportsSamplingWithTools ?

* {@link io.modelcontextprotocol.server.McpAsyncServerExchange#createMessageWithTools}
* must have negotiated at least this version.
*/
public static final String PROTOCOL_VERSION_SAMPLING_WITH_TOOLS = "2025-11-25";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should hint at particular feature gates within the schema. The schema reference doesn't mention this kind of behavior , I'd prefer having it closer to the feature itself.

Consider renaming to PROTOCOL_2025_11_25 and keeping the "sampling with tools" concept in the server instead

Comment on lines +3115 to +3117
public ToolChoice {
Assert.hasText(mode, "mode must not be empty");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Validate that mode is one of the allowed modes
  • "toolChoice": {} is a param for a sampling request (schema), and this would fail deserializing it . From what I understand, it should default to auto in that case?

*/
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonIgnoreProperties(ignoreUnknown = true)
public record SamplingMessageV2( // @formatter:off

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is confusing, since the it does not match the spec, but I can't think of anything better. Consider documenting why we need this.

Is SamplingMessage v1 intended to be replaced by v2 in the future?

@JsonProperty("role") Role role,
@JsonProperty("content")
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
List<Content> content) { // @formatter:on

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema seems to indicate we should extend Meta

Comment on lines +155 to +158
void setNegotiatedProtocolVersion(String version) {
this.negotiatedProtocolVersion.set(version);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot change over the lifetime of the session, can it? Should we guard against it?

@chemicL

chemicL commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Thank you for the careful review @Kehrlann. In light of the discoveries described in #693 (comment) I am closing this PR.

@chemicL chemicL closed this Jun 10, 2026
@chemicL chemicL deleted the sep-1577-sampling-with-tools branch June 10, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants