Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 30 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ Fast MCP solves all these problems by providing a clean, Ruby-focused implementa
- 🚀 **Real-time Updates** - Subscribe to changes for interactive applications
- 🎯 **Dynamic Filtering** - Control tool/resource access based on request context (permissions, API versions, etc.)


## 💎 What Makes FastMCP Great

```ruby
# Define tools for AI models to use
server = FastMcp::Server.new(name: 'popular-users', version: '1.0.0')
Expand Down Expand Up @@ -109,7 +109,7 @@ Control which tools and resources are available based on request context:
class AdminTool < FastMcp::Tool
tags :admin, :dangerous
description "Perform admin operations"

def call
# Admin only functionality
end
Expand All @@ -118,7 +118,7 @@ end
# Filter tools based on user permissions
server.filter_tools do |request, tools|
user_role = request.params['role']

case user_role
when 'admin'
tools # Admins see all tools
Expand All @@ -131,6 +131,7 @@ end
```

### 🚂 Fast Ruby on Rails implementation

```shell
bundle add fast-mcp
bin/rails generate fast_mcp:install
Expand All @@ -155,20 +156,13 @@ FastMcp.mount_in_rails(
# allowed_ips: ['127.0.0.1', '::1']
# authenticate: true, # Uncomment to enable authentication
# auth_token: 'your-token' # Required if authenticate: true
) do |server|
Rails.application.config.after_initialize do
# FastMcp will automatically discover and register:
# - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
# - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
server.register_tools(*ApplicationTool.descendants)
server.register_resources(*ApplicationResource.descendants)
# alternatively, you can register tools and resources manually:
# server.register_tool(MyTool)
# server.register_resource(MyResource)
end
end
# tools_dir: Rails.root.join('app/tools'), # This is the default directory for tools
# resources_dir: Rails.root.join('app/resources'), # This is the default directory for resources
)
```

The install script will also:

- add app/resources folder
- add app/tools folder
- add app/tools/sample_tool.rb
Expand All @@ -187,7 +181,7 @@ These are automatically set up in Rails applications. You can use either naming

```ruby
# Using Rails-style naming:
class MyTool < ActionTool::Base
class MyTool < ApplicationTool
description "My awesome tool"

arguments do
Expand Down Expand Up @@ -219,7 +213,10 @@ class ApplicationResource < ActionResource::Base
end
```

Tools and resources are automatically registered in the server when they are inherited from ApplicationTool and ApplicationResource. Inheriting off of ActionTool::Base or FastMcp::Tool will not require you to register them manually.

### Easy Sinatra setup

I'll let you check out the dedicated [sinatra integration docs](./docs/sinatra_integration.md).

## 🚀 Quick Start
Expand Down Expand Up @@ -282,17 +279,21 @@ Clone this project, then give it a go !
```shell
npx @modelcontextprotocol/inspector examples/server_with_stdio_transport.rb
```

Or to test with an SSE transport using a rack middleware:

```shell
npx @modelcontextprotocol/inspector examples/rack_middleware.rb
```

Or to test over SSE with an authenticated rack middleware:

```shell
npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
```

You can test your custom implementation with the official MCP inspector by using:

```shell
# Test with a stdio transport:
npx @modelcontextprotocol/inspector path/to/your_ruby_file.rb
Expand Down Expand Up @@ -321,6 +322,7 @@ end
### Integrating with Claude Desktop

Add your server to your Claude Desktop configuration at:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

Expand All @@ -329,28 +331,27 @@ Add your server to your Claude Desktop configuration at:
"mcpServers": {
"my-great-server": {
"command": "ruby",
"args": [
"/Users/path/to/your/awesome/fast-mcp/server.rb"
]
"args": ["/Users/path/to/your/awesome/fast-mcp/server.rb"]
}
}
}
```

## How to add a MCP server to Claude, Cursor, or other MCP clients?

Please refer to [configuring_mcp_clients](docs/configuring_mcp_clients.md)

## 📊 Supported Specifications

| Feature | Status |
|---------|--------|
| ✅ **JSON-RPC 2.0** | Full implementation for communication |
| ✅ **Tool Definition & Calling** | Define and call tools with rich argument types |
| ✅ **Resource & Resource Templates Management** | Create, read, update, and subscribe to resources |
| ✅ **Transport Options** | STDIO, HTTP, and SSE for flexible integration |
| ✅ **Framework Integration** | Rails, Sinatra, Hanami, and any Rack-compatible framework |
| ✅ **Authentication** | Secure your AI endpoints with token authentication |
| ✅ **Schema Support** | Full JSON Schema for tool arguments with validation |
| Feature | Status |
| ----------------------------------------------- | --------------------------------------------------------- |
| ✅ **JSON-RPC 2.0** | Full implementation for communication |
| ✅ **Tool Definition & Calling** | Define and call tools with rich argument types |
| ✅ **Resource & Resource Templates Management** | Create, read, update, and subscribe to resources |
| ✅ **Transport Options** | STDIO, HTTP, and SSE for flexible integration |
| ✅ **Framework Integration** | Rails, Sinatra, Hanami, and any Rack-compatible framework |
| ✅ **Authentication** | Secure your AI endpoints with token authentication |
| ✅ **Schema Support** | Full JSON Schema for tool arguments with validation |

## 🗺️ Use Cases

Expand Down Expand Up @@ -406,6 +407,7 @@ FastMcp.authenticated_rack_middleware(app,
Check out the [examples directory](examples) for more detailed examples:

- **🔨 Basic Examples**:

- [Simple Server](examples/server_with_stdio_transport.rb)
- [Tool Examples](examples/tool_examples.rb)

Expand Down
18 changes: 14 additions & 4 deletions lib/fast_mcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
module FastMcp
class << self
attr_accessor :server

attr_accessor :tools_dir
attr_accessor :resources_dir
end
end

Expand Down Expand Up @@ -102,18 +105,20 @@ def self.register_tools(*tools)

# Register a resource with the MCP server
# @param resource [FastMcp::Resource] The resource to register
# @option options [Boolean] :notify Whether to notify subscribers about the list change
# @return [FastMcp::Resource] The registered resource
def self.register_resource(resource)
def self.register_resource(resource, notify: true)
self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
self.server.register_resource(resource)
self.server.register_resource(resource, notify: notify)
end

# Register multiple resources at once
# @param resources [Array<FastMcp::Resource>] The resources to register
# @option options [Boolean] :notify Whether to notify subscribers about the list change
# @return [Array<FastMcp::Resource>] The registered resources
def self.register_resources(*resources)
def self.register_resources(*resources, notify: true)
self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
self.server.register_resources(*resources)
self.server.register_resources(*resources, notify: notify)
end

# Mount the MCP middleware in a Rails application
Expand All @@ -127,6 +132,8 @@ def self.register_resources(*resources)
# @option options [Logger] :logger The logger to use
# @option options [Boolean] :authenticate Whether to use authentication
# @option options [String] :auth_token The authentication token
# @option options [String] :tools_dir The directory to load tools from
# @option options [String] :resources_dir The directory to load resources from
# @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
# @yield [server] A block to configure the server
# @yieldparam server [FastMcp::Server] The server to configure
Expand All @@ -148,6 +155,9 @@ def self.mount_in_rails(app, options = {})
options[:logger] = logger
options[:allowed_origins] = allowed_origins

self.tools_dir = options.delete(:tools_dir) || Rails.root.join('app/tools')
self.resources_dir = options.delete(:resources_dir) || Rails.root.join('app/resources')

# Create or get the server
self.server = FastMcp::Server.new(name: name, version: version, logger: logger)
yield self.server if block_given?
Expand Down
15 changes: 3 additions & 12 deletions lib/generators/fast_mcp/install/templates/fast_mcp_initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@
# allowed_ips: ['127.0.0.1', '::1']
# authenticate: true, # Uncomment to enable authentication
# auth_token: 'your-token', # Required if authenticate: true
) do |server|
Rails.application.config.after_initialize do
# FastMcp will automatically discover and register:
# - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
# - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
server.register_tools(*ApplicationTool.descendants)
server.register_resources(*ApplicationResource.descendants)
# alternatively, you can register tools and resources manually:
# server.register_tool(MyTool)
# server.register_resource(MyResource)
end
end
# tools_dir: Rails.root.join('app/tools'), # This is the default directory for tools
# resources_dir: Rails.root.join('app/resources'), # This is the default directory for resources
)
36 changes: 23 additions & 13 deletions lib/mcp/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
require 'fileutils'
require_relative '../mcp/server'

# Create ActionTool and ActionResource modules at load time
unless defined?(ActionTool)
# Create ActionTool and ActionResource modules when using Rails
if !defined?(ActionTool) && defined?(Rails)
module ::ActionTool
Base = FastMcp::Tool
end
end

unless defined?(ActionResource)
if !defined?(ActionResource) && defined?(Rails)
module ::ActionResource
Base = FastMcp::Resource
end
Expand All @@ -20,19 +20,29 @@ module ::ActionResource
module FastMcp
# Railtie for integrating Fast MCP with Rails applications
class Railtie < Rails::Railtie
# Add tools and resources directories to autoload paths
initializer 'fast_mcp.setup_autoload_paths' do |app|
app.config.autoload_paths += %W[
#{app.root}/app/tools
#{app.root}/app/resources
]

# Auto-register all tools and resources after the application is fully loaded
config.after_initialize do |app|
FastMcp::Railtie.eager_load_and_register_tools_and_resources
end

# Auto-register all tools and resources after the application is fully loaded
config.after_initialize do
# Load all files in app/tools and app/resources directories
Dir[Rails.root.join('app', 'tools', '**', '*.rb')].each { |f| require f }
Dir[Rails.root.join('app', 'resources', '**', '*.rb')].each { |f| require f }
config.to_prepare do
FastMcp.server.clear_tools!
FastMcp.server.clear_resources!

FastMcp::Railtie.eager_load_and_register_tools_and_resources
end

def self.eager_load_and_register_tools_and_resources
Rails.autoloaders.main.eager_load_dir(FastMcp.tools_dir)
Rails.autoloaders.main.eager_load_dir(FastMcp.resources_dir)

tools = ApplicationTool.descendants.sort_by(&:name)
resources = ApplicationResource.descendants.sort_by(&:name)

FastMcp.server.register_tools(*tools)
FastMcp.server.register_resources(*resources, notify: false)
end

# Add rake tasks
Expand Down
21 changes: 17 additions & 4 deletions lib/mcp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,30 @@ def register_tool(tool)
tool.server = self
end

# Clear all tools from the server
def clear_tools!
@tools = {}
end

# Register multiple resources at once
# @param resources [Array<Resource>] Resources to register
def register_resources(*resources)
# @option options [Boolean] :notify Whether to notify subscribers about the list change
def register_resources(*resources, notify: true)
resources.each do |resource|
register_resource(resource)
register_resource(resource, notify: notify)
end
end

# Register a resource with the server
def register_resource(resource)
# @param resource [Resource] Resource to register
# @option options [Boolean] :notify Whether to notify subscribers about the list change
def register_resource(resource, notify: true)
@resources << resource

@logger.debug("Registered resource: #{resource.resource_name} (#{resource.uri})")
resource.server = self
# Notify subscribers about the list change
notify_resource_list_changed if @transport
notify_resource_list_changed if @transport && notify

resource
end
Expand All @@ -97,6 +105,11 @@ def remove_resource(uri)
end
end

# Clear all resources from the server
def clear_resources!
@resources = []
end

# Start the server using stdio transport
def start
@logger.transport = :stdio
Expand Down