Status: Experimental / WIP
Minimal MCP (Model Context Protocol) server for Rails apps with ActiveAdmin.
- Claude Code (Anthropic) - HTTP transport
Add to your Gemfile:
gem "active_admin_mcp"Then run:
bundle install
rails generate active_admin_mcp:installThe MCP server is automatically mounted at /mcp.
By default, the engine prepends its route to the top of your application's route table. This works well for most setups, but can cause issues when your routes use constraints (e.g. hostname-based routing for admin servers), as the prepended mount sits outside any constraint blocks.
The mount_strategy option controls how the engine registers its route:
| Strategy | Behaviour |
|---|---|
:prepend |
(default) Mounts at the top of the route table via routes.prepend |
:append |
Mounts at the bottom of the route table via routes.append |
:none |
Skips automatic mounting — you mount the engine yourself |
If your admin routes are wrapped in constraints, set mount_strategy to :none and mount the engine inside your route file:
# config/initializers/active_admin_mcp.rb
ActiveAdminMcp.configure do |config|
config.mount_path = "/admin/mcp"
config.mount_strategy = :none
end# config/routes.rb (or a drawn route file)
constraints AdminConstraint.new do
ActiveAdmin.routes(self)
mount ActiveAdminMcp::Engine => ActiveAdminMcp.config.mount_path
endTo protect your MCP endpoint with API token authentication:
rails generate active_admin_mcp:install --auth devise_token
rails db:migrateThis will:
- Create the
mcp_api_tokenstable - Add an "MCP Tokens" page to your ActiveAdmin panel (
app/admin/by default) - Enable token authentication in the initializer
| Option | Default | Description |
|---|---|---|
--auth |
none | Authentication method to use (e.g., devise_token) |
--admin-path |
app/admin |
Directory for the ActiveAdmin page file |
Example with a custom admin path:
rails generate active_admin_mcp:install --auth devise_token --admin-path app/admin/mcp- Log in to your ActiveAdmin panel (
/admin) - Navigate to Settings > MCP Tokens
- Create a new token and copy it — it will only be shown once
claude mcp add --transport http \
--header 'Authorization: Bearer YOUR_TOKEN' \
my-app http://localhost:3000/mcp/Or in .mcp.json:
{
"mcpServers": {
"my-app": {
"type": "http",
"url": "http://localhost:3000/mcp/",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}The initializer at config/initializers/active_admin_mcp.rb:
ActiveAdminMcp.configure do |config|
config.authentication_method = :devise_token
config.user_class = "User" # your Devise model class
end| Option | Default | Description |
|---|---|---|
authentication_method |
nil |
Set to :devise_token to enable Bearer token auth |
user_class |
"User" |
The Devise model class name |
current_user_method |
:current_admin_user |
Controller method that returns the current user |
menu_parent |
nil |
Parent menu for the MCP Tokens page (e.g., "Settings") |
mount_path |
"/mcp" |
Path where the MCP server is mounted |
mount_strategy |
:prepend |
Route mounting strategy: :prepend, :append, or :none |
auth_header_name |
"Authorization" |
HTTP header to read the Bearer token from |
If your application sits behind a reverse proxy that strips the standard Authorization header (e.g. AWS Verified Access), you can configure a custom header name:
ActiveAdminMcp.configure do |config|
config.authentication_method = :devise_token
config.auth_header_name = "X-MCP-Authorization"
endThen pass the token via the custom header in .mcp.json:
{
"mcpServers": {
"my-app": {
"type": "http",
"url": "https://admin.example.com/admin/mcp/",
"headers": {
"X-MCP-Authorization": "Bearer YOUR_TOKEN"
}
}
}
}claude mcp add --transport http my-app http://localhost:3000/mcp/Or add to your .mcp.json:
{
"mcpServers": {
"my-app": {
"type": "http",
"url": "http://localhost:3000/mcp/"
}
}
}| Tool | Description |
|---|---|
list_resources |
List all ActiveAdmin resources with their attributes |
query |
Query a resource using Ransack syntax |
Query users where email contains "example.com"
→ query(resource: "User", q: { email_cont: "example.com" })
Find active posts from last week
→ query(resource: "Post", q: { status_eq: "active", created_at_gt: "2025-12-01" })
- SSE transport support for streaming
- Configurable resource allowlist
- Write operations (create, update, delete)
- Custom tool definitions per resource
- Rate limiting
MIT