Skip to content

A Ruby on Rails engine that provides Model Context Protocol (MCP) πŸ€–πŸšŠ

License

Notifications You must be signed in to change notification settings

facexiang/active_mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Active MCP πŸ”Œ

Gem Version MIT License Rails

A Ruby on Rails engine for the Model Context Protocol (MCP) - connect your Rails apps to AI tools with minimal effort.

πŸ“– Table of Contents

✨ 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

  1. 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

  1. 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
  1. Create schema for your application:
class MySchema < ActiveMcp::Schema::Base
  def tools
    [
      CreateNoteTool.new
    ]
  end
end
  1. 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
  class << self
    def mime_type
      "application/json"
    end
  end

  def initialize(id:)
    @user = User.find(id)
  end

  def resource_name
    @user.name
  end

  def uri
    "data://localhost/users/#{@user.id}"
  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:

  1. Text Content - Use the text method to return structured data:
def text
  # Return strings, arrays, hashes, or any JSON-serializable object
  { items: Product.all.map(&:attributes) }
end
  1. Binary Content - Use the blob method 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 messages(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 messages
    # ...
  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.

About

A Ruby on Rails engine that provides Model Context Protocol (MCP) πŸ€–πŸšŠ

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •