Rails MCP Engine
Rails MCP Engine provides a unified tool-definition pipeline for Rails 8 applications. Service classes declare Sorbet-typed signatures and metadata once, and the engine auto-generates both RubyLLM and FastMCP tool classes at boot.
Core Dependencies
This engine is built on top of these powerful libraries:
-
RubyLLM: For generating LLM-compatible tool definitions. Supports versions
>= 1.9, < 3(1.x and 2.x). - FastMCP: For creating Model Context Protocol (MCP) servers.
- Sorbet: For static type checking and signature definitions.
Installation
Add this line to your application's Gemfile:
gem 'rails_mcp_engine'And then execute:
bundle installHow it works
-
Service classes live under the
Tools::namespace, extendToolMeta, and expose a single Sorbet-signed entrypoint (defaults to#call). - ToolMeta DSL captures names, descriptions, and parameter metadata, while Sorbet signatures provide the type source of truth.
- The ToolSchema pipeline merges Sorbet type information with metadata, producing a unified schema AST.
-
Factories (
ToolSchema::RubyLlmFactoryandToolSchema::FastMcpFactory) transform the AST into RubyLLM tools and FastMCPApplicationToolsubclasses, respectively. - The Engine automatically iterates through registered service classes and generates both tool types during Rails initialization (
to_prepare).
Defining a tool service
Create a service that extends ToolMeta and uses Sorbet for the entrypoint signature. Only business logic belongs here; tool wrappers are generated.
# app/services/tools/book_meeting_service.rb
class Tools::BookMeetingService
extend T::Sig
extend ToolMeta
tool_name "book_meeting"
tool_description "Books a meeting."
tool_param :window, description: "Start/finish window"
tool_param :participants, description: "Email recipients"
sig do
params(
window: T::Hash[Symbol, String],
participants: T::Array[String]
).returns(T::Hash[Symbol, T.untyped])
end
def call(window:, participants:)
# ... business logic ...
end
endOn boot, the engine generates:
-
Tools::BookMeeting < RubyLLM::Toolwith a matchingparamsblock on RubyLLM 1.x orparametersblock on 2.x. -
Mcp::BookMeetingTool < ApplicationToolwith a matchingargumentsblock.
Default meta tool
Tools::MetaToolService is included by default to explore and execute registered tools at runtime. It exposes a single action argument with supporting keywords:
-
list: return full tool details (name, description, params, return type). -
list_summary: return only names and descriptions. -
search: providequeryto fuzzy-match name/description. -
get: providetool_nameto fetch a full schema payload. -
run: providetool_nameandargumentsto invoke a tool through its service class.
Note: The
registeraction is not available via the tool interface for security reasons. Developers can manually register tools usingTools::MetaToolService.new.register_tool("ClassName")in their code.
Example invocation from a console:
Tools::MetaToolService.new.call(action: 'run', tool_name: 'book_meeting', arguments: { window: { start: '...', finish: '...' }, participants: ['a@example.com'] })Tool Registration Hooks
You can attach before_call and after_call hooks when manually registering tools. These hooks are useful for logging, tracing, or other side effects.
Tools::MetaToolService.new.register_tool(
'Tools::BookMeetingService',
before_call: ->(args) { Rails.logger.info("Calling tool with #{args}") },
after_call: ->(result) { Rails.logger.info("Tool returned #{result}") }
)-
before_call: AProcthat receives the arguments hash. -
after_call: AProcthat receives the result.
These hooks are executed around the tool's entrypoint method for both RubyLLM and FastMCP wrappers.
Using Tools in Host Application
You can easily fetch the generated RubyLLM tool classes for use in your host application (e.g., when calling an LLM API):
# Fetch specific tool classes by name
tool_classes = Tools::MetaToolService.ruby_llm_tools(['book_meeting', 'calculator'])
# Use them with RubyLLM
chat = RubyLLM.chat(model: 'gpt-4o').with_tools(*tool_classes)
response = chat.ask('Book a meeting with the team.')Development
After checking out the repo, run bundle install to install dependencies. Then, run bundle exec rails test to run the tests.
Gem release
ruby script/release.rb adapts Collavre's release script for this single gem repository.
Run it from a clean main checkout with Ruby 3.2+, Bundler, Git push access, and RubyGems credentials (gem signin, including MFA when prompted). It pulls main with fast-forward only, shows commits since the last version change, and prompts for a higher MAJOR.MINOR.PATCH version (default: next patch).
To use a known release commit instead of the last version-file change:
ruby script/release.rb --last-release-commit <SHA>The full or abbreviated commit SHA must exist locally and be an ancestor of the updated HEAD. Changes after that commit (excluding the commit itself) are shown and used in release notes. Invalid, ambiguous, or unrelated commits stop the script before version input or file changes. Omit the option to keep the existing last-version-change behavior. Use --help to see available options.
The script creates a release branch, updates the version, RELEASE_NOTES.md, and lockfile, runs Rails unit and system tests, commits those files, builds into build/gems, and publishes to RubyGems. It then fast-forwards main, pushes it, and pushes only the new vVERSION tag. System tests require Chrome/Chromium, as in CI.
Dependency or test failures restore the original files and return to main, keeping the release branch for diagnosis. Failures after the commit leave the commit and build available. If upload fails, retry the printed gem push command; after a successful upload, finish the Git steps manually if necessary. RubyGems publication cannot be undone by Git rollback; do not rerun the whole script after a successful upload.
To install this gem onto your local machine, run bundle exec rake install. Use ruby script/release.rb for releases as described above.
Playground & Chat
The engine includes a built-in playground and chat interface for testing your tools.
-
Mount the engine: Ensure the engine is mounted in your
config/routes.rb(e.g.,mount RailsMcpEngine::Engine => '/rails_mcp_engine'). -
Access the Playground: Navigate to
/rails_mcp_engine/playgroundto register and test tools individually. -
Access the Chat: Navigate to
/rails_mcp_engine/chatto test tools within a conversational interface using OpenAI models.
The playground allows you to:
- Register tool services dynamically by pasting Ruby code.
- Run registered tools with JSON arguments.
- View tool schemas and details.
The chat interface allows you to:
- Chat with OpenAI models (e.g., gpt-4o).
- Automatically invoke registered tools during the conversation.
- View tool calls and results within the chat history.
Screenshots:
| Playground | Chat |
|---|---|
![]() |
![]() |
License
The gem is available as open source under the terms of the MIT License.

