{"id":28264237,"url":"https://github.com/modelcontextprotocol/ruby-sdk","last_synced_at":"2026-04-24T07:06:04.238Z","repository":{"id":294213429,"uuid":"940241698","full_name":"modelcontextprotocol/ruby-sdk","owner":"modelcontextprotocol","description":"The official Ruby SDK for the Model Context Protocol.","archived":false,"fork":false,"pushed_at":"2026-04-17T17:06:33.000Z","size":825,"stargazers_count":782,"open_issues_count":9,"forks_count":108,"subscribers_count":19,"default_branch":"main","last_synced_at":"2026-04-18T13:02:53.640Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ruby.sdk.modelcontextprotocol.io","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/modelcontextprotocol.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-02-27T21:00:23.000Z","updated_at":"2026-04-17T17:06:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"e0a74c68-e963-4481-bb79-f6477b86f41d","html_url":"https://github.com/modelcontextprotocol/ruby-sdk","commit_stats":null,"previous_names":["modelcontextprotocol/ruby-sdk"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/modelcontextprotocol/ruby-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fruby-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fruby-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fruby-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fruby-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelcontextprotocol","download_url":"https://codeload.github.com/modelcontextprotocol/ruby-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelcontextprotocol%2Fruby-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32143979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T15:33:03.595Z","status":"ssl_error","status_checked_at":"2026-04-22T15:30:42.712Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-20T09:10:16.310Z","updated_at":"2026-04-24T07:06:04.229Z","avatar_url":"https://github.com/modelcontextprotocol.png","language":"Ruby","funding_links":[],"categories":["📚 Projects (1974 total)","Ruby","Open Source","Mcp Server Directories \u0026 Lists","MCP Frameworks and libraries","📦 Other","SDKs","MCP Servers \u0026 Protocol"],"sub_categories":["MCP Servers","MCP","Ruby"],"readme":"# MCP Ruby SDK [![Gem Version](https://img.shields.io/gem/v/mcp)](https://rubygems.org/gems/mcp) [![Apache 2.0 licensed](https://img.shields.io/badge/license-Apache%202.0-blue)](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/LICENSE) [![CI](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml)\n\nThe official Ruby SDK for Model Context Protocol servers and clients.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'mcp'\n```\n\nAnd then execute:\n\n```console\n$ bundle install\n```\n\nOr install it yourself as:\n\n```console\n$ gem install mcp\n```\n\nYou may need to add additional dependencies depending on which features you wish to access.\n\n## Building an MCP Server\n\nThe `MCP::Server` class is the core component that handles JSON-RPC requests and responses.\nIt implements the Model Context Protocol specification, handling model context requests and responses.\n\n### Key Features\n\n- Implements JSON-RPC 2.0 message handling\n- Supports protocol initialization and capability negotiation\n- Manages tool registration and invocation\n- Supports prompt registration and execution\n- Supports resource registration and retrieval\n- Supports stdio \u0026 Streamable HTTP (including SSE) transports\n- Supports notifications for list changes (tools, prompts, resources)\n- Supports roots (server-to-client filesystem boundary queries)\n- Supports sampling (server-to-client LLM completion requests)\n- Supports cursor-based pagination for list operations\n\n### Supported Methods\n\n- `initialize` - Initializes the protocol and returns server capabilities\n- `ping` - Simple health check\n- `tools/list` - Lists all registered tools and their schemas\n- `tools/call` - Invokes a specific tool with provided arguments\n- `prompts/list` - Lists all registered prompts and their schemas\n- `prompts/get` - Retrieves a specific prompt by name\n- `resources/list` - Lists all registered resources and their schemas\n- `resources/read` - Retrieves a specific resource by name\n- `resources/templates/list` - Lists all registered resource templates and their schemas\n- `resources/subscribe` - Subscribes to updates for a specific resource\n- `resources/unsubscribe` - Unsubscribes from updates for a specific resource\n- `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs\n- `roots/list` - Requests filesystem roots from the client (server-to-client)\n- `sampling/createMessage` - Requests LLM completion from the client (server-to-client)\n- `elicitation/create` - Requests user input from the client (server-to-client)\n\n### Usage\n\n#### Stdio Transport\n\nIf you want to build a local command-line application, you can use the stdio transport:\n\n```ruby\nrequire \"mcp\"\n\n# Create a simple tool\nclass ExampleTool \u003c MCP::Tool\n  description \"A simple example tool that echoes back its arguments\"\n  input_schema(\n    properties: {\n      message: { type: \"string\" },\n    },\n    required: [\"message\"]\n  )\n\n  class \u003c\u003c self\n    def call(message:, server_context:)\n      MCP::Tool::Response.new([{\n        type: \"text\",\n        text: \"Hello from example tool! Message: #{message}\",\n      }])\n    end\n  end\nend\n\n# Set up the server\nserver = MCP::Server.new(\n  name: \"example_server\",\n  tools: [ExampleTool],\n)\n\n# Create and start the transport\ntransport = MCP::Server::Transports::StdioTransport.new(server)\ntransport.open\n```\n\nYou can run this script and then type in requests to the server at the command line.\n\n```console\n$ ruby examples/stdio_server.rb\n{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"ping\"}\n{\"jsonrpc\":\"2.0\",\"id\":\"2\",\"method\":\"tools/list\"}\n{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"method\":\"tools/call\",\"params\":{\"name\":\"example_tool\",\"arguments\":{\"message\":\"Hello\"}}}\n```\n\n#### Streamable HTTP Transport\n\n`MCP::Server::Transports::StreamableHTTPTransport` is a standard Rack app, so it can be mounted in any Rack-compatible framework.\nThe following examples show two common integration styles in Rails.\n\n\u003e [!IMPORTANT]\n\u003e `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,\n\u003e so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).\n\u003e Multi-process configurations (Unicorn, or Puma with `workers \u003e 0`) fork separate processes that\n\u003e do not share memory, which breaks session management and SSE connections.\n\u003e\n\u003e When running multiple server instances behind a load balancer, configure your load balancer to use\n\u003e sticky sessions (session affinity) so that requests with the same `Mcp-Session-Id` header are always\n\u003e routed to the same instance.\n\u003e\n\u003e Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.\n\n##### Rails (mount)\n\n`StreamableHTTPTransport` is a Rack app that can be mounted directly in Rails routes:\n\n```ruby\n# config/routes.rb\nserver = MCP::Server.new(\n  name: \"my_server\",\n  title: \"Example Server Display Name\",\n  version: \"1.0.0\",\n  instructions: \"Use the tools of this server as a last resort\",\n  tools: [SomeTool, AnotherTool],\n  prompts: [MyPrompt],\n)\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server)\n\nRails.application.routes.draw do\n  mount transport =\u003e \"/mcp\"\nend\n```\n\n`mount` directs all HTTP methods on `/mcp` to the transport. `StreamableHTTPTransport` internally dispatches\n`POST` (client-to-server JSON-RPC messages, with responses optionally streamed via SSE),\n`GET` (optional standalone SSE stream for server-to-client messages), and `DELETE` (session termination) per\nthe [MCP Streamable HTTP transport spec](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http),\nso no additional route configuration is needed.\n\n##### Rails (controller)\n\nWhile the mount approach creates a single server at boot time, the controller approach creates a new server per request.\nThis allows you to customize tools, prompts, or configuration based on the request (e.g., different tools per route).\n\n`StreamableHTTPTransport#handle_request` returns proper HTTP status codes (e.g., 202 Accepted for notifications):\n\n```ruby\nclass McpController \u003c ActionController::API\n  def create\n    server = MCP::Server.new(\n      name: \"my_server\",\n      title: \"Example Server Display Name\",\n      version: \"1.0.0\",\n      instructions: \"Use the tools of this server as a last resort\",\n      tools: [SomeTool, AnotherTool],\n      prompts: [MyPrompt],\n      server_context: { user_id: current_user.id },\n    )\n    # Since the `MCP-Session-Id` is not shared across requests, `stateless: true` is set.\n    transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)\n    status, headers, body = transport.handle_request(request)\n\n    render(json: body.first, status: status, headers: headers)\n  end\nend\n```\n\n### Configuration\n\nThe gem can be configured using the `MCP.configure` block:\n\n```ruby\nMCP.configure do |config|\n  config.exception_reporter = -\u003e(exception, server_context) {\n    # Your exception reporting logic here\n    # For example with Bugsnag:\n    Bugsnag.notify(exception) do |report|\n      report.add_metadata(:model_context_protocol, server_context)\n    end\n  }\n\n  config.around_request = -\u003e(data, \u0026request_handler) {\n    logger.info(\"Start: #{data[:method]}\")\n    request_handler.call\n    logger.info(\"Done: #{data[:method]}, tool: #{data[:tool_name]}\")\n  }\nend\n```\n\nor by creating an explicit configuration and passing it into the server.\nThis is useful for systems where an application hosts more than one MCP server but\nthey might require different configurations.\n\n```ruby\nconfiguration = MCP::Configuration.new\nconfiguration.exception_reporter = -\u003e(exception, server_context) {\n  # Your exception reporting logic here\n  # For example with Bugsnag:\n  Bugsnag.notify(exception) do |report|\n    report.add_metadata(:model_context_protocol, server_context)\n  end\n}\n\nconfiguration.around_request = -\u003e(data, \u0026request_handler) {\n  logger.info(\"Start: #{data[:method]}\")\n  request_handler.call\n  logger.info(\"Done: #{data[:method]}, tool: #{data[:tool_name]}\")\n}\n\nserver = MCP::Server.new(\n  # ... all other options\n  configuration:,\n)\n```\n\n### Server Context and Configuration Block Data\n\n#### `server_context`\n\nThe `server_context` is a user-defined hash that is passed into the server instance and made available to tool and prompt calls.\nIt can be used to provide contextual information such as authentication state, user IDs, or request-specific data.\n\n**Type:**\n\n```ruby\nserver_context: { [String, Symbol] =\u003e Any }\n```\n\n**Example:**\n\n```ruby\nserver = MCP::Server.new(\n  name: \"my_server\",\n  server_context: { user_id: current_user.id, request_id: request.uuid }\n)\n```\n\nThis hash is then passed as the `server_context` keyword argument to tool and prompt calls.\nNote that exception and instrumentation callbacks do not receive this user-defined hash.\nSee the relevant sections below for the arguments they receive.\n\n#### Request-specific `_meta` Parameter\n\nThe MCP protocol supports a special [`_meta` parameter](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields) in requests that allows clients to pass request-specific metadata. The server automatically extracts this parameter and makes it available to tools and prompts as a nested field within the `server_context`.\n\n**Access Pattern:**\n\nWhen a client includes `_meta` in the request params, it becomes available as `server_context[:_meta]`:\n\n```ruby\nclass MyTool \u003c MCP::Tool\n  def self.call(message:, server_context:)\n    # Access provider-specific metadata\n    session_id = server_context.dig(:_meta, :session_id)\n    request_id = server_context.dig(:_meta, :request_id)\n\n    # Access server's original context\n    user_id = server_context.dig(:user_id)\n\n    MCP::Tool::Response.new([{\n      type: \"text\",\n      text: \"Processing for user #{user_id} in session #{session_id}\"\n    }])\n  end\nend\n```\n\n**Client Request Example:**\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"my_tool\",\n    \"arguments\": { \"message\": \"Hello\" },\n    \"_meta\": {\n      \"session_id\": \"abc123\",\n      \"request_id\": \"req_456\"\n    }\n  }\n}\n```\n\n#### Configuration Block Data\n\n##### Exception Reporter\n\nThe exception reporter receives:\n\n- `exception`: The Ruby exception object that was raised\n- `server_context`: A hash describing where the failure occurred (e.g., `{ request: \u003craw JSON-RPC request\u003e }`\n  for request handling, `{ notification: \"tools_list_changed\" }` for notification delivery).\n  This is not the user-defined `server_context` passed to `Server.new`.\n\n**Signature:**\n\n```ruby\nexception_reporter = -\u003e(exception, server_context) { ... }\n```\n\n##### Around Request\n\nThe `around_request` hook wraps request handling, allowing you to execute code before and after each request.\nThis is useful for Application Performance Monitoring (APM) tracing, logging, or other observability needs.\n\nThe hook receives a `data` hash and a `request_handler` block. You must call `request_handler.call` to execute the request:\n\n**Signature:**\n\n```ruby\naround_request = -\u003e(data, \u0026request_handler) { request_handler.call }\n```\n\n**`data` availability by timing:**\n\n- Before `request_handler.call`: `method`\n- After `request_handler.call`: `tool_name`, `tool_arguments`, `prompt_name`, `resource_uri`, `error`, `client`\n- Not available inside `around_request`: `duration` (added after `around_request` returns)\n\n\u003e [!NOTE]\n\u003e `tool_name`, `prompt_name` and `resource_uri` may only be populated for the corresponding request methods\n\u003e (`tools/call`, `prompts/get`, `resources/read`), and may not be set depending on how the request is handled\n\u003e (for example, `prompt_name` is not recorded when the prompt is not found).\n\u003e `duration` is added after `around_request` returns, so it is not visible from within the hook.\n\n**Example:**\n\n```ruby\nMCP.configure do |config|\n  config.around_request = -\u003e(data, \u0026request_handler) {\n    logger.info(\"Start: #{data[:method]}\")\n    request_handler.call\n    logger.info(\"Done: #{data[:method]}, tool: #{data[:tool_name]}\")\n  }\nend\n```\n\n##### Instrumentation Callback (soft-deprecated)\n\n\u003e [!NOTE]\n\u003e `instrumentation_callback` is soft-deprecated. Use `around_request` instead.\n\u003e\n\u003e To migrate, wrap the call in `begin/ensure` so the callback still runs when the request fails:\n\u003e\n\u003e ```ruby\n\u003e # Before\n\u003e config.instrumentation_callback = -\u003e(data) { log(data) }\n\u003e\n\u003e # After\n\u003e config.around_request = -\u003e(data, \u0026request_handler) do\n\u003e   request_handler.call\n\u003e ensure\n\u003e   log(data)\n\u003e end\n\u003e ```\n\u003e\n\u003e Note that `data[:duration]` is not available inside `around_request`.\n\u003e If you need it, measure elapsed time yourself within the hook, or keep using `instrumentation_callback`.\n\nThe instrumentation callback is called after each request finishes, whether successfully or with an error.\nIt receives a hash with the following possible keys:\n\n- `method`: (String) The protocol method called (e.g., \"ping\", \"tools/list\")\n- `tool_name`: (String, optional) The name of the tool called\n- `tool_arguments`: (Hash, optional) The arguments passed to the tool\n- `prompt_name`: (String, optional) The name of the prompt called\n- `resource_uri`: (String, optional) The URI of the resource called\n- `error`: (String, optional) Error code if a lookup failed\n- `duration`: (Float) Duration of the call in seconds\n- `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request\n\n**Signature:**\n\n```ruby\ninstrumentation_callback = -\u003e(data) { ... }\n```\n\n### Server Protocol Version\n\nThe server's protocol version can be overridden using the `protocol_version` keyword argument:\n\n```ruby\nconfiguration = MCP::Configuration.new(protocol_version: \"2024-11-05\")\nMCP::Server.new(name: \"test_server\", configuration: configuration)\n```\n\nIf no protocol version is specified, the latest stable version will be applied by default.\nThe latest stable version includes new features from the [draft version](https://modelcontextprotocol.io/specification/draft).\n\nThis will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`:\n\n```ruby\nMCP::Configuration.new(protocol_version: nil)\n```\n\nIf an invalid `protocol_version` value is set, an `ArgumentError` is raised.\n\nBe sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.\n\n### Exception Reporting\n\nThe exception reporter receives two arguments:\n\n- `exception`: The Ruby exception object that was raised\n- `server_context`: A hash containing contextual information about where the error occurred\n\nThe server_context hash includes:\n\n- For tool calls: `{ tool_name: \"name\", arguments: { ... } }`\n- For general request handling: `{ request: { ... } }`\n\nWhen an exception occurs:\n\n1. The exception is reported via the configured reporter\n2. For tool calls, a generic error response is returned to the client: `{ error: \"Internal error occurred\", isError: true }`\n3. For other requests, the exception is re-raised after reporting\n\nIf no exception reporter is configured, a default no-op reporter is used that silently ignores exceptions.\n\n### Tools\n\nMCP spec includes [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) which provide functionality to LLM apps.\n\nThis gem provides a `MCP::Tool` class that can be used to create tools in three ways:\n\n1. As a class definition:\n\n```ruby\nclass MyTool \u003c MCP::Tool\n  title \"My Tool\"\n  description \"This tool performs specific functionality...\"\n  input_schema(\n    properties: {\n      message: { type: \"string\" },\n    },\n    required: [\"message\"]\n  )\n  output_schema(\n    properties: {\n      result: { type: \"string\" },\n      success: { type: \"boolean\" },\n      timestamp: { type: \"string\", format: \"date-time\" }\n    },\n    required: [\"result\", \"success\", \"timestamp\"]\n  )\n  annotations(\n    read_only_hint: true,\n    destructive_hint: false,\n    idempotent_hint: true,\n    open_world_hint: false,\n    title: \"My Tool\"\n  )\n\n  def self.call(message:, server_context:)\n    MCP::Tool::Response.new([{ type: \"text\", text: \"OK\" }])\n  end\nend\n\ntool = MyTool\n```\n\n2. By using the `MCP::Tool.define` method with a block:\n\n```ruby\ntool = MCP::Tool.define(\n  name: \"my_tool\",\n  title: \"My Tool\",\n  description: \"This tool performs specific functionality...\",\n  annotations: {\n    read_only_hint: true,\n    title: \"My Tool\"\n  }\n) do |args, server_context:|\n  MCP::Tool::Response.new([{ type: \"text\", text: \"OK\" }])\nend\n```\n\n3. By using the `MCP::Server#define_tool` method with a block:\n\n```ruby\nserver = MCP::Server.new\nserver.define_tool(\n  name: \"my_tool\",\n  description: \"This tool performs specific functionality...\",\n  annotations: {\n    title: \"My Tool\",\n    read_only_hint: true\n  }\n) do |args, server_context:|\n  Tool::Response.new([{ type: \"text\", text: \"OK\" }])\nend\n```\n\nThe server_context parameter is the server_context passed into the server and can be used to pass per request information,\ne.g. around authentication state.\n\n### Tool Annotations\n\nTools can include annotations that provide additional metadata about their behavior. The following annotations are supported:\n\n- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true\n- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false\n- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true\n- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false\n- `title`: A human-readable title for the tool\n\nAnnotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.\n\n\u003e [!NOTE]\n\u003e This **Tool Annotations** feature is supported starting from `protocol_version: '2025-03-26'`.\n\n### Tool Output Schemas\n\nTools can optionally define an `output_schema` to specify the expected structure of their results. This works similarly to how `input_schema` is defined and can be used in three ways:\n\n1. **Class definition with output_schema:**\n\n```ruby\nclass WeatherTool \u003c MCP::Tool\n  tool_name \"get_weather\"\n  description \"Get current weather for a location\"\n\n  input_schema(\n    properties: {\n      location: { type: \"string\" },\n      units: { type: \"string\", enum: [\"celsius\", \"fahrenheit\"] }\n    },\n    required: [\"location\"]\n  )\n\n  output_schema(\n    properties: {\n      temperature: { type: \"number\" },\n      condition: { type: \"string\" },\n      humidity: { type: \"integer\" }\n    },\n    required: [\"temperature\", \"condition\", \"humidity\"]\n  )\n\n  def self.call(location:, units: \"celsius\", server_context:)\n    # Call weather API and structure the response\n    api_response = WeatherAPI.fetch(location, units)\n    weather_data = {\n      temperature: api_response.temp,\n      condition: api_response.description,\n      humidity: api_response.humidity_percent\n    }\n\n    output_schema.validate_result(weather_data)\n\n    MCP::Tool::Response.new([{\n      type: \"text\",\n      text: weather_data.to_json\n    }])\n  end\nend\n```\n\n2. **Using Tool.define with output_schema:**\n\n```ruby\ntool = MCP::Tool.define(\n  name: \"calculate_stats\",\n  description: \"Calculate statistics for a dataset\",\n  input_schema: {\n    properties: {\n      numbers: { type: \"array\", items: { type: \"number\" } }\n    },\n    required: [\"numbers\"]\n  },\n  output_schema: {\n    properties: {\n      mean: { type: \"number\" },\n      median: { type: \"number\" },\n      count: { type: \"integer\" }\n    },\n    required: [\"mean\", \"median\", \"count\"]\n  }\n) do |args, server_context:|\n  # Calculate statistics and validate against schema\n  MCP::Tool::Response.new([{ type: \"text\", text: \"Statistics calculated\" }])\nend\n```\n\n3. **Using OutputSchema objects:**\n\n```ruby\nclass DataTool \u003c MCP::Tool\n  output_schema MCP::Tool::OutputSchema.new(\n    properties: {\n      success: { type: \"boolean\" },\n      data: { type: \"object\" }\n    },\n    required: [\"success\"]\n  )\nend\n```\n\nOutput schema may also describe an array of objects:\n\n```ruby\nclass WeatherTool \u003c MCP::Tool\n  output_schema(\n    type: \"array\",\n    items: {\n      properties: {\n        temperature: { type: \"number\" },\n        condition: { type: \"string\" },\n        humidity: { type: \"integer\" }\n      },\n      required: [\"temperature\", \"condition\", \"humidity\"]\n    }\n  )\nend\n```\n\nPlease note: in this case, you must provide `type: \"array\"`. The default type\nfor output schemas is `object`.\n\nMCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) specifies that:\n\n- **Server Validation**: Servers MUST provide structured results that conform to the output schema\n- **Client Validation**: Clients SHOULD validate structured results against the output schema\n- **Better Integration**: Enables strict schema validation, type information, and improved developer experience\n- **Backward Compatibility**: Tools returning structured content SHOULD also include serialized JSON in a TextContent block\n\nThe output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.\n\n### Tool Responses with Structured Content\n\nTools can return structured data alongside text content using the `structured_content` parameter.\n\nThe structured content will be included in the JSON-RPC response as the `structuredContent` field.\n\n```ruby\nclass WeatherTool \u003c MCP::Tool\n  description \"Get current weather and return structured data\"\n\n  def self.call(location:, units: \"celsius\", server_context:)\n    # Call weather API and structure the response\n    api_response = WeatherAPI.fetch(location, units)\n    weather_data = {\n      temperature: api_response.temp,\n      condition: api_response.description,\n      humidity: api_response.humidity_percent\n    }\n\n    output_schema.validate_result(weather_data)\n\n    MCP::Tool::Response.new(\n      [{\n        type: \"text\",\n        text: weather_data.to_json\n      }],\n      structured_content: weather_data\n    )\n  end\nend\n```\n\n### Tool Responses with Errors\n\nTools can return error information alongside text content using the `error` parameter.\n\nThe error will be included in the JSON-RPC response as the `isError` field.\n\n```ruby\nclass WeatherTool \u003c MCP::Tool\n  description \"Get current weather and return structured data\"\n\n  def self.call(server_context:)\n    # Do something here\n    content = {}\n\n    MCP::Tool::Response.new(\n      [{\n        type: \"text\",\n        text: content.to_json\n      }],\n      structured_content: content,\n      error: true\n    )\n  end\nend\n```\n\n### Prompts\n\nMCP spec includes [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.\n\nThe `MCP::Prompt` class provides three ways to create prompts:\n\n1. As a class definition with metadata:\n\n```ruby\nclass MyPrompt \u003c MCP::Prompt\n  prompt_name \"my_prompt\"  # Optional - defaults to underscored class name\n  title \"My Prompt\"\n  description \"This prompt performs specific functionality...\"\n  arguments [\n    MCP::Prompt::Argument.new(\n      name: \"message\",\n      title: \"Message Title\",\n      description: \"Input message\",\n      required: true\n    )\n  ]\n  meta({ version: \"1.0\", category: \"example\" })\n\n  class \u003c\u003c self\n    def template(args, server_context:)\n      MCP::Prompt::Result.new(\n        description: \"Response description\",\n        messages: [\n          MCP::Prompt::Message.new(\n            role: \"user\",\n            content: MCP::Content::Text.new(\"User message\")\n          ),\n          MCP::Prompt::Message.new(\n            role: \"assistant\",\n            content: MCP::Content::Text.new(args[\"message\"])\n          )\n        ]\n      )\n    end\n  end\nend\n\nprompt = MyPrompt\n```\n\n2. Using the `MCP::Prompt.define` method:\n\n```ruby\nprompt = MCP::Prompt.define(\n  name: \"my_prompt\",\n  title: \"My Prompt\",\n  description: \"This prompt performs specific functionality...\",\n  arguments: [\n    MCP::Prompt::Argument.new(\n      name: \"message\",\n      title: \"Message Title\",\n      description: \"Input message\",\n      required: true\n    )\n  ],\n  meta: { version: \"1.0\", category: \"example\" }\n) do |args, server_context:|\n  MCP::Prompt::Result.new(\n    description: \"Response description\",\n    messages: [\n      MCP::Prompt::Message.new(\n        role: \"user\",\n        content: MCP::Content::Text.new(\"User message\")\n      ),\n      MCP::Prompt::Message.new(\n        role: \"assistant\",\n        content: MCP::Content::Text.new(args[\"message\"])\n      )\n    ]\n  )\nend\n```\n\n3. Using the `MCP::Server#define_prompt` method:\n\n```ruby\nserver = MCP::Server.new\nserver.define_prompt(\n  name: \"my_prompt\",\n  description: \"This prompt performs specific functionality...\",\n  arguments: [\n    Prompt::Argument.new(\n      name: \"message\",\n      title: \"Message Title\",\n      description: \"Input message\",\n      required: true\n    )\n  ],\n  meta: { version: \"1.0\", category: \"example\" }\n) do |args, server_context:|\n  Prompt::Result.new(\n    description: \"Response description\",\n    messages: [\n      Prompt::Message.new(\n        role: \"user\",\n        content: Content::Text.new(\"User message\")\n      ),\n      Prompt::Message.new(\n        role: \"assistant\",\n        content: Content::Text.new(args[\"message\"])\n      )\n    ]\n  )\nend\n```\n\nThe server_context parameter is the server_context passed into the server and can be used to pass per request information,\ne.g. around authentication state or user preferences.\n\n### Key Components\n\n- `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag\n- `MCP::Prompt::Message` - Represents a message in the conversation with a role and content\n- `MCP::Prompt::Result` - The output of a prompt template containing description and messages\n- `MCP::Content::Text` - Text content for messages\n\n### Usage\n\nRegister prompts with the MCP server:\n\n```ruby\nserver = MCP::Server.new(\n  name: \"my_server\",\n  prompts: [MyPrompt],\n  server_context: { user_id: current_user.id },\n)\n```\n\nThe server will handle prompt listing and execution through the MCP protocol methods:\n\n- `prompts/list` - Lists all registered prompts and their schemas\n- `prompts/get` - Retrieves and executes a specific prompt with arguments\n\n### Resources\n\nMCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).\n\n### Reading Resources\n\nThe `MCP::Resource` class provides a way to register resources with the server.\n\n```ruby\nresource = MCP::Resource.new(\n  uri: \"https://example.com/my_resource\",\n  name: \"my-resource\",\n  title: \"My Resource\",\n  description: \"Lorem ipsum dolor sit amet\",\n  mime_type: \"text/html\",\n)\n\nserver = MCP::Server.new(\n  name: \"my_server\",\n  resources: [resource],\n)\n```\n\nThe server must register a handler for the `resources/read` method to retrieve a resource dynamically.\n\n```ruby\nserver.resources_read_handler do |params|\n  [{\n    uri: params[:uri],\n    mimeType: \"text/plain\",\n    text: \"Hello from example resource! URI: #{params[:uri]}\"\n  }]\nend\n```\n\notherwise `resources/read` requests will be a no-op.\n\n### Resource Templates\n\nThe `MCP::ResourceTemplate` class provides a way to register resource templates with the server.\n\n```ruby\nresource_template = MCP::ResourceTemplate.new(\n  uri_template: \"https://example.com/my_resource_template\",\n  name: \"my-resource-template\",\n  title: \"My Resource Template\",\n  description: \"Lorem ipsum dolor sit amet\",\n  mime_type: \"text/html\",\n)\n\nserver = MCP::Server.new(\n  name: \"my_server\",\n  resource_templates: [resource_template],\n)\n```\n\n### Roots\n\nThe Model Context Protocol allows servers to request filesystem roots from clients through the `roots/list` method.\nRoots define the boundaries of where a server can operate, providing a list of directories and files the client has made available.\n\n**Key Concepts:**\n\n- **Server-to-Client Request**: Like sampling, roots listing is initiated by the server\n- **Client Capability**: Clients must declare `roots` capability during initialization\n- **Change Notifications**: Clients that support `roots.listChanged` send `notifications/roots/list_changed` when roots change\n\n**Using Roots in Tools:**\n\nTools that accept a `server_context:` parameter can call `list_roots` on it.\nThe request is automatically routed to the correct client session:\n\n```ruby\nclass FileSearchTool \u003c MCP::Tool\n  description \"Search files within the client's project roots\"\n  input_schema(\n    properties: {\n      query: { type: \"string\" }\n    },\n    required: [\"query\"]\n  )\n\n  def self.call(query:, server_context:)\n    roots = server_context.list_roots\n    root_uris = roots[:roots].map { |root| root[:uri] }\n\n    MCP::Tool::Response.new([{\n      type: \"text\",\n      text: \"Searching in roots: #{root_uris.join(\", \")}\"\n    }])\n  end\nend\n```\n\nResult contains an array of root objects:\n\n```ruby\n{\n  roots: [\n    { uri: \"file:///home/user/projects/myproject\", name: \"My Project\" },\n    { uri: \"file:///home/user/repos/backend\", name: \"Backend Repository\" }\n  ]\n}\n```\n\n**Handling Root Changes:**\n\nRegister a callback to be notified when the client's roots change:\n\n```ruby\nserver.roots_list_changed_handler do\n  puts \"Client's roots have changed, tools will see updated roots on next call.\"\nend\n```\n\n**Error Handling:**\n\n- Raises `RuntimeError` if client does not support `roots` capability\n- Raises `StandardError` if client returns an error response\n\n### Resource Subscriptions\n\nResource subscriptions allow clients to monitor specific resources for changes.\nWhen a subscribed resource is updated, the server sends a notification to the client.\n\nThe SDK does not track subscription state internally.\nServer developers register handlers and manage their own subscription state.\nThree methods are provided:\n\n- `Server#resources_subscribe_handler` - registers a handler for `resources/subscribe` requests\n- `Server#resources_unsubscribe_handler` - registers a handler for `resources/unsubscribe` requests\n- `ServerContext#notify_resources_updated` - sends a `notifications/resources/updated` notification to the subscribing client\n\n```ruby\nsubscribed_uris = Set.new\n\nserver = MCP::Server.new(\n  name: \"my_server\",\n  resources: [my_resource],\n  capabilities: { resources: { subscribe: true } },\n)\n\nserver.resources_subscribe_handler do |params|\n  subscribed_uris.add(params[:uri].to_s)\nend\n\nserver.resources_unsubscribe_handler do |params|\n  subscribed_uris.delete(params[:uri].to_s)\nend\n\nserver.define_tool(name: \"update_resource\") do |server_context:, **args|\n  if subscribed_uris.include?(\"test://my-resource\")\n    server_context.notify_resources_updated(uri: \"test://my-resource\")\n  end\n  MCP::Tool::Response.new([MCP::Content::Text.new(\"Resource updated\").to_h])\nend\n```\n\n### Sampling\n\nThe Model Context Protocol allows servers to request LLM completions from clients through the `sampling/createMessage` method.\nThis enables servers to leverage the client's LLM capabilities without needing direct access to AI models.\n\n**Key Concepts:**\n\n- **Server-to-Client Request**: Unlike typical MCP methods (client to server), sampling is initiated by the server\n- **Client Capability**: Clients must declare `sampling` capability during initialization\n- **Tool Support**: When using tools in sampling requests, clients must declare `sampling.tools` capability\n- **Human-in-the-Loop**: Clients can implement user approval before forwarding requests to LLMs\n\n**Using Sampling in Tools:**\n\nTools that accept a `server_context:` parameter can call `create_sampling_message` on it.\nThe request is automatically routed to the correct client session:\n\n```ruby\nclass SummarizeTool \u003c MCP::Tool\n  description \"Summarize text using LLM\"\n  input_schema(\n    properties: {\n      text: { type: \"string\" }\n    },\n    required: [\"text\"]\n  )\n\n  def self.call(text:, server_context:)\n    result = server_context.create_sampling_message(\n      messages: [\n        { role: \"user\", content: { type: \"text\", text: \"Please summarize: #{text}\" } }\n      ],\n      max_tokens: 500\n    )\n\n    MCP::Tool::Response.new([{\n      type: \"text\",\n      text: result[:content][:text]\n    }])\n  end\nend\n\nserver = MCP::Server.new(name: \"my_server\", tools: [SummarizeTool])\n```\n\n**Parameters:**\n\nRequired:\n\n- `messages:` (Array) - Array of message objects with `role` and `content`\n- `max_tokens:` (Integer) - Maximum tokens in the response\n\nOptional:\n\n- `system_prompt:` (String) - System prompt for the LLM\n- `model_preferences:` (Hash) - Model selection preferences (e.g., `{ intelligencePriority: 0.8 }`)\n- `include_context:` (String) - Context inclusion: `\"none\"`, `\"thisServer\"`, or `\"allServers\"` (soft-deprecated)\n- `temperature:` (Float) - Sampling temperature\n- `stop_sequences:` (Array) - Sequences that stop generation\n- `metadata:` (Hash) - Additional metadata\n- `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)\n- `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: \"auto\" }`)\n\n**Error Handling:**\n\n- Raises `RuntimeError` if client does not support `sampling` capability\n- Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability\n- Raises `StandardError` if client returns an error response\n\n### Notifications\n\nThe server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.\n\n#### Notification Methods\n\nThe server provides the following notification methods:\n\n- `notify_tools_list_changed` - Send a notification when the tools list changes\n- `notify_prompts_list_changed` - Send a notification when the prompts list changes\n- `notify_resources_list_changed` - Send a notification when the resources list changes\n- `notify_log_message` - Send a structured logging notification message\n\n#### Session Scoping\n\nWhen using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:\n\n- **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.\nNo extra configuration is needed.\n- **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,\nas they represent server-wide state changes. These should be called on the `server` instance directly.\n\n#### Notification Format\n\nNotifications follow the JSON-RPC 2.0 specification and use these method names:\n\n- `notifications/tools/list_changed`\n- `notifications/prompts/list_changed`\n- `notifications/resources/list_changed`\n- `notifications/progress`\n- `notifications/message`\n\n### Ping\n\nThe MCP Ruby SDK supports the\n[MCP `ping` utility](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping),\nwhich allows either side of the connection to verify that the peer is still responsive.\nA `ping` request has no parameters, and the receiver MUST respond promptly with an empty result.\n\n#### Server-Side\n\nServers respond to incoming `ping` requests automatically - no setup is required.\nAny `MCP::Server` instance replies with an empty result.\n\n#### Client-Side\n\n`MCP::Client` exposes `ping` to send a ping to the server:\n\n```ruby\nclient = MCP::Client.new(transport: transport)\nclient.ping # =\u003e {} on success\n```\n\n`#ping` raises `MCP::Client::ServerError` when the server returns a JSON-RPC error.\nIt raises `MCP::Client::ValidationError` when the response `result` is missing or\nis not a Hash (matching the spec requirement that `result` be an object).\nTransport-level errors (for example, `MCP::Client::Stdio`'s `read_timeout:` firing)\npropagate as exceptions raised by the transport layer.\n\n### Progress\n\nThe MCP Ruby SDK supports progress tracking for long-running tool operations,\nfollowing the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).\n\n#### How Progress Works\n\n1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool\n2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution\n3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress\n\n#### Server-Side: Tool with Progress\n\nTools that accept a `server_context:` parameter can call `report_progress` on it.\nThe server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:\n\n```ruby\nclass LongRunningTool \u003c MCP::Tool\n  description \"A tool that reports progress during execution\"\n  input_schema(\n    properties: {\n      count: { type: \"integer\" },\n    },\n    required: [\"count\"]\n  )\n\n  def self.call(count:, server_context:)\n    count.times do |i|\n      # Do work here.\n      server_context.report_progress(i + 1, total: count, message: \"Processing item #{i + 1}\")\n    end\n\n    MCP::Tool::Response.new([{ type: \"text\", text: \"Done\" }])\n  end\nend\n```\n\nThe `server_context.report_progress` method accepts:\n\n- `progress` (required) — current progress value (numeric)\n- `total:` (optional) — total expected value, so clients can display a percentage\n- `message:` (optional) — human-readable status message\n\n**Key Features:**\n\n- Tools report progress via `server_context.report_progress`\n- `report_progress` is a no-op when no `progressToken` was provided by the client\n- Supports both numeric and string progress tokens\n\n### Completions\n\nMCP spec includes [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),\nwhich enable servers to provide autocompletion suggestions for prompt arguments and resource URIs.\n\nTo enable completions, declare the `completions` capability and register a handler:\n\n```ruby\nserver = MCP::Server.new(\n  name: \"my_server\",\n  prompts: [CodeReviewPrompt],\n  resource_templates: [FileTemplate],\n  capabilities: { completions: {} },\n)\n\nserver.completion_handler do |params|\n  ref = params[:ref]\n  argument = params[:argument]\n  value = argument[:value]\n\n  case ref[:type]\n  when \"ref/prompt\"\n    values = case argument[:name]\n    when \"language\"\n      [\"python\", \"pytorch\", \"pyside\"].select { |v| v.start_with?(value) }\n    else\n      []\n    end\n    { completion: { values: values, hasMore: false } }\n  when \"ref/resource\"\n    { completion: { values: [], hasMore: false } }\n  end\nend\n```\n\nThe handler receives a `params` hash with:\n\n- `ref` - The reference (`{ type: \"ref/prompt\", name: \"...\" }` or `{ type: \"ref/resource\", uri: \"...\" }`)\n- `argument` - The argument being completed (`{ name: \"...\", value: \"...\" }`)\n- `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)\n\nThe handler must return a hash with a `completion` key containing `values` (array of strings), and optionally `total` and `hasMore`.\nThe SDK automatically enforces the 100-item limit per the MCP specification.\n\nThe server validates that the referenced prompt, resource, or resource template is registered before calling the handler.\nRequests for unknown references return an error.\n\n### Elicitation\n\nThe MCP Ruby SDK supports [elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation),\nwhich allows servers to request additional information from users through the client during tool execution.\n\nElicitation is a **server-to-client request**. The server sends a request and blocks until the user responds via the client.\n\n#### Capabilities\n\nClients must declare the `elicitation` capability during initialization. The server checks this before sending any elicitation request\nand raises a `RuntimeError` if the client does not support it.\n\nFor URL mode support, the client must also declare `elicitation.url` capability.\n\n#### Using Elicitation in Tools\n\nTools that accept a `server_context:` parameter can call `create_form_elicitation` on it:\n\n```ruby\nserver.define_tool(name: \"collect_info\", description: \"Collect user info\") do |server_context:|\n  result = server_context.create_form_elicitation(\n    message: \"Please provide your name\",\n    requested_schema: {\n      type: \"object\",\n      properties: { name: { type: \"string\" } },\n      required: [\"name\"],\n    },\n  )\n\n  MCP::Tool::Response.new([{ type: \"text\", text: \"Hello, #{result[:content][:name]}\" }])\nend\n```\n\n#### Form Mode\n\nForm mode collects structured data from the user directly through the MCP client:\n\n```ruby\nserver.define_tool(name: \"collect_contact\", description: \"Collect contact info\") do |server_context:|\n  result = server_context.create_form_elicitation(\n    message: \"Please provide your contact information\",\n    requested_schema: {\n      type: \"object\",\n      properties: {\n        name: { type: \"string\", description: \"Your full name\" },\n        email: { type: \"string\", format: \"email\", description: \"Your email address\" },\n      },\n      required: [\"name\", \"email\"],\n    },\n  )\n\n  text = case result[:action]\n  when \"accept\"\n    \"Hello, #{result[:content][:name]} (#{result[:content][:email]})\"\n  when \"decline\"\n    \"User declined\"\n  when \"cancel\"\n    \"User cancelled\"\n  end\n\n  MCP::Tool::Response.new([{ type: \"text\", text: text }])\nend\n```\n\n#### URL Mode\n\nURL mode directs the user to an external URL for out-of-band interactions such as OAuth flows:\n\n```ruby\nserver.define_tool(name: \"authorize_github\", description: \"Authorize GitHub\") do |server_context:|\n  elicitation_id = SecureRandom.uuid\n\n  result = server_context.create_url_elicitation(\n    message: \"Please authorize access to your GitHub account\",\n    url: \"https://example.com/oauth/authorize?elicitation_id=#{elicitation_id}\",\n    elicitation_id: elicitation_id,\n  )\n\n  server_context.notify_elicitation_complete(elicitation_id: elicitation_id)\n\n  MCP::Tool::Response.new([{ type: \"text\", text: \"Authorization complete\" }])\nend\n```\n\n#### URLElicitationRequiredError\n\nWhen a tool cannot proceed until an out-of-band elicitation is completed, raise `MCP::Server::URLElicitationRequiredError`.\nThis returns a JSON-RPC error with code `-32042` to the client:\n\n```ruby\nserver.define_tool(name: \"access_github\", description: \"Access GitHub\") do |server_context:|\n  raise MCP::Server::URLElicitationRequiredError.new([\n    {\n      mode: \"url\",\n      elicitationId: SecureRandom.uuid,\n      url: \"https://example.com/oauth/authorize\",\n      message: \"GitHub authorization is required.\",\n    },\n  ])\nend\n```\n\n### Logging\n\nThe MCP Ruby SDK supports structured logging through the `notify_log_message` method, following the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).\n\nThe `notifications/message` notification is used for structured logging between client and server.\n\n#### Log Levels\n\nThe SDK supports 8 log levels with increasing severity:\n\n- `debug` - Detailed debugging information\n- `info` - General informational messages\n- `notice` - Normal but significant events\n- `warning` - Warning conditions\n- `error` - Error conditions\n- `critical` - Critical conditions\n- `alert` - Action must be taken immediately\n- `emergency` - System is unusable\n\n#### How Logging Works\n\n1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level\n2. **Server Filtering**: The server only sends log messages at the configured level or higher severity\n3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client\n\nFor example, if the client sets the level to `\"error\"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.\n\nFor more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).\n\n**Usage Example:**\n\n```ruby\nserver = MCP::Server.new(name: \"my_server\")\ntransport = MCP::Server::Transports::StdioTransport.new(server)\n\n# The client first configures the logging level (on the client side):\ntransport.send_request(\n  request: {\n    jsonrpc: \"2.0\",\n    method: \"logging/setLevel\",\n    params: { level: \"info\" },\n    id: session_id # Unique request ID within the session\n  }\n)\n\n# Send log messages at different severity levels\nserver.notify_log_message(\n  data: { message: \"Application started successfully\" },\n  level: \"info\"\n)\n\nserver.notify_log_message(\n  data: { message: \"Configuration file not found, using defaults\" },\n  level: \"warning\"\n)\n\nserver.notify_log_message(\n  data: {\n    error: \"Database connection failed\",\n    details: { host: \"localhost\", port: 5432 }\n  },\n  level: \"error\",\n  logger: \"DatabaseLogger\" # Optional logger name\n)\n```\n\n**Key Features:**\n\n- Supports 8 log levels (debug, info, notice, warning, error, critical, alert, emergency) based on https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels\n- Server has capability `logging` to send log messages\n- Messages are only sent if a transport is configured\n- Messages are filtered based on the client's configured log level\n- If the log level hasn't been set by the client, no messages will be sent\n\n#### Transport Support\n\n- **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout\n- **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)\n\n#### Usage Example\n\n```ruby\nserver = MCP::Server.new(name: \"my_server\")\n\n# Default Streamable HTTP - session oriented\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server)\n\n# When tools change, notify clients\nserver.define_tool(name: \"new_tool\") { |**args| { result: \"ok\" } }\nserver.notify_tools_list_changed\n```\n\nYou can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.\nThis mode allows for easy multi-node deployment.\nSet `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):\n\n```ruby\n# Stateless Streamable HTTP - session-less\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)\n```\n\nYou can enable JSON response mode, where the server returns `application/json` instead of `text/event-stream`.\nSet `enable_json_response: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`:\n\n```ruby\n# JSON response mode\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server, enable_json_response: true)\n```\n\nIn JSON response mode, the POST response is a single JSON object, so server-to-client messages\nthat need to arrive during request processing are not supported:\nrequest-scoped notifications (`progress`, `log`) are silently dropped, and all server-to-client requests\n(`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error.\nSession-scoped standalone notifications (`resources/updated`, `elicitation/complete`) and\nbroadcast notifications (`tools/list_changed`, etc.) still flow to clients connected to the GET SSE stream.\nThis mode is suitable for simple tool servers that do not need server-initiated requests.\n\nBy default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).\nWhen configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:\n\n```ruby\n# Session timeout of 30 minutes\ntransport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)\n```\n\n### Pagination\n\nThe MCP Ruby SDK supports [pagination](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination)\nfor list operations that may return large result sets. Pagination uses string cursor tokens carrying a zero-based offset,\ntreated as opaque by clients: the server decides page size, and the client follows `nextCursor` until the server omits it.\n\nPagination applies to `tools/list`, `prompts/list`, `resources/list`, and `resources/templates/list`.\n\n#### Server-Side: Enabling Pagination\n\nPass `page_size:` to `MCP::Server.new` to split list responses into pages. When `page_size` is omitted (the default),\nlist responses contain all items in a single response, preserving the pre-pagination behavior.\n\n```ruby\nserver = MCP::Server.new(\n  name: \"my_server\",\n  tools: tools,\n  page_size: 50,\n)\n```\n\nWhen `page_size` is set, list responses include a `nextCursor` field whenever more pages are available:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"result\": {\n    \"tools\": [\n      { \"name\": \"example_tool\" }\n    ],\n    \"nextCursor\": \"50\"\n  }\n}\n```\n\nInvalid cursors (e.g. non-numeric, negative, or out-of-range) are rejected with JSON-RPC error code `-32602 (Invalid params)` per the MCP specification.\n\n#### Client-Side: Iterating Pages\n\n`MCP::Client` exposes `list_tools`, `list_prompts`, `list_resources`, and `list_resource_templates`.\n**Each call issues exactly one `*/list` JSON-RPC request and returns exactly one page** — not the full collection.\nThe returned result object (`MCP::Client::ListToolsResult` etc.) exposes the page items and the next cursor as method accessors:\n\n```ruby\nclient = MCP::Client.new(transport: transport)\n\ncursor = nil\nloop do\n  page = client.list_tools(cursor: cursor)\n  page.tools.each { |tool| process(tool) }\n  cursor = page.next_cursor\n  break unless cursor\nend\n```\n\nThe same pattern applies to `list_prompts` (`page.prompts`), `list_resources` (`page.resources`), and\n`list_resource_templates` (`page.resource_templates`). `next_cursor` is `nil` on the final page.\n\nBecause a single call returns a single page, how many items come back depends on the server's `page_size` configuration:\n\n| Server `page_size` | `client.list_tools(cursor: nil)`                                    |\n|--------------------|---------------------------------------------------------------------|\n| Not set (default)  | Returns every item in one response. `next_cursor` is `nil`.         |\n| Set to `N`         | Returns the first `N` items. `next_cursor` is set for continuation. |\n\nIf your application needs the complete collection regardless of how the server is configured, either loop on\n`next_cursor` as shown above, or use the whole-collection methods described below.\n\n#### Fetching the Complete Collection\n\n`client.tools`, `client.resources`, `client.resource_templates`, and `client.prompts` auto-iterate\nthrough all pages and return a plain array of items, guaranteeing the full collection regardless\nof the server's `page_size` setting. When a server paginates, they issue multiple JSON-RPC round\ntrips per call and break out of the pagination loop if the server returns the same `nextCursor`\ntwice in a row as a safety measure.\n\n```ruby\ntools = client.tools # =\u003e Array\u003cMCP::Client::Tool\u003e of every tool on the server.\n```\n\nUse these when you want the complete list; use `list_tools(cursor:)` etc. when you need\nfine-grained iteration (e.g. to stream-process pages without loading everything into memory).\n\n### Advanced\n\n#### Custom Methods\n\nThe server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:\n\n```ruby\nserver = MCP::Server.new(name: \"my_server\")\n\n# Define a custom method that returns a result\nserver.define_custom_method(method_name: \"add\") do |params|\n  params[:a] + params[:b]\nend\n\n# Define a custom notification method (returns nil)\nserver.define_custom_method(method_name: \"notify\") do |params|\n  # Process notification\n  nil\nend\n```\n\n**Key Features:**\n\n- Accepts any method name as a string\n- Block receives the request parameters as a hash\n- Can handle both regular methods (with responses) and notifications\n- Prevents overriding existing MCP protocol methods\n- Supports instrumentation callbacks for monitoring\n\n**Usage Example:**\n\n```ruby\n# Client request\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"add\",\n  \"params\": { \"a\": 5, \"b\": 3 }\n}\n\n# Server response\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"result\": 8\n}\n```\n\n**Error Handling:**\n\n- Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method\n- Supports the same exception reporting and instrumentation as standard methods\n\n## Building an MCP Client\n\nThe `MCP::Client` class provides an interface for interacting with MCP servers.\n\nThis class supports:\n\n- Liveness check via the `ping` method (`MCP::Client#ping`)\n- Tool listing via the `tools/list` method (`MCP::Client#tools`)\n- Tool invocation via the `tools/call` method (`MCP::Client#call_tools`)\n- Resource listing via the `resources/list` method (`MCP::Client#resources`)\n- Resource template listing via the `resources/templates/list` method (`MCP::Client#resource_templates`)\n- Resource reading via the `resources/read` method (`MCP::Client#read_resources`)\n- Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)\n- Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)\n- Completion requests via the `completion/complete` method (`MCP::Client#complete`)\n- Automatic JSON-RPC 2.0 message formatting\n- UUID request ID generation\n\nClients are initialized with a transport layer instance that handles the low-level communication mechanics.\nAuthorization is handled by the transport layer.\n\n## Transport Layer Interface\n\nIf the transport layer you need is not included in the gem, you can build and pass your own instances so long as they conform to the following interface:\n\n```ruby\nclass CustomTransport\n  # Sends a JSON-RPC request to the server and returns the raw response.\n  #\n  # @param request [Hash] A complete JSON-RPC request object.\n  #     https://www.jsonrpc.org/specification#request_object\n  # @return [Hash] A hash modeling a JSON-RPC response object.\n  #     https://www.jsonrpc.org/specification#response_object\n  def send_request(request:)\n    # Your transport-specific logic here\n    # - HTTP: POST to endpoint with JSON body\n    # - WebSocket: Send message over WebSocket\n    # - stdio: Write to stdout, read from stdin\n    # - etc.\n  end\nend\n```\n\n### Stdio Transport Layer\n\nUse the `MCP::Client::Stdio` transport to interact with MCP servers running as subprocesses over standard input/output.\n\n`MCP::Client::Stdio.new` accepts the following keyword arguments:\n\n| Parameter | Required | Description |\n|---|---|---|\n| `command:` | Yes | The command to spawn the server process (e.g., `\"ruby\"`, `\"bundle\"`, `\"npx\"`). |\n| `args:` | No | An array of arguments passed to the command. Defaults to `[]`. |\n| `env:` | No | A hash of environment variables to set for the server process. Defaults to `nil`. |\n| `read_timeout:` | No | Timeout in seconds for waiting for a server response. Defaults to `nil` (no timeout). |\n\nExample usage:\n\n```ruby\nstdio_transport = MCP::Client::Stdio.new(\n  command: \"bundle\",\n  args: [\"exec\", \"ruby\", \"path/to/server.rb\"],\n  env: { \"API_KEY\" =\u003e \"my_secret_key\" },\n  read_timeout: 30\n)\nclient = MCP::Client.new(transport: stdio_transport)\n\n# List available tools.\ntools = client.tools\ntools.each do |tool|\n  puts \"Tool: #{tool.name} - #{tool.description}\"\nend\n\n# Call a specific tool.\nresponse = client.call_tool(\n  tool: tools.first,\n  arguments: { message: \"Hello, world!\" }\n)\n\n# Close the transport when done.\nstdio_transport.close\n```\n\nThe stdio transport automatically handles:\n\n- Spawning the server process with `Open3.popen3`\n- MCP protocol initialization handshake (`initialize` request + `notifications/initialized`)\n- JSON-RPC 2.0 message framing over newline-delimited JSON\n\n### HTTP Transport Layer\n\nUse the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.\n\nYou'll need to add `faraday` as a dependency in order to use the HTTP transport layer. Add `event_stream_parser` as well if the server uses SSE (`text/event-stream`) responses:\n\n```ruby\ngem 'mcp'\ngem 'faraday', '\u003e= 2.0'\ngem 'event_stream_parser', '\u003e= 1.0' # optional, required only for SSE responses\n```\n\nExample usage:\n\n```ruby\nhttp_transport = MCP::Client::HTTP.new(url: \"https://api.example.com/mcp\")\nclient = MCP::Client.new(transport: http_transport)\n\n# List available tools\ntools = client.tools\ntools.each do |tool|\n  puts \u003c\u003c~TOOL_INFORMATION\n    Tool: #{tool.name}\n    Description: #{tool.description}\n    Input Schema: #{tool.input_schema}\n  TOOL_INFORMATION\nend\n\n# Call a specific tool\nresponse = client.call_tool(\n  tool: tools.first,\n  arguments: { message: \"Hello, world!\" }\n)\n\n# Call a tool with progress tracking.\nresponse = client.call_tool(\n  tool: tools.first,\n  arguments: { count: 10 },\n  progress_token: \"my-progress-token\"\n)\n```\n\nThe server will send `notifications/progress` back to the client during execution.\n\n#### HTTP Authorization\n\nBy default, the HTTP transport layer provides no authentication to the server, but you can provide custom headers if you need authentication. For example, to use Bearer token authentication:\n\n```ruby\nhttp_transport = MCP::Client::HTTP.new(\n  url: \"https://api.example.com/mcp\",\n  headers: {\n    \"Authorization\" =\u003e \"Bearer my_token\"\n  }\n)\n\nclient = MCP::Client.new(transport: http_transport)\nclient.tools # will make the call using Bearer auth\n```\n\nYou can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request.\n\n#### Customizing the Faraday Connection\n\nYou can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.\nThe block is called after the default middleware is configured, so you can add middleware or swap the HTTP adapter:\n\n```ruby\nhttp_transport = MCP::Client::HTTP.new(url: \"https://api.example.com/mcp\") do |faraday|\n  faraday.use MyApp::Middleware::HttpRecorder\n  faraday.adapter :typhoeus\nend\n```\n\n### Tool Objects\n\nThe client provides a wrapper class for tools returned by the server:\n\n- `MCP::Client::Tool` - Represents a single tool with its metadata\n\nThis class provides easy access to tool properties like name, description, input schema, and output schema.\n\n## Conformance Testing\n\nThe `conformance/` directory contains a test server and runner that validate the SDK against the MCP specification using [`@modelcontextprotocol/conformance`](https://github.com/modelcontextprotocol/conformance).\n\nSee [conformance/README.md](conformance/README.md) for usage instructions.\n\n## Documentation\n\n- [SDK API documentation](https://rubydoc.info/gems/mcp)\n- [Model Context Protocol documentation](https://modelcontextprotocol.io)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelcontextprotocol%2Fruby-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodelcontextprotocol%2Fruby-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelcontextprotocol%2Fruby-sdk/lists"}