Skip to content

Support sampling/createMessage per MCP specification#282

Open
koic wants to merge 1 commit intomodelcontextprotocol:mainfrom
koic:support_sampling
Open

Support sampling/createMessage per MCP specification#282
koic wants to merge 1 commit intomodelcontextprotocol:mainfrom
koic:support_sampling

Conversation

@koic
Copy link
Copy Markdown
Member

@koic koic commented Mar 31, 2026

Motivation and Context

The MCP specification defines sampling/createMessage to request LLM completions from the client. The Ruby SDK did not yet support server-to-client requests, so the server had no way to ask the client to sample an LLM. This caused the tools-call-sampling conformance test to fail.

This adds Server#create_sampling_message to send a sampling/createMessage request to the client via the transport layer. The method validates that the client declared the sampling capability during initialization, and optionally checks the sampling.tools capability when tools or tool_choice parameters are provided.

On the transport side, Transport#send_request is introduced as an abstract method, with implementations in both StdioTransport and StreamableHTTPTransport. Each transport assigns a unique request ID, sends the JSON-RPC request to the client, and blocks until the client responds.

Client capabilities are stored per session via ServerSession, so validation is scoped to the originating client. ServerSession#create_sampling_message uses build_sampling_params for capability validation and sends the request directly through the transport with the session ID, following the same pattern as notify_progress.

Server#create_sampling_message is the public API for single-client transports (e.g., StdioTransport).

For multi-client transports (e.g., StreamableHTTPTransport), ServerSession#create_sampling_message must be used to route requests to the correct client. Tools using server_context.create_sampling_message automatically route to the correct session regardless of transport.

StreamableHTTPTransport#send_request requires session_id to prevent broadcasting sampling requests to all connected clients. Missing or invalid session IDs raise explicit errors instead of silently failing.

The include_context parameter is soft-deprecated in the MCP specification but is included for compatibility with existing clients that declare the sampling.context capability. Python and TypeScript SDKs also retain this parameter.

Ref: https://modelcontextprotocol.io/specification/latest/server/utilities/sampling

How Has This Been Tested?

Added comprehensive tests in test/mcp/server_sampling_test.rb covering required and optional parameters, capability validation, error handling, nil-param omission, per-session capability isolation, and HTTP init capability scoping.

Added transport tests in test/mcp/server/transports/stdio_transport_test.rb and test/mcp/server/transports/streamable_http_transport_test.rb for send_request round-trip behavior, error responses, stateless mode rejection, missing session_id, invalid session_id, and no-active-stream error.

Conformance: tools-call-sampling is removed from expected failures. The conformance server's TestSampling tool now calls server_context.create_sampling_message instead of returning a stub error.

This aligns the Ruby SDK with the MCP specification and other SDK implementations.

Breaking Change

None for end users.
Transport#send_request is a new abstract method. ServerSession#create_sampling_message is a new method.

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

## Motivation and Context

The MCP specification defines `sampling/createMessage` to request LLM completions from the client.
The Ruby SDK did not yet support server-to-client requests,
so the server had no way to ask the client to sample an LLM.
This caused the `tools-call-sampling` conformance test to fail.

This adds `Server#create_sampling_message` to send a `sampling/createMessage` request
to the client via the transport layer. The method validates that the client declared
the `sampling` capability during initialization, and optionally checks the
`sampling.tools` capability when tools or tool_choice parameters are provided.

On the transport side, `Transport#send_request` is introduced as an abstract method,
with implementations in both `StdioTransport` and `StreamableHTTPTransport`.
Each transport assigns a unique request ID, sends the JSON-RPC request to the client,
and blocks until the client responds.

Client capabilities are stored per session via `ServerSession`, so validation is scoped
to the originating client. `ServerSession#create_sampling_message` uses
`build_sampling_params` for capability validation and sends the request directly through
the transport with the session ID, following the same pattern as `notify_progress`.

`Server#create_sampling_message` is the public API for single-client transports
(e.g., StdioTransport).

For multi-client transports (e.g., StreamableHTTPTransport),
`ServerSession#create_sampling_message` must be used to route requests to the correct client.
Tools using `server_context.create_sampling_message` automatically route to the correct session
regardless of transport.

`StreamableHTTPTransport#send_request` requires `session_id` to prevent broadcasting sampling requests
to all connected clients. Missing or invalid session IDs raise explicit errors instead of silently failing.

The `include_context` parameter is soft-deprecated in the MCP specification but is included
for compatibility with existing clients that declare the `sampling.context` capability.
Python and TypeScript SDKs also retain this parameter.

Ref: https://modelcontextprotocol.io/specification/latest/server/utilities/sampling

## How Has This Been Tested?

Added comprehensive tests in `test/mcp/server_sampling_test.rb` covering required and optional parameters,
capability validation, error handling, nil-param omission, per-session capability isolation,
and HTTP init capability scoping.

Added transport tests in `test/mcp/server/transports/stdio_transport_test.rb` and
`test/mcp/server/transports/streamable_http_transport_test.rb` for `send_request` round-trip behavior,
error responses, stateless mode rejection, missing session_id, invalid session_id, and no-active-stream error.

Conformance: `tools-call-sampling` is removed from expected failures. The conformance server's `TestSampling`
tool now calls `server_context.create_sampling_message` instead of returning a stub error.

This aligns the Ruby SDK with the MCP specification and other SDK implementations.

## Breaking Change

None for end users.
`Transport#send_request` is a new abstract method. `ServerSession#create_sampling_message` is a new method.
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.

1 participant