Skip to content

Quick Start with Rails

Dick Davis edited this page Feb 14, 2026 · 6 revisions

Quick Start with Rails

The model-context-protocol-rb works out of the box with any valid Rack request. Currently, this project has no plans for building a deeper Rails integration, but it is fairly simple to build it out yourself. To support modern application deployments across multiple servers, the streamable HTTP transport requires Redis as an external dependency.

Here's an example of how you can easily integrate with Rails.

Configure the Server

Configure the MCP server in an initializer. The streamable HTTP transport uses only 2 background threads regardless of concurrent connections, preventing thread proliferation and memory issues. Redis configuration is included directly in the transport configuration block:

# config/initializers/model_context_protocol.rb
require "model_context_protocol"

ModelContextProtocol::Server.with_streamable_http_transport do |config|
  config.name = "MyMCPServer"
  config.title = "My MCP Server"
  config.version = "1.0.0"
  config.instructions = <<~INSTRUCTIONS
    This server provides prompts, tools, and resources for interacting with my app.

    Key capabilities:
    - Does this one thing
    - Does this other thing
    - Oh, yeah, and it does that one thing, too

    Use this server when you need to do stuff.
  INSTRUCTIONS

  # Redis configuration (required for streamable HTTP transport)
  config.redis_url = ENV.fetch("REDIS_URL", "redis://localhost:6379/0")
  config.redis_pool_size = 20

  config.registry do
    prompts do
      register MyPrompt
    end

    resources do
      register MyResource
    end

    tools do
      register MyTool
    end
  end
end

Configure Puma

Use the provided Puma plugin to manage the MCP server lifecycle. The plugin handles both single mode (development) and clustered mode (production):

# config/puma.rb
plugin :mcp

Set Routes

Then, set the routes:

constraints format: :json do
  get "/mcp", to: "model_context_protocol#handle", as: :mcp_get
  post "/mcp", to: "model_context_protocol#handle", as: :mcp_post
  delete "/mcp", to: "model_context_protocol#handle", as: :mcp_delete
end

Implement Controller

Implement a controller endpoint to handle the requests. The controller passes the Rack environment and per-request context (like authenticated user info) to the server.

class ModelContextProtocolController < ActionController::API
  include ActionController::Live

  before_action :authenticate_user

  def handle
    result = ModelContextProtocol::Server.serve(
      env: request.env,
      session_context: {
        user_id: current_user.id,
        request_id: request.request_id
      }
    )

    response.headers.merge!(result[:headers]) if result[:headers]

    if result[:stream]
      stream_response(result[:stream_proc])
    else
      render json: result[:json], status: result[:status] || 200
    end
  end

  private

  def stream_response(stream_proc)
    stream_proc&.call(response.stream)
  ensure
    response.stream.close rescue nil
  end
end

Accessing Session Context in Handlers

The session_context passed to Server.serve is automatically merged with the server's config.context and made available via the context method in your tools, prompts, and resources:

class MyTool < ModelContextProtocol::Server::Tool
  define do
    name "my_tool"
    description "Does something useful"
    input_schema do
      { type: "object", properties: {} }
    end
  end

  def call
    # Access per-request context via the context method
    user_id = context[:user_id]

    # Use user_id for authorization, logging, etc.
    server_logger.info("Tool called by user #{user_id}")

    respond_with content: text_content(text: "Done!")
  end
end

Dynamic Registry with Authorization

If you need to dynamically register handlers based on user permissions, you can register all possible handlers at setup time, then use context within handlers to check authorization:

# app/mcp/tools/admin_tool.rb
class AdminTool < ModelContextProtocol::Server::Tool
  define do
    name "admin_tool"
    description "Administrative operations"
    input_schema do
      { type: "object", properties: {} }
    end
  end

  def call
    user = User.find(context[:user_id])

    unless user.admin?
      return respond_with error: "Unauthorized: admin access required"
    end

    # Proceed with admin operation...
    respond_with content: text_content(text: "Admin operation completed")
  end
end

Next Steps

Read more about the server configuration options to better understand how you can customize your MCP server.

From here, you can get started building prompts, resources, resource templates, and tools.

Clone this wiki locally