MCP Lite

MCP Lite

[![Gem Version](https://badge.fury.io/rb/mcp_lite.svg)](https://badge.fury.io/rb/mcp_lite) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) The Lightweight implementation of Model Context Protocol (MCP) in Ruby.

Caution

This is an early version of MCP Lite. The API may change in future releases. Please check the CHANGELOG for updates.

Active MCP is deprecated. This library is a lightweight implementation of the Model Context Protocol (MCP) in Ruby, designed to be easy to use and integrate into your applications.

๐Ÿ“– Table of Contents

๐Ÿ“ฆ Installation

Add this line to your application's Gemfile:

gem 'mcp_lite'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install mcp_lite

๐Ÿš€ Setup

class CreateNoteTool < McpLite::Tool::Base
  tool_name "create_note"

  description "Create Note"

  argument :title, :string, required: true
  argument :content, :string, required: true
  argument :published_at, :string, required: true,
    visible: ->(context) { context[:role] == "admin" }

  def call(title:, content:, context:)
    note = Note.create(title: title, content: content)

    [{ type: "text", text: "Created note with ID: #{note.id}" }]
  end
end
class MySchema < McpLite::Schema::Base
  tools CreateNoteTool
end
class MyMcpController < McpLite::BaseController
  def index
    render json: MySchema.execute(param:, context:)
  end
end
Rails.application.routes.draw do
  post "/mcp", to: "my_mcp#index"

  # Your other routes
end

๐Ÿ”Œ MCP Connection Methods

Start a dedicated MCP server that communicates with your Ruby app:

# script/mcp_server.rb
server = McpLite::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"]
    }
  }
}

๐Ÿงฐ Creating MCP Tools

MCP tools are Ruby classes that inherit from McpLite::Tool::Base and define an interface for AI to interact with your application:

class SearchUsersTool < McpLite::Tool::Base
  tool_name "Search Users"

  description 'Search users by criteria'

  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)

    [{ type: "text", text: users.attributes.to_json }]
  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 < McpLite::Tool::Base
  tool_name "admin_only_tool"

  description "Admin-only tool"

  argument :command, :string, required: true, description: "Admin command"

  # Only allow admins to access this tool
  def self.visible?(context:)
    return false unless context
    return false unless context[:current_user]

    # Check if the token belongs to an admin
    User.find_by_token(context[:current_user][:token])&.admin?
  end

  def call(command:, context: {})
    # Tool implementation
  end
end

Authentication Options

1. Server Configuration

server = McpLite::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[:current_user].present?
    raise "Authentication required"
  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 < McpLite::Resource::Base
  mime_type "application/json"

  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 self.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 < McpLite::Schema::Base
  resource UserResource, items: User.all.each do |user|
    { id: user.id }
  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 < McpLite::Resource::Base
  mime_type "image/png"

  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[:current_user]

  # Check if the token belongs to an admin
  User.find_by_token(context[:current_user][: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 < McpLite::Resource::Base
  resource_template_name "users"

  uri_template "data://localhost/users/{id}"

  mime_type "application/json"

  description "This is a test."

  argument :id, complete: ->(value, context) do
    User.all.pluck(:id).filter { _1.match(value) }
  end

  def self.visible?(context:)
    # Your logic...
  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 < McpLite::Schema::Base
  resource UserResource, items: User.all.each do |user|
    { id: user.id }
  end
end

๐Ÿ’ฌ MCP Prompts

MCP Prompts allow you to define prompt set.

Creating Prompt

Resources are Ruby classes **Prompt:

class HelloPrompt < McpLite::Prompt::Base
  prompt_name "hello"

  description "This is a test."

  argument :name, required: true, description: "User name", complete: ->(value, context) do
    User.all.pluck(:name).filter { _1.match(value) }
  end

  def self.visible?(context:)
    # Your logic...
  end

  def messages(name:)
    [
      McpLite::Message::Text.new(
        role: "user",
        text: "Hello! #{name}"
      ),
      McpLite::Message::Image.new(
        role: "assistant",
        data: File.read(file),
        mime_type: "image/png"
      ),
      McpLite::Message::Audio.new(
        role: "user",
        data: File.read(file),
        mime_type: "audio/mpeg"
      ),
      McpLite::Message::Resource.new(
        role: "assistant",
        resource: UserResource.new(name: @name)
      )
    ]
  end
end
class MySchema < McpLite::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 < McpLite::Tool::Base
  # ...specific implementation
end

# โŒ BAD: Generic tool that dynamically loads models
class GenericSearchTool < McpLite::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)

  [{
    type: "text",
    text: {
      content: results.to_json(only: [:id, :name, :email]),
      metadata: {
        count: results.size,
        query: query
      }
    }.to_json
  }]
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/mcp_lite. 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.

MCP Lite

Model Context Protocol (MCP) implementation in Ruby.

Installation

Add this line to your application's Gemfile:

gem 'mcp_lite'

Usage

Basic Usage (Without Rails)

require 'mcp_lite'

class MySchema < McpLite::Schema::Base
  tool SearchTool
  resource UserResource, items: [
    {name: "UserA"},
    {name: "UserB"}
  ]
  prompt HelloPrompt
end

# Execute MCP request
result = MySchema.execute(
  params: {
    method: "tools/list",
    params: {},
    jsonrpc: "2.0"
  },
  context: {
    current_user: User.find_by_token(token)
  }
)

Rails Integration

# app/controllers/mcp_controller.rb
class McpController < McpLite::BaseController
  def index
    render json: MySchema.execute(param:, context:)
  end
end

# config/routes.rb
Rails.application.routes.draw do
  post "/mcp", to: "mcp#index"
end

// ...existing documentation...