How to Build an MCP Server for Your Rails App

An MCP (Model Context Protocol) server lets Claude, Cursor, ChatGPT connectors and your own agents call your app through typed tools. If your product is a Rails app, you can build the server right inside it, reusing your models, auth and tenancy rules. Here's the approach I use.

  1. Add the gem
  2. Define a tool
  3. Mount the server in Rails
  4. Scope every call to a user or account
  5. Test it locally
  6. Production checklist

1. Add the gem

The official Ruby SDK is published as the mcp gem (modelcontextprotocol/ruby-sdk). It supports stdio and Streamable HTTP and ships a complete Rails example.

bundle add mcp

2. Define a tool

Each tool is a class with a description, a JSON Schema for its inputs and a call method. Keep tools narrow: one clear action, predictable output, helpful errors. Putting them in app/tools lets Zeitwerk autoload them.

# app/tools/find_order_tool.rb
class FindOrderTool < MCP::Tool
  description "Look up an order by its number for the current account"
  input_schema(
    properties: { number: { type: "string" } },
    required: ["number"]
  )

  class << self
    def call(number:, server_context:)
      account = server_context[:account]
      order = account.orders.find_by(number: number)
      text = order ? order.to_json(only: %i[number status total_cents]) : "No order #{number}"
      MCP::Tool::Response.new([{ type: "text", text: text }])
    end
  end
end

3. Mount the server in Rails

The Streamable HTTP transport is a Rack app, so it mounts like any engine. The SDK's Rails example builds the server in config/routes.rb because tool classes are autoloadable by then but not yet in initializers.

# config/routes.rb
server = MCP::Server.new(
  name: "my_app",
  version: "1.0.0",
  tools: [FindOrderTool]
)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)

Rails.application.routes.draw do
  mount(transport => "/mcp")
end

The default transport keeps session state in memory, so run a single server process or follow the SDK's notes on stateless and multi-instance deployments.

4. Scope every call to a user or account

This is where most of the real work is. The model should never decide whose data it sees. Authenticate the request (OAuth or a per-user token), resolve the current account, and pass it through server_context so every tool queries through that scope, as account.orders does above. In practice that means:

  • Read tools first; add write tools one at a time.
  • Destructive actions require explicit confirmation or a human approval step.
  • Log every tool call with actor, redacted arguments and result.
  • Rate-limit per account, and return compact responses instead of whole records.

5. Test it locally

Start Rails and send an initialize request, then tools/list and tools/call. The MCP Inspector or a client like Claude Desktop or Cursor pointed at http://localhost:3000/mcp works too. Add request specs for each tool so schema or scoping regressions fail CI.

curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

6. Production checklist

  • Auth on the /mcp route (not just the app's session cookie).
  • Tenant isolation verified by tests, not by prompts.
  • Tool descriptions written for the model: when to use it, what it returns.
  • Versioning plan for tool changes so existing clients don't break.
  • Observability: structured logs and alerts on unusual write volume.
  • Review for prompt injection arriving through tool results (e.g. user-entered text).

Want help building yours?

I build MCP servers and AI features for Rails and other stacks. See MCP server development or Ruby on Rails consulting.

Book a Free 30-Min Consult

Book a Free 30-Minute Consult

Tell me what you're building or what's stuck. I'll reply with next steps, usually within one business day.

Get In Touch

Email
hello@offtherails.pro
Phone
(385) 743-8966
Location
Salt Lake City, UT · Remote across the US

Why Choose Me?

  • 10 years shipping production software
  • 5 long-term fractional clients, most for 2+ years
  • AI agents, MCP servers, Rails + React
  • Based in Salt Lake City, working remotely US-wide

What happens next

  1. I read your message and reply within one business day.
  2. We schedule a free 30-minute consult call.
  3. You get a clear recommendation and next steps, no obligation.

Prefer email? hello@offtherails.pro