Skip to content

Implement SEP-1577: Sampling With Tools - #628

Merged
alexhancock merged 2 commits into
modelcontextprotocol:mainfrom
DaleSeo:SEP-1577
Feb 6, 2026
Merged

Implement SEP-1577: Sampling With Tools#628
alexhancock merged 2 commits into
modelcontextprotocol:mainfrom
DaleSeo:SEP-1577

Conversation

@DaleSeo

@DaleSeoDaleSeo commented Jan 24, 2026

Copy link
Copy Markdown
Member

Closes#552

Motivation and Context

This PR implements SEP-1577: Sampling With Tools, enabling MCP servers to run agentic loops using the client's LLM while maintaining user supervision.

Key additions:

  • ToolChoice / ToolChoiceMode - Control tool selection behavior
  • ToolUseContent / ToolResultContent - Tool calling content types
  • SamplingContent<T> - Single or array content wrapper
  • SamplingMessageContent - Unified content enum with ToolUse and ToolResult variants
  • SamplingCapability - Structured capability with tools and context sub-capabilities

Reference implementations:

How Has This Been Tested?

  • New unit tests covering serialization, deserialization, and API usage for all new types
  • All existing tests updated and passing
  • Backward compatibility tests verifying old JSON formats still deserialize correctly

Breaking Changes

The type signature of SamplingMessage.content changed from Content to SamplingContent<SamplingMessageContent>.

Migration Made Easy

Convenience constructors (recommended):

// Beforelet msg = SamplingMessage{role:Role::User,content:Content::text("hi")};// After - use the new helper methodslet msg = SamplingMessage::user_text("hi");let msg = SamplingMessage::assistant_text("Hello");

Converting existing Content values:

use std::convert::TryInto;let content:Content = Content::text("hello");// Convert to SamplingMessageContentlet sampling_content:SamplingMessageContent = content.try_into()?;// Or convert directly to SamplingContent<SamplingMessageContent>let content:Content = Content::text("hello");let wrapped:SamplingContent<SamplingMessageContent> = content.try_into()?;

Note: TryFrom is used because Content::Resource and Content::ResourceLink variants are not supported in sampling messages.

Wire Format Compatibility

  • Deserialization is backward compatible - Old JSON format (single content object) still deserializes correctly
  • ClientCapabilities.sampling - Empty {} JSON still deserializes to SamplingCapability

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

// Advertise capabilitylet capabilities = ClientCapabilities::builder().enable_sampling().enable_sampling_tools().build();// Create request with toolslet params = CreateMessageRequestParams{messages:vec![SamplingMessage::user_text("What's the weather?")],tools:Some(vec![weather_tool]),tool_choice:Some(ToolChoice::auto()),max_tokens:1000,
..Default::default()};// Handle tool use response if result.stop_reason.as_deref() == Some(CreateMessageResult::STOP_REASON_TOOL_USE){for content in result.message.content.iter(){ifletSome(tool_use) = content.as_tool_use(){// Execute tool and continue conversation}}}

@github-actionsgithub-actionsBot added T-test Testing related changes T-config Configuration file changes T-core Core library changes T-examples Example code changes T-model Model/data structure changes T-documentation Documentation improvements and removed T-documentation Documentation improvements labels Jan 24, 2026
@DaleSeo
DaleSeo marked this pull request as ready for review January 25, 2026 02:37
@DaleSeo

Copy link
Copy Markdown
MemberAuthor

Hi @alexhancock, could you please take a look at this PR? Thanks!

@alexhancock
alexhancock self-requested a review January 30, 2026 20:20
@alexhancock

Copy link
Copy Markdown
Contributor

@DaleSeo Sorry it took me a bit. I'm glad to have support for this, but a little worried about the breaking change. Can you think of any alternatives that would keep compat, or make it a but lighter for updaters?

Interested to discuss!

@DaleSeo

Copy link
Copy Markdown
MemberAuthor

Thanks for the feedback, @alexhancock! I've made a few updates to simplify the migration:

  1. I added TryFrom conversions for existing Content values:
use std::convert::TryInto;let content:Content = Content::text("hello");let sampling_content:SamplingMessageContent = content.try_into()?;
  1. The wire format is backward compatible. The old JSON (single content object) still deserializes correctly, so existing serialized data will remain intact.

  2. I created convenience constructors to make common cases easier:

// BeforeSamplingMessage{role:Role::User,content:Content::text("hi")}// AfterSamplingMessage::user_text("hi")

The type change is unavoidable to support tool use and result content types from SEP-1577, but I think the migration path is now reasonable. Most users can either use the new constructors or add .try_into() to their existing code.

Let me know if you have any other suggestions!

@alexhancockalexhancock 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.

Thanks @DaleSeo, approving but also leaving this list of recommendations.

Generated by Opus 4.5 comparing the diff to https://modelcontextprotocol.io/community/seps/1577--sampling-with-tools.md

## SEP-1577 Implementation Review - Recommended Changes
1. **Missing capability validation (MUST requirement)**: Add runtime validation to throw an error when `tools` or `toolChoice` are provided but `clientCapabilities.sampling.tools` is missing.
2. **Tool use/result balancing not enforced (MUST requirement)**: Add validation that every assistant message with `ToolUseContent` (id: X) is followed by a user message with `ToolResultContent` (toolUseId: X).
3. **Tool result content mixing not enforced**: Spec states "SamplingMessage with tool result content blocks MUST NOT contain other content types" - add validation for this.
4. **Role-content type mismatch possible**: `ToolUseContent` should only appear in assistant messages, `ToolResultContent` only in user messages. Currently not enforced - consider runtime validation.
5. **CreateMessageResult.role not constrained**: Spec says `role: "assistant"` (literal), but implementation allows any `Role`. Minor, but could be tightened.
6. **Dead code**: `AssistantMessageContent` and `UserMessageContent` enums in `content.rs` are defined but never used. Remove or use them.

The validations seem good to do as followups?

@alexhancock
alexhancock merged commit 8bd3fcb into modelcontextprotocol:mainFeb 6, 2026
11 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Feb 5, 2026
@DaleSeoDaleSeo mentioned this pull request Feb 6, 2026
9 tasks
@DaleSeo

DaleSeo commented Feb 6, 2026

Copy link
Copy Markdown
MemberAuthor

Thanks for the thorough review, @alexhancock! You're right on all those points.

For context on how things ended up this way: I was primarily referencing other SDK implementations and it turns out they don't fully enforce these MUST requirements either, so I carried over the same gaps. I initially planned to use separate AssistantMessageContent and UserMessageContent enums to enforce the role constraints at the type level, but I switched to a single SamplingMessageContent enum for simpler serialization and easier conversion from Content. I forgot to go back and remove the original enums, so they ended up as dead code. The runtime validation that should have replaced the type-level enforcement also fell through the cracks in that refactor.

Also, thanks for the tip on fetching the SEP markdown directly. I didn't know about that trick and it'll be really useful for future contributions.

I've put up a follow-up PR: #646.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-examplesExample code changesT-modelModel/data structure changesT-testTesting related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SEP-1577: Sampling With Tools

2 participants

@DaleSeo@alexhancock