Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ gem "after_commit_everywhere", "~> 1.0"

# AI
gem "ruby-openai"
gem "fast-mcp"

group :development, :test do
gem "debug", platforms: %i[mri windows]
Expand Down
41 changes: 41 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,35 @@ GEM
dotenv (= 3.1.8)
railties (>= 6.1)
drb (2.2.3)
dry-configurable (1.3.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-core (1.1.0)
concurrent-ruby (~> 1.0)
logger
zeitwerk (~> 2.6)
dry-inflector (1.2.0)
dry-initializer (3.2.0)
dry-logic (1.6.0)
bigdecimal
concurrent-ruby (~> 1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-schema (1.14.1)
concurrent-ruby (~> 1.0)
dry-configurable (~> 1.0, >= 1.0.1)
dry-core (~> 1.1)
dry-initializer (~> 3.2)
dry-logic (~> 1.5)
dry-types (~> 1.8)
zeitwerk (~> 2.6)
dry-types (1.8.3)
bigdecimal (~> 3.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
dry-inflector (~> 1.0)
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
erb (5.0.1)
erb_lint (0.9.0)
activesupport
Expand All @@ -206,6 +235,13 @@ GEM
net-http (>= 0.5.0)
faraday-retry (2.3.2)
faraday (~> 2.0)
fast-mcp (1.5.0)
addressable (~> 2.8)
base64
dry-schema (~> 1.14)
json (~> 2.0)
mime-types (~> 3.4)
rack (~> 3.1)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
Expand Down Expand Up @@ -321,6 +357,10 @@ GEM
matrix (0.4.2)
memory_profiler (1.1.0)
method_source (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0729)
mini_histogram (0.3.1)
mini_magick (5.2.0)
benchmark
Expand Down Expand Up @@ -640,6 +680,7 @@ DEPENDENCIES
faraday
faraday-multipart
faraday-retry
fast-mcp
foreman
hotwire-livereload
hotwire_combobox
Expand Down
5 changes: 5 additions & 0 deletions app/resources/application_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class ApplicationResource < ActionResource::Base
# write your custom logic to be shared across all resources here
end
5 changes: 5 additions & 0 deletions app/tools/application_tool.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class ApplicationTool < ActionTool::Base
# write your custom logic to be shared across all tools here
end
42 changes: 42 additions & 0 deletions config/initializers/fast_mcp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

# FastMcp - Model Context Protocol for Rails
# This initializer sets up the MCP middleware in your Rails application.
#
# In Rails applications, you can use:
# - ActionTool::Base as an alias for FastMcp::Tool
# - ActionResource::Base as an alias for FastMcp::Resource
#
# All your tools should inherit from ApplicationTool which already uses ActionTool::Base,
# and all your resources should inherit from ApplicationResource which uses ActionResource::Base.

# Mount the MCP middleware in your Rails application
# You can customize the options below to fit your needs.
require 'fast_mcp'

Check failure on line 15 in config/initializers/fast_mcp.rb

View workflow job for this annotation

GitHub Actions / ci / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

FastMcp.mount_in_rails(
Rails.application,
name: Rails.application.class.module_parent_name.underscore.dasherize,
version: '1.0.0',

Check failure on line 20 in config/initializers/fast_mcp.rb

View workflow job for this annotation

GitHub Actions / ci / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and avoid using a 'magic string', it's a good practice to define the version as a constant. This makes it easier to manage and update the version number, especially if it's referenced elsewhere in the application.

MCP_VERSION = '1.0.0'

FastMcp.mount_in_rails(
  Rails.application,
  name: Rails.application.class.module_parent_name.underscore.dasherize,
  version: MCP_VERSION,

path_prefix: '/mcp', # This is the default path prefix

Check failure on line 21 in config/initializers/fast_mcp.rb

View workflow job for this annotation

GitHub Actions / ci / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
messages_route: 'messages', # This is the default route for the messages endpoint

Check failure on line 22 in config/initializers/fast_mcp.rb

View workflow job for this annotation

GitHub Actions / ci / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
sse_route: 'sse' # This is the default route for the SSE endpoint

Check failure on line 23 in config/initializers/fast_mcp.rb

View workflow job for this annotation

GitHub Actions / ci / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
# Add allowed origins below, it defaults to Rails.application.config.hosts
# allowed_origins: ['localhost', '127.0.0.1', '[::1]', 'example.com', /.*\.example\.com/],
# localhost_only: true, # Set to false to allow connections from other hosts
# whitelist specific ips to if you want to run on localhost and allow connections from other IPs
# allowed_ips: ['127.0.0.1', '::1']
# authenticate: true, # Uncomment to enable authentication
# auth_token: 'your-token', # Required if authenticate: true
) do |server|
Rails.application.config.after_initialize do
# FastMcp will automatically discover and register:
# - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
# - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
server.register_tools(*ApplicationTool.descendants)
server.register_resources(*ApplicationResource.descendants)
Comment on lines +37 to +38

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In the development environment, Rails lazy-loads classes, so ApplicationTool.descendants and ApplicationResource.descendants will likely be empty when this initializer runs. This will prevent your tools and resources from being registered. You should explicitly eager-load these directories to ensure all tool and resource classes are loaded before registration.

    # Eager-load tools and resources to ensure they are discovered, especially in development.
    Rails.autoloaders.main.eager_load_dir(Rails.root.join("app/tools"))
    Rails.autoloaders.main.eager_load_dir(Rails.root.join("app/resources"))

    server.register_tools(*ApplicationTool.descendants)
    server.register_resources(*ApplicationResource.descendants)

# alternatively, you can register tools and resources manually:
# server.register_tool(MyTool)
# server.register_resource(MyResource)
end
end
32 changes: 32 additions & 0 deletions docs/MCP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Model Context Protocol (MCP)

This application ships with [fast-mcp](https://github.com/yjacquin/fast-mcp), a Ruby implementation of the Model Context Protocol. The middleware is mounted at `/mcp` and exposes two endpoints:

- `POST /mcp/messages` – JSON‑RPC endpoint for MCP requests
- `GET /mcp/sse` – Server‑sent events stream

## Defining tools and resources

Add tools under `app/tools/` and resources under `app/resources/`. Tools inherit from `ApplicationTool` and resources from `ApplicationResource`.

Any subclasses of these base classes are automatically registered with the MCP server on boot.

## Example request

Once the Rails server is running, an MCP client can send a JSON‑RPC message to invoke a tool:

```bash
curl -X POST http://localhost:3000/mcp/messages \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/execute","params":{"name":"your_tool","arguments":{}}}'
```

Replace `your_tool` with the name of a tool you defined.

Use the SSE endpoint to subscribe to resource updates or other MCP events:

```bash
curl http://localhost:3000/mcp/sse
```

Consult the [fast-mcp documentation](https://github.com/yjacquin/fast-mcp) for full protocol details.
Loading