Skip to content

mmgreiner/mcp_app

Repository files navigation

MCP-Rails-App

This is a small example of a rails app with Model Context Protocol. It is based on mcp-on-rails, which itself is based on ruby-mcp.

Creating the application

Creating a simple rails app with mcp:

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

We now have (see mcp-on-rails):

  • MCP server endpoint at /mcp
  • Automatic MCP tool generation during scaffolding
  • All necessary MCP infrastructure configured

I have created a simple Post model:

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

This creates the normal model, controller, and view, but also tools:

app/tools
└── posts
    ├── create_tool.rb
    ├── delete_tool.rb
    ├── index_tool.rb
    ├── show_tool.rb
    └── update_tool.rb

They contain the code for the mcp server:

module Posts
  class IndexTool < MCP::Tool
    tool_name "post-index-tool"
    description "List the last count of Posts entities. The count parameter is an integer and defaults to 10."

    input_schema(
      properties: {
        count: { type: "integer" },
      },
      required: []
    )

    def self.call(count: 10, server_context:)
      posts = Post.all
      posts = posts.last(count)

      response = posts.map(&:to_mcp_response).join("\n")
      response = "Nothing was found" unless response.present?

      MCP::Tool::Response.new([ { type: "text", text: response } ])
    rescue StandardError => e
      MCP::Tool::Response.new([ { type: "text", text: "An error occurred, what happened was #{e.message}" } ])
    end
  end
end

The routes.rb contain a new line:

# Model Context Protocol
post "/mcp", to: "mcp#handle"
get  "/mcp", to: "mcp#handle"

So the localhost:3000/mcp is the entry point for the MCP tools.

Also added is a mcp_controller.rb:

# frozen_string_literal: true

class McpController < ActionController::API
  def handle
    if params[:method] == "notifications/initialized"
      head :accepted
    else
      render(json: mcp_server.handle_json(request.body.read))
    end
  end

  private

  def mcp_server
    MCP::Server.new(
      name: "rails_mcp_server",
      version: "1.0.0",
      tools: MCP::Tool.descendants
    )
  end
end

So all sub-classes of MCP::Tool will be served as tools.

Testing

Test is not as simple. There are basically two ways:

MCP supports stdio as transport layer, or streamable http. Unfortunately, Claude Desktop as of version 0.13 only supports stdio.

Claude Desktop

To use Claude Desktop, we need a proxy which converts the streamable http to stdio transport. ChatGPT helped to develop this proxy server.

The configuration file has to be ~/Library/Application Support/Claude/claude_desktop_config.json. Claude's log files can be found under ~/Library/Logs/Claude.

The configuration file will look like this:

{
    "mcpServers": {
        "cmi-mcp": {
            "command": "ruby",
            "args": [
                "/Users/.../mcp_stdio_proxy.rb"
            ],
            "env": {
                "MCP_ENDPOINT": "http://127.0.0.1:3000/mcp"
            }
        }
    }
}

This will start the proxy which takes the route of the mcp endpoint as environment variable.

Prompting

You can now prompt Claude once you have started the server:

% rails s
=> Booting Puma
=> Rails 8.0.3 application starting in development 
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 7.0.4 ("Romantic Warrior")
* Ruby version: ruby 3.4.6 (2025-09-16 revision dbd83256b1) +YJIT +PRISM [x86_64-darwin24]
*  Min threads: 3
*  Max threads: 3
*  Environment: development
*          PID: 58966
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop

Show me the tools

Here are all the tools available to me:

Post Management Tools:

  1. rails-mcp:post-update-tool - Update a Post entity of a given ID
  2. rails-mcp:post-show-tool - Show all information about Post of the given ID
  3. rails-mcp:post-index-tool - List the last count of Posts entities
  4. rails-mcp:post-delete-tool - Delete a Post entity of the given ID
  5. rails-mcp:post-create-tool - Create a new Post entity

Artifacts Tool:

  1. artifacts - Creates and updates artifacts (self-contained pieces of content)

These are all the MCP tools currently connected and available in this session.

Create a post entited "First post" with the content: "this is my first post"

I'll create a post with that title and content.Perfect! I've successfully created your post. Here are the details:

  • ID: 3
  • Title: First post
  • Content: this is my first post
  • Created: October 6, 2025 at 18:29:54 UTC
  • Updated: October 6, 2025 at 18:29:54 UTC

The post has been saved and is now in the system.

MCP Inspector

Download and start the mcp inspector according to mcp-inspector:

npx @modelcontextprotocol/inspector

mcp-inspector supports stdio and streamable http.

So in one console, start the rails app. In another console, start modelcontextprotocol/inspector. It should open the browser automatically.

To connect to the stdio proxy server, set:

Parameter Value
Transport Type STDIO
Command ruby
Arguments /.../mcp_stdio_proxy.rb

Now you can click on the tools and test them.

Streamable http

For streamable http, we need to use CORS. Add this file:

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'  # You can restrict this to MCP Inspector’s origin later

    resource '/mcp*',
      headers: :any,
      methods: [:get, :post, :options],
      expose: ['Content-Type'],
      max_age: 600
  end
end

Restart the rails server, and then set the inspector to:

Parameter Value
Transport Type Streamable HTTP
URL http://localhost:3000/mcp
Connection Type Direct

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published