freshcrate
Home > MCP Servers > action_mcp

Description

Rails Engine with MCP compliant Spec.

README

ActionMCP

ActionMCP is a Ruby gem focused on providing Model Context Protocol (MCP) capability to Ruby on Rails applications, specifically as a server.

ActionMCP is designed for production Rails environments and does not support STDIO transport. STDIO is not included because it is not production-ready and is only suitable for desktop or script-based use cases. Instead, ActionMCP is built for robust, network-based deployments.

The client functionality in ActionMCP is intended to connect to remote MCP servers, not to local processes via STDIO.

It offers base classes and helpers for creating MCP applications, making it easier to integrate your Ruby/Rails application with the MCP standard.

With ActionMCP, you can focus on your app's logic while it handles the boilerplate for MCP compliance.

Introduction

Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to large language models (LLMs).

Think of it as a universal interface for connecting AI assistants to external data sources and tools.

MCP allows AI systems to plug into various resources in a consistent, secure way, enabling two-way integration between your data and AI-powered applications.

This means an AI (like an LLM) can request information or actions from your application through a well-defined protocol, and your app can provide context or perform tasks for the AI in return.

ActionMCP is targeted at developers building MCP-enabled Rails applications. It simplifies the process of integrating Ruby and Rails apps with the MCP standard by providing a set of base classes and an easy-to-use server interface.

Protocol Support

ActionMCP supports MCP 2025-06-18 (current) with backward compatibility for MCP 2025-03-26. The protocol implementation is fully compliant with the MCP specification, including:

  • JSON-RPC 2.0 transport layer
  • Capability negotiation during initialization
  • Error handling with proper error codes (-32601 for method not found, -32002 for consent required)
  • Session management with resumable sessions
  • Change notifications for dynamic capability updates

For a detailed (and entertaining) breakdown of protocol versions, features, and our design decisions, see The Hitchhiker's Guide to MCP.

Don't Panic: The guide contains everything you need to know about surviving MCP protocol versions.

Note: STDIO transport is not supported in ActionMCP. This gem is focused on production-ready, network-based deployments. STDIO is only suitable for desktop or script-based experimentation and is intentionally excluded.

Instead of implementing MCP support from scratch, you can subclass and configure the provided Prompt, Tool, and ResourceTemplate classes to expose your app's functionality to LLMs.

ActionMCP handles the underlying MCP message format and routing, so you can adhere to the open standard with minimal effort.

In short, ActionMCP helps you build an MCP server (the component that exposes capabilities to AI) more quickly and with fewer mistakes.

Client connections: The client part of ActionMCP is meant to connect to remote MCP servers only. Connecting to local processes (such as via STDIO) is not supported.

Requirements

  • Ruby: 3.4.8+ or 4.0.0+
  • Rails: 8.1.1+
  • Database: PostgreSQL, MySQL, or SQLite3

ActionMCP is tested against Ruby 3.4.8 and 4.0.0 with Rails 8.1.1+.

Installation

To start using ActionMCP, add it to your project:

# Add gem to your Gemfile
$ bundle add actionmcp

# Install dependencies
bundle install

# Copy migrations from the engine
bin/rails action_mcp:install:migrations

# Generate base classes and configuration
bin/rails generate action_mcp:install

# Create necessary database tables
bin/rails db:migrate

The action_mcp:install generator will:

  • Create base application classes (ApplicationGateway, ApplicationMCPTool, etc.)
  • Generate the MCP configuration file (config/mcp.yml)
  • Set up the basic directory structure for MCP components (app/mcp/)

Database migrations are copied separately using bin/rails action_mcp:install:migrations.

Core Components

ActionMCP provides three core abstractions to streamline MCP server development:

ActionMCP::Prompt

ActionMCP::Prompt enables you to create reusable prompt templates that can be discovered and used by LLMs. Each prompt is defined as a Ruby class that inherits from ApplicationMCPPrompt.

Key features:

  • Define expected arguments with descriptions and validation rules
  • Build multi-step conversations with mixed content types
  • Support for text, images, audio, and resource attachments
  • Add messages with different roles (user/assistant)

Example:

class AnalyzeCodePrompt < ApplicationMCPPrompt
  prompt_name "analyze_code"
  description "Analyze code for potential improvements"

  argument :language, description: "Programming language", default: "Ruby"
  argument :code, description: "Code to explain", required: true

  validates :language, inclusion: { in: %w[Ruby Python JavaScript] }

  def perform
    render(text: "Please analyze this #{language} code for improvements:")
    render(text: code)

    # You can add assistant messages too
    render(text: "Here are some things to focus on in your analysis:", role: :assistant)

    # Even add resources if needed
    render(resource: "file://documentation/#{language.downcase}_style_guide.pdf",
           mime_type: "application/pdf",
           blob: get_style_guide_pdf(language))
  end

  private

  def get_style_guide_pdf(language)
    # Implementation to retrieve style guide as base64
  end
end

Prompts can be executed by instantiating them and calling the call method:

analyze_prompt = AnalyzeCodePrompt.new(language: "Ruby", code: "def hello; puts 'Hello, world!'; end")
result = analyze_prompt.call

ActionMCP::Tool

ActionMCP::Tool allows you to create interactive functions that LLMs can call with arguments to perform specific tasks. Each tool is a Ruby class that inherits from ApplicationMCPTool.

Key features:

  • Define input properties with types, descriptions, and validation
  • Return multiple response types (text, images, errors)
  • Progressive responses with multiple render calls
  • Automatic input validation based on property definitions
  • Consent management for sensitive operations

Example:

class CalculateSumTool < ApplicationMCPTool
  tool_name "calculate_sum"
  description "Calculate the sum of two numbers"

  property :a, type: "number", description: "The first number", required: true
  property :b, type: "number", description: "The second number", required: true

  def perform
    sum = a + b
    render(text: "Calculating #{a} + #{b}...")
    render(text: "The sum is #{sum}")

    # You can report errors if needed
    if sum > 1000
      report_error("Warning: Sum exceeds recommended limit")
    end

    # Or even images
    render(image: generate_visualization(a, b), mime_type: "image/png")
  end

  private

  def generate_visualization(a, b)
    # Implementation to create a visualization as base64
  end
end

Consent Management

For tools that perform sensitive operations (file system access, database modifications, external API calls), you can require explicit user consent:

class FileSystemTool < ApplicationMCPTool
  tool_name "read_file"
  description "Read contents of a file"

  # Require explicit consent before execution
  requires_consent!

  property :file_path, type: "string", description: "Path to file", required: true

  def perform
    # This code only runs after user grants consent
    content = File.read(file_path)
    render(text: "File contents: #{content}")
  end
end

Consent Flow:

  1. When a consent-required tool is called without consent, it returns a JSON-RPC error with code -32002
  2. The client must explicitly grant consent for the specific tool
  3. Once granted, the tool can execute normally for that session
  4. Consent is session-scoped and can be revoked at any time

Managing Consent:

# Check if consent is granted
session.consent_granted_for?("read_file")

# Grant consent for a tool
session.grant_consent("read_file")

# Revoke consent
session.revoke_consent("read_file")

Tools can be executed by instantiating them and calling the call method:

sum_tool = CalculateSumTool.new(a: 5, b: 10)
result = sum_tool.call

Structured output (output_schema)

Advertise a JSON Schema for your tool's structuredContent and return machine-validated results alongside any text output.

class PriceQuoteTool < ApplicationMCPTool
  tool_name "price_quote"
  description "Return a structured price quote"

  property :sku, type: "string", description: "SKU to price", required: true

  output_schema do
    string :sku, required: true, description: "SKU that was priced"
    number :price_cents, required: true, description: "Total price in cents"
    object :meta do
      string :currency, required: true, enum: %w[USD EUR GBP]
      boolean :cached, default: false
    end
  end

  def perform
    price_cents = lookup_price_cents(sku) # Implement your lookup

    render structured: { sku: sku,
                         price_cents: price_cents,
                         meta: { currency: "USD", cached: false } }
  end
end

The schema is included in the tool definition, and the structured payload is emitted as structuredContent in the response while remaining compatible with text/audio/image renders.

Returning resource links from a tool

When you want to hand back a URI instead of embedding the payload, use the built-in render_resource_link, which produces the MCP resource_link content type.

class ReportLinkTool < ApplicationMCPTool
  tool_name "report_link"
  description "Return a downloadable report link"

  property :report_id, type: "string", required: true

  def perform
    render_resource_link(
      uri: "reports://#{report_id}.json",
      name: "Report #{report_id}",
      description: "Downloadable JSON for report #{report_id}",
      mime_type: "application/json"
    )
  end
end

Clients can resolve the URI with a separate resources/read call, keeping tool responses lightweight while still discoverable.

Task-augmented tools (async execution with progress)

Use MCP Tasks when work might take seconds/minutes. Advertise task support with task_required! (or task_optional!) and let callers opt in by sending _meta.task on tools/call. While running as a task, you can emit progress updates with report_progress!.

class BatchIndexTool < ApplicationMCPTool
  tool_name "batch_index"
  description "Index many items asynchronously with progress updates"

  task_required! # advertise that this tool is intended to run as a task
  property :items, type: "array_string", description: "Items to index", required: true

  def perform
    total = items.length
    items.each_with_index do |item, idx|
      index_item(item) # your indexing logic

      percent = ((idx + 1) * 100.0 / total).round
      report_progress!(percent: percent, message: "Indexed #{idx + 1}/#{total}")
    end

    render(text: "Indexed #{total} items")
  end

  private

  def index_item(item)
    # Implement your indexing logic here
  end
end

Call it as a task from a client by adding _meta.task (creates a Task record and runs the tool via ToolExecutionJob):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "batch_index",
    "arguments": { "items": ["a", "b", "c"] },
    "_meta": { "task": { "ttl": 120000, "pollInterval": 2000 } }
  }
}

Poll task status with tasks/get or fetch the result when finished with tasks/result. Use tasks/cancel to stop non-terminal tasks.

ActionMCP::ResourceTemplate

ActionMCP::ResourceTemplate facilitates the creation of URI templates for dynamic resources that LLMs can access. This allows models to request specific data using parameterized URIs.

Templates support two MCP resource surfaces:

  • resources/templates/list — parameterized URI patterns (existing behavior)
  • resources/list — concrete resources with known URIs (via self.list)

Note: Not all MCP clients support both resource endpoints. Claude Code (as of v2.1.50) only calls resources/list, and Codex stubs resource methods entirely. Implement self.list on your templates to ensure resources are visible to all clients. Crush and VS Code support both endpoints.

Example:

class ProductResourceTemplate < ApplicationMCPResTemplate
  uri_template "product/{id}"
  description "Access product information by ID"
  mime_type "application/json"

  parameter :id, description: "Product identifier", required: true

  validates :id, format: { with: /\A\d+\z/, message: "must be numeric" }

  # Optional: enumerate concrete resources for resources/list
  def self.list(session: nil)
    Product.limit(50).map do |product|
      build_resource(
        uri: "product/#{product.id}",
        name: product.name,
        title: "Product ##{product.id}"
      )
    end
  end

  # Resolve a specific resource for resources/read
  def resolve
    product = Product.find_by(id: id)
    return unless product

    ActionMCP::Content::Resource.new(
      "product/#{id}",
      mime_type,
      text: product.to_json
    )
  end
end

Callbacks:

before_resolve do |template|
  # Starting to resolve product: #{template.product_id}
end

after_resolve do |template|
  # Finished resolving product resource for product: #{template.product_id}
end

around_resolve do |template, block|
  start_time = Time.current
  resource = block.call
  resource
end

Resource templates are automatically registered and used when LLMs request resources matching their patterns. See RESOURCE_TEMPLATES.md for the full API reference including pagination, deduplication, and read contract details.

📚 Documentation

ActionMCP provides comprehensive documentation across multiple specialized guides. Each guide focuses on specific aspects to keep information organized and prevent context overload:

Getting Started & Setup

Component Development

  • 📋 TOOLS.MD - Complete guide to developing MCP tools

    • Generator usage and best practices
    • Property definitions, validation, and consent management
    • Output schemas for structured responses
    • Error handling, testing, and security considerations
    • Advanced features like additional properties and authentication context
  • 📝 PROMPTS.MD - Prompt template development guide

    • Creating reusable prompt templates
    • Multi-step conversations and mixed content types
    • Argument validation and prompt chaining
  • 🔗 RESOURCE_TEMPLATES.md - Resource template implementation

    • URI template patterns and parameter extraction
    • Dynamic resource resolution and collections
    • Callbacks and validation patterns

Client & Integration

  • 🔌 CLIENTUSAGE.MD - Complete client implementation guide
    • Session management and resumability
    • Transport configuration and connection handling
    • Tool, prompt, and resource collections
    • Production deployment patterns
  • 🔐 GATEWAY.md - Authentication gateway guide
    • Implementing ApplicationGateway
    • Identifier handling via ActionMCP::Current
    • Auth patterns, error handling, and hardening tips

Protocol & Technical Details

  • 🚀 The Hitchhiker's Guide to MCP - Protocol versions and migration
    • Comprehensive comparison of MCP protocol versions (2024-11-05, 2025-03-26, 2025-06-18)
    • Design decisions and architectural rationale
    • Migration paths and compatibility considerations
    • Feature evolution and technical specifications (Don't Panic!)

Advanced Configuration

Development & Testing

Troubleshooting & Production

💡 Pro Tip: Start with the component-specific guides (TOOLS.MD, PROMPTS.MD, RESOURCE_TEMPLATES.md) for hands-on development, then reference the Hitchhiker's Guide for protocol details and CLIENTUSAGE.MD for integration patterns.

Configuration

ActionMCP is configured via config.action_mcp in your Rails application.

By default, the name is set to your application's name and the version defaults to "0.0.1" unless your app has a version file.

You can override these settings in your configuration (e.g., in config/application.rb):

module Tron
  class Application < Rails::Application
    config.action_mcp.name = "Friendly MCP (Master Control Program)"  # defaults to Rails.application.name
    config.action_mcp.version = "1.2.3"                               # defaults to "0.0.1"
    config.action_mcp.logging_enabled = true                          # defaults to true
    config.action_mcp.logging_level = :info                           # defaults to :info, can be :debug, :info, :warn, :error, :fatal

    # Server instructions - helps LLMs understand the server's purpose
    config.action_mcp.server_instructions = [
      "Use this server to access and control Tron system programs",
      "Helpful for managing system processes and user data"
    ]
  end
end

For dynamic versioning, consider adding the rails_app_version gem.

Server Instructions

Server instructions help LLMs understand what your server is for and when to use it. They describe the server's purpose and goal, not technical details like rate limits or authentication (tools are self-documented via their own descriptions).

Instructions are returned at the top level of the MCP initialization response.

You can configure server instructions in your config/mcp.yml file:

shared:
  # Describe the server's purpose - helps LLMs know when to use this server
  server_instructions:
    - "Use this server to manage Fizzy project tickets and workflows"
    - "Helpful for tracking bugs, features, and sprint planning"

development:
  # Development-specific purpose description
  server_instructions:
    - "Development server for testing Fizzy integration"
    - "Use for prototyping ticket management workflows"

production:
  # Production-specific purpose description
  server_instructions:
    - "Production Fizzy server for managing live project data"

Instructions are sent as a single string (joined by newlines) at the top level of the initialization response, helping LLMs understand your server's purpose.

Session Storage

ActionMCP provides a pluggable session storage system that allows you to choose how sessions are persisted based on your environment and requirements.

Session Store Types

ActionMCP includes three session store implementations:

  1. :volatile - In-memory storage using Concurrent::Hash

    • Default for development and test environments
    • Sessions are lost on server restart
    • Fast and lightweight for local development
    • No external dependencies
  2. :active_record - Database-backed storage

    • Default for production environment
    • Sessions persist across server restarts
    • Supports session resumability
    • Requires database migrations
  3. :test - Special store for testing

    • Tracks notifications and method calls
    • Provides assertion helpers
    • Automatically used in test environment when using TestHelper

Configuration

You can configure the session store type in your Rails configuration or config/mcp.yml:

# config/application.rb or environment files
Rails.application.configure do
  config.action_mcp.session_store_type = :active_record  # or :volatile
end

Or in config/mcp.yml:

# Global session store type (used by both client and server)
session_store_type: volatile

# Client-specific session store type (falls back to session_store_type if not specified)
client_session_store_type: volatile

# Server-specific session store type (falls back to session_store_type if not specified)
server_session_store_type: active_record

The defaults are:

  • Production: :active_record
  • Development: :volatile
  • Test: :volatile (or :test when using TestHelper)

Separate Client and Server Session Stores

You can configure different session store types for client and server operations:

  • session_store_type: Global setting used by both client and server when specific types aren't set
  • client_session_store_type: Session store used by ActionMCP client connections (falls back to global setting)
  • server_session_store_type: Session store used by ActionMCP server sessions (falls back to global setting)

This allows you to optimize each component separately. For example, you might use volatile storage for client sessions (faster, temporary) while using persistent storage for server sessions (maintains state across restarts).

Using Different Session Stores

# The session store is automatically selected based on configuration
# You can access it directly if needed:
session_store = ActionMCP::Server.session_store

# Create a session
session = session_store.create_session(session_id, {
  status: "initialized",
  protocol_version: "2025-03-26",
  # ... other session attributes
})

# Load a session
session = session_store.load_session(session_id)

# Update a session
session_store.update_session(session_id, { status: "active" })

# Delete a session
session_store.delete_session(session_id)

Session Resumability

With the :active_record store, clients can resume sessions after disconnection:

# Client includes session ID in request headers
# Server automatically resumes the existing session
headers["Mcp-Session-Id"] = "existing-session-id"

# If the session exists, it will be resumed
# If not, a new session will be created

Custom Session Stores

You can create custom session stores by inheriting from ActionMCP::Server::SessionStore::Base:

class MyCustomSessionStore < ActionMCP::Server::SessionStore::Base
  def create_session(session_id, payload = {})
    # Implementation
  end

  def load_session(session_id)
    # Implementation
  end

  def update_session(session_id, updates)
    # Implementation
  end

  def delete_session(session_id)
    # Implementation
  end

  def exists?(session_id)
    # Implementation
  end
end

# Register your custom store
ActionMCP::Server.session_store = MyCustomSessionStore.new

Thread Pool Management

ActionMCP uses thread pools to efficiently handle message callbacks. This prevents the system from being overwhelmed by too many threads under high load.

Thread Pool Configuration

You can configure the thread pool in your config/mcp.yml:

production:
  # Thread pool configuration
  min_threads: 10    # Minimum number of threads to keep in the pool
  max_threads: 20    # Maximum number of threads the pool can grow to
  max_queue: 500     # Maximum number of tasks that can be queued

The thread pool will automatically:

  • Start with min_threads threads
  • Scale up to max_threads as needed
  • Queue tasks up to max_queue limit
  • Use caller's thread if queue is full (fallback policy)

Graceful Shutdown

When your application is shutting down, you should call:

ActionMCP::Server.shutdown

This ensures all thread pools are properly terminated and tasks are completed.

Engine and Mounting

WARNING: Do NOT mount ActionMCP::Engine in your routes.rb. ActionMCP is a standalone Rack application that runs on its own port via mcp/config.ru. Mounting it as a Rails engine route will not work correctly.

When you use run ActionMCP.server in your mcp/config.ru, the MCP endpoint is available at the root path (/) by default and can be configured via config.action_mcp.base_path. Always use ActionMCP.server (not ActionMCP::Engine directly) — it initializes required subsystems.

Installing ActionMCP

ActionMCP includes generators to help you set up your project quickly. The install generator creates all necessary base classes and configuration files:

# Install ActionMCP with base classes and configuration
bin/rails generate action_mcp:install

This will create:

  • app/mcp/promp

Release History

VersionChangesUrgencyDate
action_mcp/v0.108.0## [0.108.0](https://github.com/seuros/action_mcp/compare/action_mcp/v0.107.1...action_mcp/v0.108.0) (2026-04-08) ### Features * add resource template test helpers ([f547429](https://github.com/seuros/action_mcp/commit/f5474291cbe0303d858217160cadd18e645a0435)) ### Bug Fixes * **docs:** correct TestHelper method names and argument order ([f83bce6](https://github.com/seuros/action_mcp/commit/f83bce62002d8d0ea7ea1a206839734621c478af)) * guard against nil session when client sends unknown McpHigh4/8/2026

Dependencies & License Audit

Loading dependencies...

Similar Packages

spaceship-mcp🚀 Manage domains, DNS, contacts, and listings with spaceship-mcp, a community-built MCP server for the Spaceship API.main@2026-04-21
comfy-pilot🤖 Create and modify workflows effortlessly with ComfyUI's AI assistant, enabling natural conversations with agents like Claude and Gemini.main@2026-04-21
PlexMCP-OSS🌐 Build a robust MCP gateway platform to enhance your Plex experience with seamless integration and reliable performance.main@2026-04-21
scraper-mcp🌐 Streamline web scraping with Scraper MCP, a server that optimizes content for AI by filtering data and reducing token usage for LLMs.main@2026-04-21
tekmetric-mcp🔍 Ask questions about your shop data in natural language and get instant answers about appointments, customers, and repair orders with Tekmetric MCP.main@2026-04-21