Active MCP ๐
๐ Table of Contents
- Active MCP ๐
- ๐ Table of Contents
- โจ Features
- ๐ฆ Installation
- ๐ Setup
- ๐ MCP Connection Methods
- 1. Direct HTTP Connection
- 2. Standalone MCP Server
- ๐ Rails Generators
- Install Generator
- Tool Generator
- Resource Generator
- ๐งฐ Creating MCP Tools
- ๐ Input Schema
- ๐ Authorization & Authentication
- Authorization for Tools
- Authentication Options
- ๐ฆ MCP Resources
- Creating Resources
- Resource Types
- ๐ฆ MCP Resource Templates
- Creating Resource Templates
- ๐ฌ MCP Prompts
- Creating Prompt
- ๐ก Best Practices
- 1. Create Specific Tool Classes
- 2. Validate and Sanitize Inputs
- 3. Return Structured Responses
- ๐งช Development
- ๐ฅ Contributing
- ๐ License
โจ Features
- Simple Integration: Easily expose Rails functionality as MCP tools
- Resource Support: Share files and data with AI assistants through MCP resources
- Powerful Generators: Quickly scaffold MCP tools and resources with Rails generators
- Authentication Support: Built-in authentication and authorization capabilities
- Flexible Configuration: Multiple deployment and connection options
๐ฆ 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
- Initialize
The easiest way to set up Active MCP in your Rails application is to use the install generator:
$ rails generate active_mcp:install
This generator will create a configuration initializer at config/initializers/active_mcp.rb
- Create a tool by inheriting from
ActiveMcp::Tool::Base:
$ rails generate active_mcp:tool create_note
class CreateNoteTool < ActiveMcp::Tool::Base
def tool_name
"Create Note"
end
def description
"Create Note"
end
argument :title, :string, required: true
argument :content, :string, required: true
def call(title:, content:, context:)
note = Note.create(title: title, content: content)
"Created note with ID: #{note.id}"
end
end
- Create schema for your application:
class MySchema < ActiveMcp::Schema::Base
tool CreateNoteTool.new
end
- Create controller ans set up routing:
class MyMcpController < ActiveMcp::BaseController
def schema
MySchema.new(context:)
end
end
Rails.application.routes.draw do
post "/mcp", to: "my_mcp#index"
# Your other routes
end
๐ MCP Connection Methods
Active MCP supports two connection methods:
1. Direct HTTP Connection
Set your MCP client to connect directly to your Rails application:
https://your-app.example.com/mcp
2. Standalone MCP Server
Start a dedicated MCP server that communicates with your Rails app:
# script/mcp_server.rb
server = ActiveMcp::Server.new(
name: "My App MCP Server",
uri: 'https://your-app.example.com/mcp'
)
server.start
Then configure your MCP client:
{
"mcpServers": {
"my-rails-app": {
"command": "/path/to/ruby",
"args": ["/path/to/script/mcp_server.rb"]
}
}
}
๐ Rails Generators
Active MCP provides generators to help you quickly set up and extend your MCP integration:
Install Generator
Initialize Active MCP in your Rails application:
$ rails generate active_mcp:install
Tool Generator
Create new MCP tools quickly:
$ rails generate active_mcp:tool search_users
This creates a new tool file at app/mcp/tools/search_users_tool.rb with ready-to-customize starter code.
Resource Generator
Generate new MCP resources to share data with AI:
$ rails generate active_mcp:resource profile_image
This creates a new resource file at app/mcp/resources/profile_image_resource.rb that you can customize to provide various types of content to AI assistants.
๐งฐ Creating MCP Tools
MCP tools are Ruby classes that inherit from ActiveMcp::Tool::Base and define an interface for AI to interact with your application:
class SearchUsersTool < ActiveMcp::Tool::Base
def tool_name
"Search Users"
end
def description
'Search users by criteria'
end
argument :email, :string, required: false, description: 'Email to search for'
argument :name, :string, required: false, description: 'Name to search for'
argument :limit, :integer, required: false, description: 'Maximum number of records to return'
def call(email: nil, name: nil, limit: 10, context: {})
criteria = {}
criteria[:email] = email if email.present?
criteria[:name] = name if name.present?
users = User.where(criteria).limit(limit)
users.attributes
end
end
๐ Input Schema
Define arguments for your tools using the argument method:
argument :name, :string, required: true, description: 'User name'
argument :age, :integer, required: false, description: 'User age'
argument :addresses, :array, required: false, description: 'User addresses'
argument :preferences, :object, required: false, description: 'User preferences'
Supported types:
| Type | Description |
|---|---|
:string |
Text values |
:integer |
Whole numbers |
:number |
Decimal numbers (float/decimal) |
:boolean |
True/false values |
:array |
Lists of values |
:object |
Hash/dictionary structures |
:null |
Null values |
๐ Authorization & Authentication
Authorization for Tools
Control access to tools by overriding the visible? class method:
class AdminOnlyTool < ActiveMcp::Tool::Base
def tool_name
"Admin-only tool"
end
def description
"Admin-only tool"
end
argument :command, :string, required: true, description: "Admin command"
# Only allow admins to access this tool
def visible?(context:)
return false unless context
return false unless context[:auth_info][:type] == :bearer
# Check if the token belongs to an admin
context[:auth_info] == "admin-token" || User.find_by_token(context[:auth_info])&.admin?
end
def call(command:, context: {})
# Tool implementation
end
end
Authentication Options
1. Server Configuration
server = ActiveMcp::Server.new(
name: "My Secure MCP Server",
uri: 'http://localhost:3000/mcp',
auth: {
type: :bearer,
token: ENV['MCP_AUTH_TOKEN']
}
)
server.start
2. Token Verification in Tools
def call(resource_id:, context: {})
# Check if authentication is provided
unless context[:auth_info].present?
raise "Authentication required"
end
# Verify the token
user = User.authenticate_with_token(context[:auth_info][:token])
unless user
raise "Invalid authentication token"
end
# Proceed with authenticated operation
# ...
end
๐ฆ MCP Resources
MCP Resources allow you to share data and files with AI assistants. Resources have a URI, MIME type, and can return either text or binary data.
Creating Resources
Resources are Ruby classes **Resource:
class UserResource < ActiveMcp::Resource::Base
def initialize(id:)
@user = User.find(id)
end
def resource_name
@user.name
end
def uri
"data://localhost/users/#{@user.id}"
end
def mime_type
"application/json"
end
def description
@user.profile
end
def visible?(context:)
# Your logic...
end
def text
# Return JSON data
{
id: @user.id,
name: @user.name,
email: @user.email,
created_at: @user.created_at
}
end
end
class MySchema < ActiveMcp::Schema::Base
User.all.each do |user|
resource UserResource.new(id: user.id)
end
end
Resource Types
Resources can return two types of content:
- Text Content - Use the
textmethod to return structured data:
def text
# Return strings, arrays, hashes, or any JSON-serializable object
{ items: Product.all.map(&:attributes) }
end
- Binary Content - Use the
blobmethod to return binary files:
class ImageResource < ActiveMcp::Resource::Base
class << self
def mime_type
"image/png"
end
end
def resource_name
"profile_image"
end
def uri
"data://localhost/image"
end
def description
"Profile image"
end
def blob
# Return binary file content
File.read(Rails.root.join("public", "profile.png"))
end
end
Resources can be protected using the same authorization mechanism as tools:
def visible?(context: {})
return false unless context
return false unless context[:auth_info][:type] == :bearer
# Check if the token belongs to an admin
User.find_by_token(context[:auth_info][:token])&.admin?
end
๐ฆ MCP Resource Templates
MCP Resource Teamplates allow you to define template of resources.
Creating Resource Templates
Resources are Ruby classes **ResourceTemplates:
class UserResource < ActiveMcp::Resource::Base
class << self
def resource_template_name
"users"
end
def uri_template
"data://localhost/users/{id}"
end
def mime_type
"application/json"
end
def description
"This is a test."
end
def visible?(context:)
# Your logic...
end
end
argument :id, ->(value) do
User.all.pluck(:id).filter { _1.match(value) }
end
def initialize(id:)
@user = User.find(id)
end
def resource_name
@user.name
end
def description
@user.profile
end
def uri
"data://localhost/users/#{@user.name}"
end
def text
{ name: @user.name }
end
end
class MySchema < ActiveMcp::Schema::Base
User.all.each do |user|
resource UserResource.new(id: user.id)
end
end
๐ฌ MCP Prompts
MCP Prompts allow you to define prompt set.
Creating Prompt
Resources are Ruby classes **Prompt:
class HelloPrompt < ActiveMcp::Prompt::Base
class << self
def prompt_name
"hello"
end
def description
"This is a test."
end
def visible?(context:)
# Your logic...
end
end
argument :name, ->(value) do
User.all.pluck(:name).filter { _1.match(value) }
end
def initialize(name:)
@name = name
end
def
[
ActiveMcp::Message::Text.new(
role: "user",
text: "Hello! #{@name}"
),
ActiveMcp::Message::Image.new(
role: "assistant",
data: File.read(file),
mime_type: "image/png"
),
ActiveMcp::Message::Audio.new(
role: "user",
data: File.read(file),
mime_type: "audio/mpeg"
),
ActiveMcp::Message::Resource.new(
role: "assistant",
resource: UserResource.new(name: @name)
)
]
end
end
class MySchema < ActiveMcp::Schema::Base
prompt HelloPrompt
end
๐ก Best Practices
1. Create Specific Tool Classes
Create dedicated tool classes for each model or operation instead of generic tools:
# โ
GOOD: Specific tool for a single purpose
class SearchUsersTool < ActiveMcp::Tool::Base
# ...specific implementation
end
# โ BAD: Generic tool that dynamically loads models
class GenericSearchTool < ActiveMcp::Tool::Base
# Avoid this pattern - security and maintainability issues
end
2. Validate and Sanitize Inputs
Always validate and sanitize inputs in your tool implementations:
def call(user_id:, context: {})
# Validate input
unless user_id.is_a?(Integer) || user_id.to_s.match?(/^\d+$/)
raise "Invalid user ID format"
end
# Proceed with validated data
user = User.find_by(id: user_id)
# ...
end
3. Return Structured Responses
Return structured responses that are easy for AI to parse:
def call(query:, context: {})
results = User.search(query)
{
content: results.to_json(only: [:id, :name, :email]),
metadata: {
count: results.size,
query: query
}
}
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/moekiorg/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.