Active Context
A Ruby on Rails engine that provides Model Context Protocol (MCP) capabilities to Rails applications. This gem allows you to easily create and expose MCP-compatible tools from your Rails application.
Installation
Add this line to your application's Gemfile:
gem 'active_mcp'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install active_mcp
Setup
- Mount the ActiveMcp engine in your
config/routes.rb:
Rails.application.routes.draw do
mount ActiveMcp::Engine, at: "/mcp"
# Your other routes
end
- Create a tool by inheriting from
ActiveMcp::Tool:
class CreateNoteTool < ActiveMcp::Tool
description "Create Note!!"
property :title, :string
property :content, :string
def call(title:, content:)
Note.create(title:, content:)
"Created!"
end
end
- Start the MCP server:
server = ActiveMcp::Server.new(
name: "ActiveMcp DEMO",
uri: 'https://your-app.example.com/mcp'
)
server.start
Rails Generators
MCP Rails provides generators to help you quickly create new MCP tools:
# Generate a new MCP tool
$ rails generate active_mcp:tool search_users
This creates a new tool file at app/models/tools/search_users_tool.rb with the following starter code:
class SearchUsersTool < ActiveMcp::Tool
description 'Search users'
property :param1, :string, required: true, description: 'First parameter description'
property :param2, :string, required: false, description: 'Second parameter description'
# Add more parameters as needed
def call(param1:, param2: nil, auth_info: nil, **args)
# auth_info = { type: :bearer, token: 'xxx', header: 'Bearer xxx' }
# Implement your tool logic here
"Tool executed successfully with #{param1}"
end
end
You can then customize the generated tool to fit your needs.
Input Schema
property :name, :string, required: true, description: 'User name'
property :age, :integer, required: false, description: 'User age'
property :addresses, :array, required: false, description: 'User addresses'
property :preferences, :object, required: false, description: 'User preferences'
Supported types include:
:string:integer:number(float/decimal):boolean:array:object(hash/dictionary):null
Using with MCP Clients
Any MCP-compatible client can connect to your server. The most common way is to provide the MCP server URL:
http://your-app.example.com/mcp
Clients will discover the available tools and their input schemas automatically through the MCP protocol.
Authentication Flow
ActiveMcp supports receiving authentication credentials from MCP clients and forwarding them to your Rails application. There are two ways to handle authentication:
1. Using Server Configuration
When creating your MCP server, you can pass authentication options that will be included in every request:
server = ActiveMcp::Server.new(
name: "ActiveMcp DEMO",
uri: 'http://localhost:3000/mcp',
auth: {
type: :bearer, # or :basic
token: ENV[:ACCESS_TOKEN]
}
)
server.start
2. Custom Controller with Auth Handling
For more advanced authentication, create a custom controller that handles the authentication flow:
class CustomController < ActiveMcpController
before_action :authenticate
private
def authenticate
# Extract auth from MCP request
auth_header = request.headers['Authorization']
if auth_header.present?
# Process the auth header (Bearer token, etc.)
token = auth_header.split(' ').last
# Validate the token against your auth system
user = User.find_by_token(token)
unless user
render_error(-32600, "Authentication failed")
return false
end
# Set current user for tool access
Current.user = user
else
render_error(-32600, "Authentication required")
return false
end
end
end
3. Using Auth in Tools
Authentication information is automatically passed to your tools through the auth_info parameter:
class SecuredDataTool < ActiveMcp::Tool
description 'Access secured data'
property :resource_id, :string, required: true, description: 'ID of the resource to access'
def call(resource_id:, auth_info: nil, **args)
# Check if auth info exists
unless auth_info.present?
raise "Authentication required to access this resource"
end
# Extract token from auth info
token = auth_info[:token]
# Validate token and get user
user = User.authenticate_with_token(token)
unless user
raise "Invalid authentication token"
end
# Check if user has access to the resource
resource = Resource.find(resource_id)
if resource.user_id != user.id
raise "Access denied to this resource"
end
# Return the secured data
{
type: "text",
content: resource.to_json
}
end
end
Advanced Configuration
Custom Controller
If you need to customize the MCP controller behavior, you can create your own controller that inherits from ActiveMcpController:
class CustomController < ActiveContexController
# Add custom behavior, authentication, etc.
end
And update your routes:
Rails.application.routes.draw do
post "/mcp", to: "custom_mcp#index"
end
Best Practices
Create a Tool for Each Model
For security reasons, it's recommended to create specific tools for each model rather than generic tools that dynamically determine the model class. This approach:
- Increases security by avoiding dynamic class loading
- Makes your tools more explicit and easier to understand
- Provides better validation and error handling specific to each model
For example, instead of creating a generic search tool, create specific search tools for each model:
# Good: Specific tool for searching users
class SearchUsersTool < ActiveMcp::Tool
description 'Search users by criteria'
property :email, :string, required: false, description: 'Email to search for'
property :name, :string, required: false, description: 'Name to search for'
property :limit, :integer, required: false, description: 'Maximum number of records to return'
def call(email: nil, name: nil, limit: 10)
criteria = {}
criteria[:email] = email if email.present?
criteria[:name] = name if name.present?
users = User.where(criteria).limit(limit)
{
type: "text",
content: users.to_json(only: [:id, :name, :email, :created_at])
}
end
end
# Good: Specific tool for searching posts
class SearchPostsTool < ActiveMcp::Tool
description 'Search posts by criteria'
property :title, :string, required: false, description: 'Title to search for'
property :author_id, :integer, required: false, description: 'Author ID to filter by'
property :limit, :integer, required: false, description: 'Maximum number of records to return'
def call(title: nil, author_id: nil, limit: 10)
criteria = {}
criteria[:title] = title if title.present?
criteria[:author_id] = if .present?
posts = Post.where(criteria).limit(limit)
{
type: "text",
content: posts.to_json(only: [:id, :title, :author_id, :created_at])
}
end
end
Development
After checking out the repo, run bundle install to install dependencies. Then, run bundle exec rake to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/kawakamimoeki/active_mcp. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.