Skip to content
Dick Davis edited this page Mar 13, 2026 · 2 revisions

Tools

The ModelContextProtocol::Server::Tool base class allows subclasses to define a tool that the MCP client can use.

Define the tool properties and schemas, then implement the call method to build your tool. Any arguments passed to the tool from the MCP client will be available in the arguments hash with symbol keys (e.g., arguments[:argument_name]), and any context values provided in the server configuration will be available in the context hash. Use the respond_with instance method to ensure your prompt responds with appropriately formatted response data.

You can also send MCP log messages to clients from within your tool by calling a valid logger level method on the client_logger and passing a string message. For server-side debugging and monitoring, use the server_logger to write logs that are not sent to clients.

Tool Definition

Use the define block to set tool properties and configure schemas.

Property Description
name The programmatic name of the tool
title Human-readable display name
description Short description of what the tool does
input_schema JSON schema block for validating tool inputs
output_schema JSON schema block for validating structured content outputs
annotations Block using a DSL to define tool annotations (e.g., read_only_hint, destructive_hint, open_world_hint)
security_schemes Block returning an array of security scheme hashes (e.g., noauth, oauth2)

Tool Methods

Define your tool properties and schemas, implement the call method using content helpers and respond_with to serialize responses. You can wrap long running operations in a cancellable block to allow clients to cancel the request. Also, you can automatically send progress notifications to clients by wrapping long-running operations in a progressable block.

Method Context Description
define Class definition Block for defining tool metadata and schemas
call Instance method Main method to implement tool logic and build response
cancellable Within call Wrap long-running operations to allow client cancellation (e.g., cancellable { slow_operation })
progressable Within call Wrap long-running operations to send clients progress notifications (e.g., progressable { slow_operation })
respond_with Within call Return properly formatted response data with various content types

Content Blocks

Use content blocks to properly format the content included in tool responses.

Method Context Description
text_content Within call Create text content block
image_content Within call Create image content block (requires data: and mime_type:)
audio_content Within call Create audio content block (requires data: and mime_type:)
embedded_resource_content Within call Create embedded resource content block (requires resource:)
resource_link Within call Create resource link content block (requires name: and uri:)

Response Types

Tools can return different types of responses using respond_with.

Response Type Usage Description
structured_content: respond_with structured_content: data Return structured data validated against output schema
content: respond_with content: content_block Return single content block
content: respond_with content: [content_blocks] Return array of mixed content blocks
error: respond_with error: "message" Return tool error response

Available Instance Variables

Arguments from MCP clients and server context are available, along with logging capabilities.

Variable Context Description
arguments Within call Hash containing client-provided arguments (symbol keys)
context Within call Hash containing server configuration context values
client_logger Within call Client logger instance for sending MCP log messages (e.g., client_logger.info("message"))
server_logger Within call Server logger instance for debugging and monitoring (e.g., server_logger.debug("message"))

Examples

Structured Content Response

This is an example of a tool that returns structured content validated by an output schema:

class TestToolWithStructuredContentResponse < ModelContextProtocol::Server::Tool
  define do
    # The name of the tool for programmatic use
    name "get_weather_data"
    # The human-readable tool name for display in UI
    title "Weather Data Retriever"
    # A short description of what the tool does
    description "Get current weather data for a location"
    # The JSON schema for validating tool inputs
    input_schema do
      {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "City name or zip code"
          }
        },
        required: ["location"]
      }
    end
    # The JSON schema for validating structured content
    output_schema do
      {
        type: "object",
        properties: {
          temperature: {
            type: "number",
            description: "Temperature in celsius"
          },
          conditions: {
            type: "string",
            description: "Weather conditions description"
          },
          humidity: {
            type: "number",
            description: "Humidity percentage"
          }
        },
        required: ["temperature", "conditions", "humidity"]
      }
    end
  end

  def call
    # Use values provided by the server as context
    user_id = context[:user_id]
    client_logger.info("Initiating request for user #{user_id}...")

    # Use values provided by clients as tool arguments
    location = arguments[:location]
    client_logger.info("Getting weather data for #{location}...")

    # Returns a hash that validates against the output schema
    weather_data = get_weather_data(location)

    # Respond with structured content
    respond_with structured_content: weather_data
  end

  private

  # Simulate calling an external API to get weather data for the provided input
  def get_weather_data(location)
    {
      temperature: 22.5,
      conditions: "Partly cloudy",
      humidity: 65
    }
  end
end

Text Response

This is an example tool that returns a text response:

class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
  define do
    name "double"
    title "Number Doubler"
    description "Doubles the provided number"
    input_schema do
      {
        type: "object",
        properties: {
          number: {
            type: "string"
          }
        },
        required: ["number"]
      }
    end
  end

  def call
    client_logger.info("Silly user doesn't know how to double a number")
    number = arguments[:number].to_i
    calculation = number * 2

    user_id = context[:user_id]
    salutation = user_id ? "User #{user_id}, " : ""
    text_content = text_content(text: salutation << "#{number} doubled is #{calculation}")

    respond_with content: text_content
  end
end

Image Response

This is an example of a tool that returns an image:

class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
  define do
    name "custom-chart-generator"
    description "Generates a chart in various formats"
    input_schema do
      {
        type: "object",
        properties: {
          chart_type: {
            type: "string",
            description: "Type of chart (pie, bar, line)"
          },
          format: {
            type: "string",
            description: "Image format (jpg, svg, etc)"
          }
        },
        required: ["chart_type", "format"]
      }
    end
  end

  def call
    # Map format to mime type
    mime_type = case arguments[:format].downcase
    when "svg"
      "image/svg+xml"
    when "jpg", "jpeg"
      "image/jpeg"
    else
      "image/png"
    end

    # In a real implementation, we would generate an actual chart
    # This is a small valid base64 encoded string (represents "test")
    data = "dGVzdA=="
    image_content = image_content(data:, mime_type:)
    respond_with content: image_content
  end
end

Embedded Resource Response

This is an example of a tool that returns an embedded resource response:

class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
  define do
    name "resource-finder"
    description "Finds a resource given a name"
    input_schema do
      {
        type: "object",
        properties: {
          name: {
            type: "string",
            description: "The name of the resource"
          }
        },
        required: ["name"]
      }
    end
  end

  RESOURCE_MAPPINGS = {
    test_annotated_resource: TestAnnotatedResource,
    test_binary_resource: TestBinaryResource,
    test_resource: TestResource
  }.freeze

  def call
    name = arguments[:name]
    resource_klass = RESOURCE_MAPPINGS[name.downcase.to_sym]
    unless resource_klass
      return respond_with :error, text: "Resource `#{name}` not found"
    end

    resource_data = resource_klass.call(client_logger, context)

    respond_with content: embedded_resource_content(resource: resource_data)
  end
end

Mixed Content Response

This is an example of a tool that returns mixed content:

class TestToolWithMixedContentResponse < ModelContextProtocol::Server::Tool
  define do
    name "get_temperature_history"
    description "Gets comprehensive temperature history for a zip code"
    input_schema do
      {
        type: "object",
        properties: {
          zip: {
            type: "string"
          }
        },
        required: ["zip"]
      }
    end
  end

  def call
    client_logger.info("Getting comprehensive temperature history data")

    zip = arguments[:zip]
    temperature_history = retrieve_temperature_history(zip:)
    temperature_history_block = text_content(text: temperature_history.join(", "))

    temperature_chart = generate_weather_history_chart(temperature_history)
    temperature_chart_block = image_content(
      data: temperature_chart[:base64_chart_data],
      mime_type: temperature_chart[:mime_type]
    )

    respond_with content: [temperature_history_block, temperature_chart_block]
  end

  private

  def retrieve_temperature_history(zip:)
    # Simulates a call to an API or DB to retrieve weather history
    [85.2, 87.4, 89.0, 95.3, 96.0]
  end

  def generate_weather_history_chart(history)
    # SImulate a call to generate a chart given the weather history
    {
      base64_chart_data: "dGVzdA==",
      mime_type: "image/png"
    }
  end
end

Error Response

This is an example of a tool that returns a tool error response:

class TestToolWithToolErrorResponse < ModelContextProtocol::Server::Tool
  define do
    name "api-caller"
    description "Makes calls to external APIs"
    input_schema do
      {
        type: "object",
        properties: {
          api_endpoint: {
            type: "string",
            description: "API endpoint URL"
          },
          method: {
            type: "string",
            description: "HTTP method (GET, POST, etc)"
          }
        },
        required: ["api_endpoint", "method"]
      }
    end
  end

  def call
    # Simulate an API call failure
    respond_with error: "Failed to call API at #{arguments[:api_endpoint]}: Connection timed out"
  end
end

Cancellable Operation

This is an example of a tool that allows a client to cancel a long-running operation:

class TestToolWithCancellableSleep < ModelContextProtocol::Server::Tool
  define do
    name "cancellable_sleep"
    title "Cancellable Sleep Tool"
    description "Sleep for 3 seconds with cancellation support"
    input_schema do
      {
        type: "object",
        properties: {},
        additionalProperties: false
      }
    end
  end

  def call
    client_logger.info("Starting 3 second sleep operation")

    result = cancellable do
      sleep 3
      "Sleep completed successfully"
    end

    respond_with content: text_content(text: result)
  end
end

Progress and Cancellation

This is an example of a tool that automatically sends progress notifications to the client and allows the client to cancel the operation:

class TestToolWithProgressableAndCancellable < ModelContextProtocol::Server::Tool
  define do
    name "test_tool_with_progressable_and_cancellable"
    description "A test tool that demonstrates combined progressable and cancellable functionality"

    input_schema do
      {
        type: "object",
        properties: {
          max_duration: {
            type: "number",
            description: "Expected maximum duration in seconds"
          },
          work_steps: {
            type: "number",
            description: "Number of work steps to perform"
          }
        },
        required: ["max_duration"]
      }
    end
  end

  def call
    max_duration = arguments[:max_duration] || 10
    work_steps = arguments[:work_steps] || 10
    client_logger.info("Starting progressable call with max_duration=#{max_duration}, work_steps=#{work_steps}")

    result = progressable(max_duration:, message: "Processing #{work_steps} items") do
      cancellable do
        processed_items = []

        work_steps.times do |i|
          sleep(max_duration / work_steps.to_f)
          processed_items << "item_#{i + 1}"
        end

        processed_items
      end
    end

    response = text_content(text: "Successfully processed #{result.length} items: #{result.join(", ")}")

    respond_with content: response
  end
end

Tool Annotations

Tools can use annotations to provide hints about their behavior to clients. The annotations block uses a validated DSL with the following methods:

Method Type Description
read_only_hint Boolean If true, the tool does not modify its environment (default: false)
destructive_hint Boolean If true, the tool may perform destructive updates (default: true). Only meaningful when read_only_hint is false
idempotent_hint Boolean If true, calling the tool repeatedly with the same arguments has no additional effect (default: false). Only meaningful when read_only_hint is false
open_world_hint Boolean If true, the tool may interact with external entities (default: true)
class TestToolWithAnnotations < ModelContextProtocol::Server::Tool
  define do
    name "fetch"
    description "Fetch the full contents of a single resource"
    input_schema do
      {
        type: "object",
        properties: {
          id: {
            type: "string",
            description: "Unique identifier of the resource to fetch"
          }
        },
        required: ["id"]
      }
    end
    annotations do
      read_only_hint true
      open_world_hint false
    end
  end

  def call
    id = arguments[:id]
    client_logger.info("Fetching resource #{id}")

    respond_with content: text_content(text: "Contents of resource #{id}")
  end
end

Security Schemes

This is an example of a tool that declares security schemes to indicate authentication requirements:

class TestToolWithSecuritySchemes < ModelContextProtocol::Server::Tool
  define do
    name "search"
    description "Search indexed documents"
    input_schema do
      {
        type: "object",
        properties: {
          q: {
            type: "string",
            description: "Search query"
          }
        },
        required: ["q"]
      }
    end
    security_schemes do
      [
        {type: "noauth"},
        {type: "oauth2", scopes: ["search.read"]}
      ]
    end
  end

  def call
    query = arguments[:q]
    client_logger.info("Searching for: #{query}")

    respond_with content: text_content(text: "Results for '#{query}'")
  end
end

Clone this wiki locally