Class: ActionMCP::Server::SamplingRequest

Inherits:
Object
  • Object
show all
Defined in:
lib/action_mcp/server/sampling_request.rb

Constant Summary collapse

UNSET =
Object.new.freeze
TOOL_SCHEMA_OBJECT =
{
  "type" => "object",
  "required" => [ "type" ],
  "properties" => {
    "$schema" => { "type" => "string" },
    "properties" => {
      "type" => "object",
      "additionalProperties" => {
        "type" => "object",
        "properties" => {},
        "additionalProperties" => true
      }
    },
    "required" => { "type" => "array", "items" => { "type" => "string" } },
    "type" => { "type" => "string", "const" => "object" }
  }
}.freeze
SAMPLING_DEFINITIONS =
Content::Validation::DEFINITIONS.merge(
  "ProgressToken" => { "type" => %w[string integer] },
  "Role" => { "type" => "string", "enum" => %w[assistant user] },
  "ToolSchema" => TOOL_SCHEMA_OBJECT,
  "ModelHint" => {
    "type" => "object",
    "properties" => { "name" => { "type" => "string" } }
  },
  "ModelPreferences" => {
    "type" => "object",
    "properties" => {
      "hints" => {
        "type" => "array",
        "items" => { "$ref" => "#/$defs/ModelHint" }
      },
      "costPriority" => { "type" => "number", "minimum" => 0, "maximum" => 1 },
      "speedPriority" => { "type" => "number", "minimum" => 0, "maximum" => 1 },
      "intelligencePriority" => { "type" => "number", "minimum" => 0, "maximum" => 1 }
    }
  },
  "ToolAnnotations" => {
    "type" => "object",
    "properties" => {
      "destructiveHint" => { "type" => "boolean" },
      "idempotentHint" => { "type" => "boolean" },
      "openWorldHint" => { "type" => "boolean" },
      "readOnlyHint" => { "type" => "boolean" },
      "title" => { "type" => "string" }
    }
  },
  "ToolExecution" => {
    "type" => "object",
    "properties" => {
      "taskSupport" => {
        "type" => "string",
        "enum" => %w[forbidden optional required]
      }
    }
  },
  "Tool" => {
    "type" => "object",
    "required" => %w[inputSchema name],
    "properties" => Content::Validation::META_PROPERTY.merge(
      "annotations" => { "$ref" => "#/$defs/ToolAnnotations" },
      "description" => { "type" => "string" },
      "execution" => { "$ref" => "#/$defs/ToolExecution" },
      "icons" => { "type" => "array", "items" => { "$ref" => "#/$defs/Icon" } },
      "inputSchema" => { "$ref" => "#/$defs/ToolSchema" },
      "name" => { "type" => "string" },
      "outputSchema" => { "$ref" => "#/$defs/ToolSchema" },
      "title" => { "type" => "string" }
    )
  },
  "ToolChoice" => {
    "type" => "object",
    "properties" => {
      "mode" => { "type" => "string", "enum" => %w[auto none required] }
    }
  },
  "TaskMetadata" => {
    "type" => "object",
    "properties" => { "ttl" => { "type" => "integer" } }
  },
  "ToolUseContent" => {
    "type" => "object",
    "required" => %w[id input name type],
    "properties" => Content::Validation::META_PROPERTY.merge(
      "id" => { "type" => "string" },
      "input" => { "type" => "object" },
      "name" => { "type" => "string" },
      "type" => { "type" => "string", "const" => "tool_use" }
    )
  },
  "ToolResultContent" => {
    "type" => "object",
    "required" => %w[content toolUseId type],
    "properties" => Content::Validation::META_PROPERTY.merge(
      "content" => {
        "type" => "array",
        "items" => { "$ref" => "#/$defs/ContentBlock" }
      },
      "isError" => { "type" => "boolean" },
      "structuredContent" => { "type" => "object" },
      "toolUseId" => { "type" => "string" },
      "type" => { "type" => "string", "const" => "tool_result" }
    )
  },
  "SamplingMessageContentBlock" => {
    "anyOf" => %w[TextContent ImageContent AudioContent ToolUseContent ToolResultContent].map do |name|
      { "$ref" => "#/$defs/#{name}" }
    end
  },
  "SamplingMessage" => {
    "type" => "object",
    "required" => %w[content role],
    "properties" => Content::Validation::META_PROPERTY.merge(
      "content" => {
        "anyOf" => [
          { "$ref" => "#/$defs/SamplingMessageContentBlock" },
          {
            "type" => "array",
            "items" => { "$ref" => "#/$defs/SamplingMessageContentBlock" }
          }
        ]
      },
      "role" => { "$ref" => "#/$defs/Role" }
    )
  },
  "CreateMessageRequestParams" => {
    "type" => "object",
    "required" => %w[maxTokens messages],
    "properties" => {
      "_meta" => {
        "type" => "object",
        "properties" => {
          "progressToken" => { "$ref" => "#/$defs/ProgressToken" }
        }
      },
      "includeContext" => {
        "type" => "string",
        "enum" => %w[allServers none thisServer]
      },
      "maxTokens" => { "type" => "integer" },
      "messages" => {
        "type" => "array",
        "items" => { "$ref" => "#/$defs/SamplingMessage" }
      },
      "metadata" => { "type" => "object" },
      "modelPreferences" => { "$ref" => "#/$defs/ModelPreferences" },
      "stopSequences" => { "type" => "array", "items" => { "type" => "string" } },
      "systemPrompt" => { "type" => "string" },
      "task" => { "$ref" => "#/$defs/TaskMetadata" },
      "temperature" => { "type" => "number" },
      "toolChoice" => { "$ref" => "#/$defs/ToolChoice" },
      "tools" => { "type" => "array", "items" => { "$ref" => "#/$defs/Tool" } }
    }
  }
).freeze
PARAMS_SCHEMA =
{
  "$schema" => "https://json-schema.org/draft/2020-12/schema",
  "$ref" => "#/$defs/CreateMessageRequestParams",
  "$defs" => SAMPLING_DEFINITIONS
}.freeze
PARAMS_SCHEMER =
JSONSchemer.schema(
  PARAMS_SCHEMA,
  formats: Content::Validation::FORMAT_VALIDATORS
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ SamplingRequest

Returns a new instance of SamplingRequest.

Yields:

  • (_self)

Yield Parameters:



321
322
323
324
325
326
327
328
329
330
331
# File 'lib/action_mcp/server/sampling_request.rb', line 321

def initialize
  @messages = self.class.messages.map(&:deep_dup)
  @system_prompt = self.class.system_prompt
  @context = self.class.include_context
  @model_hints = self.class.model_hints.deep_dup
  @intelligence_priority = self.class.intelligence_priority
  @max_tokens = self.class.max_tokens
  @temperature = self.class.temperature

  yield self if block_given?
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



319
320
321
# File 'lib/action_mcp/server/sampling_request.rb', line 319

def context
  @context
end

#cost_priorityObject

Returns the value of attribute cost_priority.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def cost_priority
  @cost_priority
end

#intelligence_priorityObject

Returns the value of attribute intelligence_priority.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def intelligence_priority
  @intelligence_priority
end

#max_tokensObject

Returns the value of attribute max_tokens.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def max_tokens
  @max_tokens
end

#messagesObject

Returns the value of attribute messages.



319
320
321
# File 'lib/action_mcp/server/sampling_request.rb', line 319

def messages
  @messages
end

#metadataObject

Returns the value of attribute metadata.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def 
  @metadata
end

#model_hintsObject

Returns the value of attribute model_hints.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def model_hints
  @model_hints
end

#request_metaObject

Returns the value of attribute request_meta.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def request_meta
  @request_meta
end

#speed_priorityObject

Returns the value of attribute speed_priority.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def speed_priority
  @speed_priority
end

#stop_sequencesObject

Returns the value of attribute stop_sequences.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def stop_sequences
  @stop_sequences
end

#system_promptObject

Returns the value of attribute system_prompt.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def system_prompt
  @system_prompt
end

#taskObject

Returns the value of attribute task.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def task
  @task
end

#temperatureObject

Returns the value of attribute temperature.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def temperature
  @temperature
end

#tool_choiceObject

Returns the value of attribute tool_choice.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def tool_choice
  @tool_choice
end

#toolsObject

Returns the value of attribute tools.



316
317
318
# File 'lib/action_mcp/server/sampling_request.rb', line 316

def tools
  @tools
end

Class Method Details

.configure {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



178
179
180
# File 'lib/action_mcp/server/sampling_request.rb', line 178

def configure
  yield self
end

.include_context(value = UNSET) ⇒ Object



192
193
194
195
# File 'lib/action_mcp/server/sampling_request.rb', line 192

def include_context(value = UNSET)
  @default_context = value unless value.equal?(UNSET)
  @default_context
end

.intelligence_priority(value = UNSET) ⇒ Object



202
203
204
205
# File 'lib/action_mcp/server/sampling_request.rb', line 202

def intelligence_priority(value = UNSET)
  @default_intelligence_priority = value unless value.equal?(UNSET)
  @default_intelligence_priority.nil? ? 0.9 : @default_intelligence_priority
end

.max_tokens(value = UNSET) ⇒ Object



207
208
209
210
# File 'lib/action_mcp/server/sampling_request.rb', line 207

def max_tokens(value = UNSET)
  @default_max_tokens = value unless value.equal?(UNSET)
  @default_max_tokens.nil? ? 500 : @default_max_tokens
end

.messages(value = UNSET) ⇒ Object



182
183
184
185
# File 'lib/action_mcp/server/sampling_request.rb', line 182

def messages(value = UNSET)
  @default_messages = value.map { |message| normalize_message(message) } unless value.equal?(UNSET)
  @default_messages ||= []
end

.model_hints(value = UNSET) ⇒ Object



197
198
199
200
# File 'lib/action_mcp/server/sampling_request.rb', line 197

def model_hints(value = UNSET)
  @default_model_hints = value unless value.equal?(UNSET)
  @default_model_hints ||= []
end

.system_prompt(value = UNSET) ⇒ Object



187
188
189
190
# File 'lib/action_mcp/server/sampling_request.rb', line 187

def system_prompt(value = UNSET)
  @default_system_prompt = value unless value.equal?(UNSET)
  @default_system_prompt
end

.temperature(value = UNSET) ⇒ Object



212
213
214
215
# File 'lib/action_mcp/server/sampling_request.rb', line 212

def temperature(value = UNSET)
  @default_temperature = value unless value.equal?(UNSET)
  @default_temperature.nil? ? 0.7 : @default_temperature
end

.validate_params!(params) ⇒ Object

Raises:

  • (ArgumentError)


217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/action_mcp/server/sampling_request.rb', line 217

def validate_params!(params)
  raise ArgumentError, "Sampling request params must be an object" unless params.is_a?(Hash)

  wire_params = params.deep_stringify_keys
  errors = PARAMS_SCHEMER.validate(wire_params).to_a
  unless errors.empty?
    pointers = errors.map { |error| error["data_pointer"].presence || "/" }.uniq
    raise ArgumentError, "Sampling request must match MCP 2025-11-25 at #{pointers.join(', ')}"
  end

  validate_tool_conversation!(wire_params.fetch("messages"))
  params
end

Instance Method Details

#add_message(content, role: "user", _meta: nil) ⇒ Object



341
342
343
344
345
346
347
348
349
350
# File 'lib/action_mcp/server/sampling_request.rb', line 341

def add_message(content, role: "user", _meta: nil)
  if content.respond_to?(:to_h) && !content.is_a?(Hash) && !content.is_a?(Array)
    content = content.to_h
  end
  content = Content::Text.new(content, annotations: nil).to_h if content.is_a?(String)

  message = { role: role, content: content }
  message[:_meta] = _meta if _meta
  @messages << message
end

#include_context=(value) ⇒ Object



337
338
339
# File 'lib/action_mcp/server/sampling_request.rb', line 337

def include_context=(value)
  @context = value
end

#to_hObject



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/action_mcp/server/sampling_request.rb', line 352

def to_h
  params = {
    messages: messages.map { |message| message.slice(:role, :content, :_meta).compact },
    systemPrompt: system_prompt,
    includeContext: context,
    modelPreferences: {
      hints: model_hints.map { |hint| hint.is_a?(Hash) ? hint : { name: hint } },
      costPriority: cost_priority,
      speedPriority: speed_priority,
      intelligencePriority: intelligence_priority
    }.compact,
    maxTokens: max_tokens,
    temperature: temperature,
    stopSequences: stop_sequences,
    metadata: ,
    tools: tools,
    toolChoice: tool_choice,
    task: task,
    _meta: request_meta
  }.compact

  self.class.validate_params!(params)
end