Skip to content

Latest commit

 

History

History
242 lines (179 loc) · 8.03 KB

File metadata and controls

242 lines (179 loc) · 8.03 KB

MCP on Rails (with optional OAuth)

A Rails application template that integrates the Model Context Protocol (MCP) with Ruby on Rails. During setup, the template asks whether to add OAuth 2.0 protection using Devise and Doorkeeper — so one template supports both plain MCP and fully authenticated setups.

Quick Start

git clone https://github.com/pstrzalk/mcp-on-rails.git
rails new myapp -m mcp-on-rails-oauth/mcp
cd myapp
rails db:migrate
rails server

The template will prompt:

Add Devise + Doorkeeper OAuth 2.0 authentication? (y/n)

Answer n for a plain MCP server or y for full OAuth protection.

Adding the MCP template to an existing Rails application

You may just as easily apply this template to an existing Rails app.

git clone https://github.com/pstrzalk/mcp-on-rails.git
cd your-project/
rails app:template LOCATION=../mcp-on-rails/mcp

Plain MCP (answer "n")

Creates a Rails app with an open MCP server — no authentication required.

What you get

  • mcp gem added to Gemfile
  • McpController at /mcp — inherits ActionController::API, handles MCP protocol
  • MCP routesPOST /mcp, GET /mcp, DELETE /mcp, OPTIONS /mcp
  • Scaffold hookrails generate scaffold automatically creates MCP tools
  • Custom tool generatorrails generate mcp_tool ToolName field:type
  • to_mcp_response on ApplicationRecord for consistent text formatting
  • rake mcp:tools to list all registered tools

Usage

rails new myapp -m mcp-on-rails-oauth/mcp   # answer n
cd myapp && rails db:migrate

rails generate scaffold Post title:string body:text
rails db:migrate
rails server

Test it:

# MCP initialize
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

# List tools
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Project structure (plain mode)

app/
├── controllers/
│   └── mcp_controller.rb              # Open MCP endpoint (no auth)
├── models/
│   └── application_record.rb          # Extended with to_mcp_response
└── tools/                             # MCP tools (auto-generated per scaffold)

config/
├── initializers/
│   └── mcp.rb                         # MCP tool autoloading
└── routes.rb                          # MCP routes

Connecting AI assistants (plain mode)

{
  "name": "my-rails-app",
  "type": "StreamableHttp",
  "url": "http://localhost:3000/mcp"
}

No authentication needed — the /mcp endpoint is open.


OAuth MCP (answer "y")

Creates a Rails app with an OAuth 2.0-protected MCP server, including PKCE, dynamic client registration, and resource indicator support — everything needed for MCP's OAuth authorization flow.

What you get

Everything from plain mode, plus:

  • devise + doorkeeper gems added to Gemfile
  • Devise user authentication (sign-up, sign-in, password reset)
  • Doorkeeper OAuth 2.0 provider with PKCE enforcement (S256)
  • McpController protected by doorkeeper_authorize! with token audience validation (RFC 8707)
  • Dynamic client registration at POST /oauth/register (RFC 7591)
  • Protected resource metadata at GET /.well-known/oauth-protected-resource (RFC 9728)
  • Authorization server metadata at GET /.well-known/oauth-authorization-server (RFC 8414)
  • Resource indicators — tokens are scoped to the /mcp resource (RFC 8707)

Usage

rails new myapp -m mcp-on-rails-oauth/mcp   # answer y
cd myapp && rails db:migrate

rails generate scaffold Post title:string body:text
rails db:migrate
rails server

The /mcp endpoint now requires a Bearer token — unauthenticated requests return 401.

OAuth flow

The full authorization flow follows MCP's OAuth specification:

  1. Discovery — Client fetches GET /.well-known/oauth-protected-resource to find the authorization server
  2. Server metadata — Client fetches GET /.well-known/oauth-authorization-server for endpoints and capabilities
  3. Client registrationPOST /oauth/register with client metadata (RFC 7591)
  4. AuthorizationGET /oauth/authorize with PKCE code_challenge (S256) and resource parameter
  5. User authentication — Devise handles sign-in/sign-up
  6. Token exchangePOST /oauth/token with code_verifier and resource parameter
  7. MCP requestsPOST /mcp with Authorization: Bearer <token>

Supported RFCs

RFC Description Endpoint
OAuth 2.0 + PKCE Authorization with Proof Key for Code Exchange (S256) /oauth/authorize, /oauth/token
RFC 7591 Dynamic Client Registration POST /oauth/register
RFC 8414 Authorization Server Metadata GET /.well-known/oauth-authorization-server
RFC 8707 Resource Indicators resource parameter in auth + token requests
RFC 9728 Protected Resource Metadata GET /.well-known/oauth-protected-resource

Project structure (OAuth mode)

app/
├── controllers/
│   ├── mcp_controller.rb                              # OAuth-protected MCP endpoint
│   ├── oauth_client_registration_controller.rb        # RFC 7591
│   └── oauth_authorization_server_metadata_controller.rb  # RFC 8414 + 9728
├── models/
│   ├── user.rb                                        # Devise user with OAuth associations
│   ├── oauth_application.rb
│   ├── oauth_access_token.rb
│   └── oauth_access_grant.rb
├── tools/                                             # MCP tools (auto-generated per scaffold)
└── views/
    └── devise/                                        # Customizable auth views

config/
├── initializers/
│   ├── doorkeeper.rb                                  # OAuth + PKCE config
│   ├── devise.rb                                      # User auth config
│   └── mcp.rb                                         # MCP tool autoloading
└── routes.rb                                          # All OAuth + MCP routes

db/migrate/
├── *_devise_create_users.rb
├── *_create_doorkeeper_tables.rb
├── *_enable_pkce.rb
└── *_add_resource_to_oauth_tables.rb

Connecting AI assistants (OAuth mode)

{
  "name": "my-rails-app",
  "type": "StreamableHttp",
  "url": "http://localhost:3000/mcp"
}

The client must complete the OAuth PKCE flow before making MCP requests — the /mcp endpoint returns 401 without a valid Bearer token.


Common Features (both modes)

Scaffolding models with MCP tools

rails generate scaffold Post title:string content:text
rails db:migrate

This creates standard Rails files plus 5 MCP tools in app/tools/posts/:

  • show_tool.rb — Retrieve a single post by ID
  • index_tool.rb — List posts with pagination
  • create_tool.rb — Create new posts
  • update_tool.rb — Update existing posts
  • delete_tool.rb — Delete posts

Creating custom MCP tools

rails generate mcp_tool WeatherCheck location:string

Creating custom MCP prompts

rails generate mcp_prompt hotel_finder location:required check_in_date:required adults price_max

This creates app/prompts/hotel_finder.rb with a prompt class inheriting from MCP::Prompt. Arguments are optional by default — append :required to make them required.

Prompts are automatically loaded from app/prompts/ and registered with the MCP server. Unlike tools, prompts are not auto-generated during scaffolding — they are created explicitly via the generator.

Listing available tools and prompts

rake mcp:tools
rake mcp:prompts

License

MIT License