Mirror of https://github.com/modelcontextprotocol/ruby-sdk — The official Ruby SDK for the Model Context Protocol servers and clients.
  • Ruby 99.8%
  • Shell 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Koichi ITO 8ad80dd0b4 Let a provider add parameters to the token requests it makes
## Motivation and Context

`ClientCredentialsProvider` builds a fixed `client_credentials` token request: `grant_type`, `scope`,
`resource`, and the client authentication that `Flow#post_to_token_endpoint` adds. An authorization server
that requires further parameters, such as Auth0's `audience`, cannot be used through `oauth:` at all,
and giving up `oauth:` also gives up the bearer header and the 401-driven retry that the transport provides.

RFC 6749 Section 8.2 allows extension parameters on the token request, and the TypeScript SDK lets
a provider shape its token request through `prepareTokenRequest`. `ClientCredentialsProvider.new` now takes
`token_request_params:`, a Hash of String keys and values added to every token request the provider makes,
and `Flow#post_to_token_endpoint` reads the same name by duck typing, so a `Provider` or `CrossAppAccessProvider`
subclass that defines the method gets the same treatment on its authorization code exchange, refresh,
or `jwt-bearer` request.
`Provider.new` and `CrossAppAccessProvider.new` take the same keyword, held by `StorageBackedProvider` like
`authorization_request_validator`, so every bundled provider refuses a bad value before anything is stored.

The parameters go underneath the flow's own, so the SDK's values always win, and a key the SDK sets itself
(`Flow::RESERVED_TOKEN_REQUEST_PARAMS`) is refused rather than silently overridden: with `ArgumentError`
from the constructor, before any `client_information` is written, and again with `ArgumentError` from the flow,
before the token request is sent. Only Strings are accepted because `URI.encode_www_form` encodes other scalars
and Arrays in ways the caller did not write, and the provider keeps a frozen copy so a later change to
the caller's Hash cannot alter what is sent.
A Hash comparing keys by identity is refused too, since two equal keys are two entries there, sent twice
by a provider method or silently collapsed into one by the copy. The flow raises `ArgumentError` rather than
`AuthorizationError` because `MCP::Client::HTTP` treats a failed refresh as a reason to run the interactive flow,
which would then fail the same way after the user signed in.
Both refusals raise `Flow::InvalidTokenRequestParamsError`, a subclass of `ArgumentError`, so a caller can tell them
from an unknown keyword, and the copy duplicates keys as well as values, since `Hash` copies only keys
whose class is exactly `String`.

Fixes #554.

## How Has This Been Tested?

New tests cover every token request the parameters can ride (the `client_credentials` grant under
all three client authentication methods, the authorization code exchange, refresh, and `jwt-bearer`),
both refusal boundaries (the constructors of all three providers, and the flow for a provider defining the method),
and the transport surfacing a refused hook from a refresh attempt without starting the interactive flow.
All of them fail against the previous library.

## Breaking Changes

None. The keyword is optional and the hook is opt-in; a provider that does not define `token_request_params` sends
the same requests as before.
2026-09-17 13:26:52 +09:00
.github Group the conformance Rake tasks under a conformance namespace 2026-08-30 02:24:52 +09:00
bin Add API documentation with GitHub Pages deployment 2025-11-21 12:21:38 -06:00
conformance Group the conformance Rake tasks under a conformance namespace 2026-08-30 02:24:52 +09:00
docs Let a provider add parameters to the token requests it makes 2026-09-17 13:26:52 +09:00
examples Update examples for the 2026-07-28 era 2026-08-28 12:03:52 +09:00
lib Let a provider add parameters to the token requests it makes 2026-09-17 13:26:52 +09:00
test Let a provider add parameters to the token requests it makes 2026-09-17 13:26:52 +09:00
.gitattributes Internal Release v0.1.1 2025-05-08 17:37:27 -04:00
.gitignore Add a rake docs:preview task serving the docs site locally 2026-08-25 12:36:54 +09:00
.rubocop.yml Suppress RuboCop extension suggestions (#484) 2026-08-07 18:46:46 -04:00
AGENTS.md Group the conformance Rake tasks under a conformance namespace 2026-08-30 02:24:52 +09:00
CHANGELOG.md Release 1.5.1 2026-09-09 10:43:15 +09:00
CLAUDE.md Add CLAUDE.md referencing AGENTS.md 2026-04-01 01:03:43 +09:00
CODE_OF_CONDUCT.md Internal Release v0.1.1 2025-05-08 17:37:27 -04:00
dev.yml fix dev style (#208) 2026-01-06 01:00:31 -05:00
Gemfile Drop the json pin for Ruby 2.7.0 through 2.7.2 from the Gemfile 2026-09-10 11:55:31 +09:00
LICENSE chore: update licensing to Apache 2.0 for new contributions 2026-01-12 22:49:19 +00:00
mcp.gemspec Speed Up Tool::Schema Validation by 5x to 100x 2026-06-01 21:47:30 +09:00
Rakefile Group the conformance Rake tasks under a conformance namespace 2026-08-30 02:24:52 +09:00
README.md [Doc] Align the Docs Page Names With the MCP Specification 2026-08-22 18:07:38 +09:00
RELEASE.md [Docs] Extract release documentation to RELEASE.md 2025-11-04 11:13:39 +09:00
ROADMAP.md [Doc] Align the ROADMAP deprecation wording with the shipped behavior 2026-08-23 01:15:20 +09:00
SECURITY.md Add SECURITY.md with GitHub Security Advisories guidance (#232) 2026-02-18 20:49:17 -05:00
VERSIONING.md Add VERSIONING.md documenting the versioning and breaking-change policy 2026-07-21 11:33:20 +09:00

MCP Ruby SDK Gem Version Apache 2.0 licensed CI

The official Ruby SDK for Model Context Protocol servers and clients.

Detailed guides are available at https://ruby.sdk.modelcontextprotocol.io.

Features

  • Build MCP servers that expose tools, prompts, and resources to any MCP host
  • Build MCP clients that connect to any MCP server, with automatic lifecycle negotiation and OAuth 2.1 authorization
  • Speak every standard transport: stdio and Streamable HTTP (including SSE), with a Rails integration
  • Cover the full protocol surface: server-to-client requests, multi round-trip requests, notifications, progress, logging, cancellation, completions, and pagination

Installation

Add this line to your application's Gemfile:

gem "mcp"

And then execute:

$ bundle install

Or install it yourself as:

$ gem install mcp

You may need to add additional dependencies depending on which features you wish to access.

Quick Start

The following minimal programs show both sides of the protocol: a server that exposes a single tool, and a client that spawns such a server and drives it over stdio.

MCP Server

A minimal server defines a tool and serves it over the stdio transport:

require "mcp"

# Create a simple tool
class ExampleTool < MCP::Tool
  description "A simple example tool that echoes back its arguments"
  input_schema(
    properties: {
      message: { type: "string" },
    },
    required: ["message"]
  )

  class << self
    def call(message:, server_context:)
      MCP::Tool::Response.new([{
        type: "text",
        text: "Hello from example tool! Message: #{message}",
      }])
    end
  end
end

# Set up the server
server = MCP::Server.new(
  name: "example_server",
  tools: [ExampleTool],
)

# Create and start the transport
transport = MCP::Server::Transports::StdioTransport.new(server)
transport.open

Save the script as server.rb, run it, and send JSON-RPC requests via stdin:

$ ruby server.rb
{"jsonrpc":"2.0","id":"1","method":"ping"}
{"jsonrpc":"2.0","id":"2","method":"tools/list"}
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}

The same server can also run over Streamable HTTP, including mounted inside a Rails application; see Server Transports.

MCP Client

A minimal client spawns a stdio server as a subprocess, connects, and lists and calls its tools:

stdio_transport = MCP::Client::Stdio.new(
  command: "bundle",
  args: ["exec", "ruby", "path/to/server.rb"],
  env: { "API_KEY" => "my_secret_key" },
  read_timeout: 30
)
client = MCP::Client.new(transport: stdio_transport)

# Perform the MCP initialization handshake before sending any requests.
client.connect

# List available tools.
tools = client.tools
tools.each do |tool|
  puts "Tool: #{tool.name} - #{tool.description}"
end

# Call a specific tool.
response = client.call_tool(
  tool: tools.first,
  arguments: { message: "Hello, world!" }
)

# Close the transport when done.
stdio_transport.close

The same client can connect to Streamable HTTP servers with MCP::Client::HTTP; see Client Transports.

Examples

Runnable examples are available in examples/, including a complete Rails application in examples/rails.

Documentation

License

This project is licensed under the Apache License 2.0 for new contributions, with existing code under MIT. See the LICENSE file for details.