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
- ๐ฅ Using Context in the Schema
- ๐ก 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
def tools
[
CreateNoteTool.new
]
end
end
- Create controller ans set up routing:
class MyMcpController < ActiveMcp::BaseController
private
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
def resources
User.all.each do |user|
UserResource.new(id: user.id)
end
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
Resource teamplates are Ruby classes **Resource:
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, complete: ->(value, context) 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
def resources
User.all.each do |user|
UserResource.new(id: user.id)
end
end
end
๐ฌ MCP Prompts
MCP Prompts allow you to define prompt set.
Creating Prompt
Resources are Ruby classes **Prompt:
class HelloPrompt < ActiveMcp::Prompt::Base
argument :name, required: true, description: "User name", complete: ->(value, context) do
User.all.pluck(:name).filter { _1.match(value) }
end
def initialize(greeting:)
@greeting = greeting
end
def prompt_name
"hello"
end
def description
"This is a test."
end
def visible?(context:)
# Your logic...
end
def (name:)
[
ActiveMcp::Message::Text.new(
role: "user",
text: "#{@greeting} #{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
def prompts
[
HelloPrompt.new(greeting: "Hello!")
]
end
end
๐ฅ Using Context in the Schema
class MySchema < ActiveMcp::Schema::Base
def prompts
user = User.find_by_token(context[:auth_info][:token])
user.greetings.map do |greeting|
GreetingPrompt.new(greeting: greeting)
end
end
end
class GreetingPrompt < ActiveMcp::Prompt::Base
def initialize(greeting:)
@greeting = greeting
end
def prompt_name
"greeting_#{@greeting.text}"
end
def
# ...
end
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.
๐ฅ 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.