fast-mcp 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94b9b8af1c648e0637ad0dc3d0118c8354392bac61b0f8c30d4eb264bc7a8fda
4
- data.tar.gz: 728c09e7b7848fcc1f9296e42fed083933b8c8bb9cf08d02c8708009a378f0c1
3
+ metadata.gz: dde8305abcf47ffc314fc95df26467f737957e1b97eecae8c2c4a88b34c76017
4
+ data.tar.gz: 22715faf289cd3f45090480f59585220c3bb904b2a15c9669c5b93700a85e104
5
5
  SHA512:
6
- metadata.gz: 1e26886cc6c006df64e913b7d517fbcffd1c2c4e6a27787e8fccbb3e0d5c172ec1e337ada9928cff1821fca96e2fcd173e4bf7bc4f492fc9eb4404463ed3e0a8
7
- data.tar.gz: c91ea6f439f68ae5f301f4cd5adb24aaebc1939cfe8f2458ec038310b294293b2eaf6b5f8518972fdef5f1bf6acdbda59ec14997d1a01cf46171d4605152cf93
6
+ metadata.gz: eacade9a05c17a5032bbe2ed13e68bf094e7d018f272163a70f2457971c60ed79672488ab177fe3926dc9aa51cbf738ed2e3ba70bd9ab4c309599e593659ab97
7
+ data.tar.gz: 390cb3617e29219da28df5c49ff7da06c36b09d341068b5bc4c20883b64dd61f33c67e0542e03f368bf686129f1338ea5af3157bac28439c824520095250adfd
data/CHANGELOG.md CHANGED
@@ -5,13 +5,57 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2025-04-13
9
+ ### Added
10
+ - Security enhancement: Added DNS rebinding protection by validating Origin headers [#32 @yjacquin](https://github.com/yjacquin/fast-mcp/pull/32/files)
11
+ - Added configuration options for allowed origins in rack middleware [#32 @yjacquin](https://github.com/yjacquin/fast-mcp/pull/32/files)
12
+ - Allow to change the SSE and Messages route [#23 @pedrofurtado](https://github.com/yjacquin/fast-mcp/pull/23)
13
+ - Fix invalid return value when processing notifications/initialized request [#31 @abMatGit](https://github.com/yjacquin/fast-mcp/pull/31)
14
+
15
+
16
+ ## [1.0.0] - 2025-03-30
17
+
18
+ ### Added
19
+ - Rails integration improvements via enhanced Railtie support
20
+ - Automatic tool and resource registration in Rails applications
21
+ - Extended Rails autoload paths for tools and resources directories
22
+ - Sample generator templates for resources and tools
23
+ - MCP Client configuration documentation as reported by [#8 @sivag-csod](https://github.com/yjacquin/fast-mcp/issues/8)
24
+ - Example Ruby on Rails app in the documentation
25
+ - `FastMcp.server` now exposes the MCP server to apps that may need it to access resources
26
+ - Automated Github Releases through Github Workflow
27
+
28
+ ### Fixed
29
+ - Fixed bug with Rack middlewares not being initialized properly.
30
+ - Fixed bug with STDIO logging preventing a proper connection with clients [# 11 @cs3b](https://github.com/yjacquin/fast-mcp/issues/11)
31
+ - Fixed Rails SSE streaming detection and handling
32
+ - Improved error handling in client reconnection scenarios
33
+ - Namespace consistency correction (FastMCP -> FastMcp) throughout the codebase
34
+
35
+ ### Improved
36
+ - ⚠️ [Breaking] Resource content declaration changes
37
+ - Now resources implement `content` over `default_content`
38
+ - `content` is dynamically called when calling a resource, this implies we can declare dynamic resource contents like:
39
+ ```ruby
40
+ class HighestScoringUsersResource < FastMcp::Resource
41
+ ...
42
+ def content
43
+ User.order(score: :desc).last(5).map(&:as_json)
44
+ end
45
+ end
46
+ ```
47
+ - More robust SSE connection lifecycle management
48
+ - Optimized test suite with faster execution times
49
+ - Better logging for debugging connection issues
50
+ - Documentation had outdated examples
51
+
8
52
  ## [0.1.0] - 2025-03-12
9
53
 
10
54
  ### Added
11
55
 
12
56
  - Initial release of the Fast MCP library
13
- - MCP::Tool class with multiple definition styles
14
- - MCP::Server class with STDIO transport and HTTP / SSE transport
57
+ - FastMcp::Tool class with multiple definition styles
58
+ - FastMcp::Server class with STDIO transport and HTTP / SSE transport
15
59
  - Rack Integration with authenticated and standard middleware options
16
60
  - Resource management with subscription capabilities
17
61
  - Binary resource support
data/README.md CHANGED
@@ -28,140 +28,169 @@ Fast MCP solves all these problems by providing a clean, Ruby-focused implementa
28
28
  - 🛠️ **Tools API** - Let AI models call your Ruby functions securely, with in-depth argument validation through [Dry-Schema](https://github.com/dry-rb/dry-schema).
29
29
  - 📚 **Resources API** - Share data between your app and AI models
30
30
  - 🔄 **Multiple Transports** - Choose from STDIO, HTTP, or SSE based on your needs
31
- - 🧩 **Framework Integration** - Works seamlessly with Rails, Sinatra, and Hanami
32
- - 🔒 **Authentication Support** - Secure your AI endpoints with ease
31
+ - 🧩 **Framework Integration** - Works seamlessly with Rails, Sinatra or any Rack app.
32
+ - 🔒 **Authentication Support** - Secure your AI-powered endpoints with ease
33
33
  - 🚀 **Real-time Updates** - Subscribe to changes for interactive applications
34
34
 
35
- ## 💎 What Makes FastMCP Great
36
35
 
36
+ ## 💎 What Makes FastMCP Great
37
37
  ```ruby
38
38
  # Define tools for AI models to use
39
- server = MCP::Server.new(name: 'recipe-ai', version: '1.0.0')
39
+ server = FastMcp::Server.new(name: 'popular-users', version: '1.0.0')
40
40
 
41
- # Define a tool by inheriting from MCP::Tool
42
- class GetRecipesTool < MCP::Tool
43
- description "Find recipes based on ingredients"
44
-
45
- arguments do
41
+ # Define a tool by inheriting from FastMcp::Tool
42
+ class CreateUserTool < FastMcp::Tool
43
+ description "Create a user"
46
44
  # These arguments will generate the needed JSON to be presented to the MCP Client
47
- # And they will be vaidated at run time.
45
+ # And they will be validated at run time.
48
46
  # The validation is based off Dry-Schema, with the addition of the description.
49
- required(:ingredients).array(:string).description("List of ingredients")
50
- optional(:cuisine).filled(:string).description("Type of cuisine")
47
+ arguments do
48
+ required(:first_name).filled(:string).description("First name of the user")
49
+ optional(:age).filled(:integer).description("Age of the user")
50
+ required(:address).hash do
51
+ optional(:street).filled(:string)
52
+ optional(:city).filled(:string)
53
+ optional(:zipcode).filled(:string)
54
+ end
51
55
  end
52
-
53
- def call(ingredients:, cuisine: nil)
54
- Recipe.find_by_ingredients(ingredients, cuisine: cuisine)
56
+
57
+ def call(first_name:, age: nil, address: {})
58
+ User.create!(first_name:, age:, address:)
55
59
  end
56
60
  end
57
61
 
58
62
  # Register the tool with the server
59
- server.register_tool(GetRecipesTool)
63
+ server.register_tool(CreateUserTool)
60
64
 
61
- # Share data resources with AI models by inheriting from MCP::Resource
62
- class IngredientsResource < MCP::Resource
63
- uri "food/popular_ingredients"
64
- name "Popular Ingredients"
65
+ # Share data resources with AI models by inheriting from FastMcp::Resource
66
+ class PopularUsers < FastMcp::Resource
67
+ uri "file://popular_users.json"
68
+ resource_name "Popular Users"
65
69
  mime_type "application/json"
66
-
67
- def default_content
68
- JSON.generate(Ingredient.popular.as_json)
70
+
71
+ def content
72
+ JSON.generate(User.popular.limit(5).as_json)
69
73
  end
70
74
  end
71
75
 
72
76
  # Register the resource with the server
73
- server.register_resource(IngredientsResource)
77
+ server.register_resource(PopularUsers)
74
78
 
75
79
  # Accessing the resource through the server
76
- server.read_resource("food/popular_ingredients")
80
+ server.read_resource(PopularUsers.uri)
77
81
 
78
- # Updating the resource content through the server
79
- server.update_resource("food/popular_ingredients", JSON.generate({id: 1, name: 'tomato'}))
82
+ # Notify the resource content has been updated to clients
83
+ server.notify_resource_updated(PopularUsers.uri)
84
+ ```
80
85
 
86
+ ### 🚂 Fast Ruby on Rails implementation
87
+ ```shell
88
+ bundle add fast-mcp
89
+ bin/rails generate fast_mcp:install
90
+ ```
81
91
 
82
- # Easily integrate with web frameworks
83
- # config/application.rb (Rails)
84
- config.middleware.use MCP::RackMiddleware.new(
85
- name: 'recipe-ai',
86
- version: '1.0.0'
87
- ) do |server|
88
- # Register tools and resources here
89
- server.register_tool(GetRecipesTool)
90
- end
92
+ This will add a configurable `fast_mcp.rb` initializer
91
93
 
92
- # Secure your AI endpoints
93
- config.middleware.use MCP::AuthenticatedRackMiddleware.new(
94
- name: 'recipe-ai',
95
- version: '1.0.0',
96
- token: ENV['MCP_AUTH_TOKEN']
97
- )
94
+ ```ruby
95
+ require 'fast_mcp'
98
96
 
99
- # Build real-time applications
100
- server.on_resource_update do |resource|
101
- ActionCable.server.broadcast("recipe_updates", resource.metadata)
97
+ FastMcp.mount_in_rails(
98
+ Rails.application,
99
+ name: Rails.application.class.module_parent_name.underscore.dasherize,
100
+ version: '1.0.0',
101
+ path_prefix: '/mcp', # This is the default path prefix
102
+ messages_route: 'messages', # This is the default route for the messages endpoint
103
+ sse_route: 'sse', # This is the default route for the SSE endpoint
104
+ # Add allowed origins below, it defaults to Rails.application.config.hosts
105
+ # allowed_origins: ['localhost', '127.0.0.1', 'example.com', /.*\.example\.com/],
106
+ # authenticate: true, # Uncomment to enable authentication
107
+ # auth_token: 'your-token' # Required if authenticate: true
108
+ ) do |server|
109
+ Rails.application.config.after_initialize do
110
+ # FastMcp will automatically discover and register:
111
+ # - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
112
+ # - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
113
+ server.register_tools(*ApplicationTool.descendants)
114
+ server.register_resources(*ApplicationResource.descendants)
115
+ # alternatively, you can register tools and resources manually:
116
+ # server.register_tool(MyTool)
117
+ # server.register_resource(MyResource)
118
+ end
102
119
  end
103
120
  ```
121
+ The install script will also:
122
+ - add app/resources folder
123
+ - add app/tools folder
124
+ - add app/tools/sample_tool.rb
125
+ - add app/resources/sample_resource.rb
126
+ - add ApplicationTool to inherit from
127
+ - add ApplicationResource to inherit from as well
104
128
 
105
- ## 📦 Installation
129
+ #### Rails-friendly class naming conventions
106
130
 
107
- ```ruby
108
- # In your Gemfile
109
- gem 'fast-mcp'
131
+ For Rails applications, FastMCP provides Rails-style class names to better fit with Rails conventions:
110
132
 
111
- # Then run
112
- bundle install
133
+ - `ActionTool::Base` - An alias for `FastMcp::Tool`
134
+ - `ActionResource::Base` - An alias for `FastMcp::Resource`
113
135
 
114
- # Or install it yourself
115
- gem install fast-mcp
116
- ```
136
+ These are automatically set up in Rails applications. You can use either naming convention in your code:
117
137
 
118
- ## 🚀 Quick Start
138
+ ```ruby
139
+ # Using Rails-style naming:
140
+ class MyTool < ActionTool::Base
141
+ description "My awesome tool"
119
142
 
120
- ### Testing with the inspector
143
+ arguments do
144
+ required(:input).filled(:string)
145
+ end
121
146
 
122
- MCP has developed a very [useful inspector](https://github.com/modelcontextprotocol/inspector).
123
- You can use it to validate your implementation. I suggest you use the examples I provided with this project as an easy boilerplate.
124
- Clone this project, then give it a go !
147
+ def call(input:)
148
+ # Your implementation
149
+ end
150
+ end
125
151
 
126
- ```shell
127
- npx @modelcontextprotocol/inspector examples/server_with_stdio_transport.rb
128
- ```
129
- Or to test with an SSE transport using a rack middleware:
130
- ```shell
131
- npx @modelcontextprotocol/inspector examples/rack_middleware.rb
152
+ # Using standard FastMcp naming:
153
+ class AnotherTool < FastMcp::Tool
154
+ # Both styles work interchangeably in Rails apps
155
+ end
132
156
  ```
133
157
 
134
- Or to test over SSE with an authenticated rack middleware:
135
- ```shell
136
- npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
137
- ```
158
+ When creating new tools or resources, the generators will use the Rails naming convention by default:
138
159
 
139
- You can test your custom implementation with the official MCP inspector by using:
140
- ```shell
141
- # Test with a stdio transport:
142
- npx @modelcontextprotocol/inspector path/to/your_ruby_file.rb
160
+ ```ruby
161
+ # app/tools/application_tool.rb
162
+ class ApplicationTool < ActionTool::Base
163
+ # Base methods for all tools
164
+ end
143
165
 
144
- # Test with an HTTP / SSE server. In the UI select SSE and input your address.
145
- npx @modelcontextprotocol/inspector
166
+ # app/resources/application_resource.rb
167
+ class ApplicationResource < ActionResource::Base
168
+ # Base methods for all resources
169
+ end
146
170
  ```
147
171
 
148
- ### Create a Server with Tools and Resources
172
+ ### Easy Sinatra setup
173
+ I'll let you check out the dedicated [sinatra integration docs](./docs/sinatra_integration.md).
174
+
175
+ ## 🚀 Quick Start
176
+
177
+ ### Create a Server with Tools and Resources and STDIO transport
149
178
 
150
179
  ```ruby
151
180
  require 'fast_mcp'
152
181
 
153
182
  # Create an MCP server
154
- server = MCP::Server.new(name: 'my-ai-server', version: '1.0.0')
183
+ server = FastMcp::Server.new(name: 'my-ai-server', version: '1.0.0')
155
184
 
156
- # Define a tool by inheriting from MCP::Tool
157
- class SummarizeTool < MCP::Tool
185
+ # Define a tool by inheriting from FastMcp::Tool
186
+ class SummarizeTool < FastMcp::Tool
158
187
  description "Summarize a given text"
159
-
188
+
160
189
  arguments do
161
190
  required(:text).filled(:string).description("Text to summarize")
162
191
  optional(:max_length).filled(:integer).description("Maximum length of summary")
163
192
  end
164
-
193
+
165
194
  def call(text:, max_length: 100)
166
195
  # Your summarization logic here
167
196
  text.split('.').first(3).join('.') + '...'
@@ -171,14 +200,14 @@ end
171
200
  # Register the tool with the server
172
201
  server.register_tool(SummarizeTool)
173
202
 
174
- # Create a resource by inheriting from MCP::Resource
175
- class StatisticsResource < MCP::Resource
203
+ # Create a resource by inheriting from FastMcp::Resource
204
+ class StatisticsResource < FastMcp::Resource
176
205
  uri "data/statistics"
177
- name "Usage Statistics"
206
+ resource_name "Usage Statistics"
178
207
  description "Current system statistics"
179
208
  mime_type "application/json"
180
-
181
- def default_content
209
+
210
+ def content
182
211
  JSON.generate({
183
212
  users_online: 120,
184
213
  queries_per_minute: 250,
@@ -188,30 +217,38 @@ class StatisticsResource < MCP::Resource
188
217
  end
189
218
 
190
219
  # Register the resource with the server
191
- server.register_resource(StatisticsResource.new)
220
+ server.register_resource(StatisticsResource)
192
221
 
193
222
  # Start the server
194
223
  server.start
195
224
  ```
196
225
 
197
- ### Integrate with Web Frameworks
226
+ ## 🧪 Testing with the inspector
198
227
 
199
- #### Rails
228
+ MCP has developed a very [useful inspector](https://github.com/modelcontextprotocol/inspector).
229
+ You can use it to validate your implementation. I suggest you use the examples I provided with this project as an easy boilerplate.
230
+ Clone this project, then give it a go !
200
231
 
201
- ```ruby
202
- # config/application.rb
203
- module YourApp
204
- class Application < Rails::Application
205
- # ...
206
- config.middleware.use MCP::RackMiddleware.new(
207
- name: 'my-ai-server',
208
- version: '1.0.0'
209
- ) do |server|
210
- # Register tools and resources here
211
- server.register_tool(SummarizeTool)
212
- end
213
- end
214
- end
232
+ ```shell
233
+ npx @modelcontextprotocol/inspector examples/server_with_stdio_transport.rb
234
+ ```
235
+ Or to test with an SSE transport using a rack middleware:
236
+ ```shell
237
+ npx @modelcontextprotocol/inspector examples/rack_middleware.rb
238
+ ```
239
+
240
+ Or to test over SSE with an authenticated rack middleware:
241
+ ```shell
242
+ npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
243
+ ```
244
+
245
+ You can test your custom implementation with the official MCP inspector by using:
246
+ ```shell
247
+ # Test with a stdio transport:
248
+ npx @modelcontextprotocol/inspector path/to/your_ruby_file.rb
249
+
250
+ # Test with an HTTP / SSE server. In the UI select SSE and input your address.
251
+ npx @modelcontextprotocol/inspector
215
252
  ```
216
253
 
217
254
  #### Sinatra
@@ -221,7 +258,7 @@ end
221
258
  require 'sinatra'
222
259
  require 'fast_mcp'
223
260
 
224
- use MCP::RackMiddleware.new(name: 'my-ai-server', version: '1.0.0') do |server|
261
+ use FastMcp::RackMiddleware.new(name: 'my-ai-server', version: '1.0.0') do |server|
225
262
  # Register tools and resources here
226
263
  server.register_tool(SummarizeTool)
227
264
  end
@@ -250,6 +287,9 @@ Add your server to your Claude Desktop configuration at:
250
287
  }
251
288
  ```
252
289
 
290
+ ## How to add a MCP server to Claude, Cursor, or other MCP clients?
291
+ Please refer to [configuring_mcp_clients](docs/configuring_mcp_clients.md)
292
+
253
293
  ## 📊 Supported Specifications
254
294
 
255
295
  | Feature | Status |
@@ -270,17 +310,43 @@ Add your server to your Claude Desktop configuration at:
270
310
  - 📚 **Interactive Documentation**: Create AI-enhanced API documentation
271
311
  - 💬 **Chatbots and Assistants**: Build AI assistants with access to your app's data
272
312
 
313
+ ## 🔒 Security Features
314
+
315
+ Fast MCP includes built-in security features to protect your applications:
316
+
317
+ ### DNS Rebinding Protection
318
+
319
+ The HTTP/SSE transport validates the Origin header on all incoming connections to prevent DNS rebinding attacks, which could allow malicious websites to interact with local MCP servers.
320
+
321
+ ```ruby
322
+ # Configure allowed origins (defaults to ['localhost', '127.0.0.1'])
323
+ FastMcp.rack_middleware(app,
324
+ allowed_origins: ['localhost', '127.0.0.1', 'your-domain.com', /.*\.your-domain\.com/],
325
+ # other options...
326
+ )
327
+ ```
328
+
329
+ ### Authentication
330
+
331
+ Fast MCP supports token-based authentication for all connections:
332
+
333
+ ```ruby
334
+ # Enable authentication
335
+ FastMcp.authenticated_rack_middleware(app,
336
+ auth_token: 'your-secret-token',
337
+ # other options...
338
+ )
339
+ ```
340
+
273
341
  ## 📖 Documentation
274
342
 
275
343
  - [🚀 Getting Started Guide](docs/getting_started.md)
276
344
  - [🧩 Integration Guide](docs/integration_guide.md)
277
345
  - [🛤️ Rails Integration](docs/rails_integration.md)
278
346
  - [🌐 Sinatra Integration](docs/sinatra_integration.md)
279
- - [🌸 Hanami Integration](docs/hanami_integration.md)
280
347
  - [📚 Resources](docs/resources.md)
281
348
  - [🛠️ Tools](docs/tools.md)
282
- - [🔌 Transports](docs/transports.md)
283
- - [📘 API Reference](docs/api_reference.md)
349
+ - [🔒 Security](docs/security.md)
284
350
 
285
351
  ## 💻 Examples
286
352
 
data/lib/fast_mcp.rb CHANGED
@@ -4,13 +4,20 @@
4
4
  # https://modelcontextprotocol.io/introduction
5
5
 
6
6
  # Define the MCP module
7
- module MCP
7
+ module FastMcp
8
+ class << self
9
+ attr_accessor :server
10
+ end
8
11
  end
9
12
 
10
13
  # Require the core components
11
14
  require_relative 'mcp/tool'
12
15
  require_relative 'mcp/server'
13
16
  require_relative 'mcp/resource'
17
+ require_relative 'mcp/railtie' if defined?(Rails::Railtie)
18
+
19
+ # Load generators if Rails is available
20
+ require_relative 'generators/fast_mcp/install/install_generator' if defined?(Rails::Generators)
14
21
 
15
22
  # Require all transport files
16
23
  require_relative 'mcp/transports/base_transport'
@@ -22,28 +29,34 @@ end
22
29
  require_relative 'mcp/version'
23
30
 
24
31
  # Convenience method to create a Rack middleware
25
- module MCP
32
+ module FastMcp
26
33
  # Create a Rack middleware for the MCP server
27
34
  # @param app [#call] The Rack application
28
35
  # @param options [Hash] Options for the middleware
29
36
  # @option options [String] :name The name of the server
30
37
  # @option options [String] :version The version of the server
31
38
  # @option options [String] :path_prefix The path prefix for the MCP endpoints
39
+ # @option options [String] :messages_route The route for the messages endpoint
40
+ # @option options [String] :sse_route The route for the SSE endpoint
32
41
  # @option options [Logger] :logger The logger to use
42
+ # @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
33
43
  # @yield [server] A block to configure the server
34
- # @yieldparam server [MCP::Server] The server to configure
44
+ # @yieldparam server [FastMcp::Server] The server to configure
35
45
  # @return [#call] The Rack middleware
36
46
  def self.rack_middleware(app, options = {})
37
47
  name = options.delete(:name) || 'mcp-server'
38
48
  version = options.delete(:version) || '1.0.0'
39
49
  logger = options.delete(:logger) || Logger.new
40
50
 
41
- server = MCP::Server.new(name: name, version: version, logger: logger)
51
+ server = FastMcp::Server.new(name: name, version: version, logger: logger)
42
52
  yield server if block_given?
43
53
 
44
54
  # Store the server in the Sinatra settings if available
45
55
  app.settings.set(:mcp_server, server) if app.respond_to?(:settings) && app.settings.respond_to?(:mcp_server=)
46
56
 
57
+ # Store the server in the FastMcp module
58
+ self.server = server
59
+
47
60
  server.start_rack(app, options)
48
61
  end
49
62
 
@@ -53,17 +66,121 @@ module MCP
53
66
  # @option options [String] :name The name of the server
54
67
  # @option options [String] :version The version of the server
55
68
  # @option options [String] :auth_token The authentication token
69
+ # @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
56
70
  # @yield [server] A block to configure the server
57
- # @yieldparam server [MCP::Server] The server to configure
71
+ # @yieldparam server [FastMcp::Server] The server to configure
58
72
  # @return [#call] The Rack middleware
59
73
  def self.authenticated_rack_middleware(app, options = {})
60
74
  name = options.delete(:name) || 'mcp-server'
61
75
  version = options.delete(:version) || '1.0.0'
62
76
  logger = options.delete(:logger) || Logger.new
63
77
 
64
- server = MCP::Server.new(name: name, version: version, logger: logger)
78
+ server = FastMcp::Server.new(name: name, version: version, logger: logger)
65
79
  yield server if block_given?
66
80
 
81
+ # Store the server in the FastMcp module
82
+ self.server = server
83
+
67
84
  server.start_authenticated_rack(app, options)
68
85
  end
86
+
87
+ # Register a tool with the MCP server
88
+ # @param tool [FastMcp::Tool] The tool to register
89
+ # @return [FastMcp::Tool] The registered tool
90
+ def self.register_tool(tool)
91
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
92
+ self.server.register_tool(tool)
93
+ end
94
+
95
+ # Register multiple tools at once
96
+ # @param tools [Array<FastMcp::Tool>] The tools to register
97
+ # @return [Array<FastMcp::Tool>] The registered tools
98
+ def self.register_tools(*tools)
99
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
100
+ self.server.register_tools(*tools)
101
+ end
102
+
103
+ # Register a resource with the MCP server
104
+ # @param resource [FastMcp::Resource] The resource to register
105
+ # @return [FastMcp::Resource] The registered resource
106
+ def self.register_resource(resource)
107
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
108
+ self.server.register_resource(resource)
109
+ end
110
+
111
+ # Register multiple resources at once
112
+ # @param resources [Array<FastMcp::Resource>] The resources to register
113
+ # @return [Array<FastMcp::Resource>] The registered resources
114
+ def self.register_resources(*resources)
115
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
116
+ self.server.register_resources(*resources)
117
+ end
118
+
119
+ # Mount the MCP middleware in a Rails application
120
+ # @param app [Rails::Application] The Rails application
121
+ # @param options [Hash] Options for the middleware
122
+ # @option options [String] :name The name of the server
123
+ # @option options [String] :version The version of the server
124
+ # @option options [String] :path_prefix The path prefix for the MCP endpoints
125
+ # @option options [String] :messages_route The route for the messages endpoint
126
+ # @option options [String] :sse_route The route for the SSE endpoint
127
+ # @option options [Logger] :logger The logger to use
128
+ # @option options [Boolean] :authenticate Whether to use authentication
129
+ # @option options [String] :auth_token The authentication token
130
+ # @option options [Array<String,Regexp>] :allowed_origins List of allowed origins for DNS rebinding protection
131
+ # @yield [server] A block to configure the server
132
+ # @yieldparam server [FastMcp::Server] The server to configure
133
+ # @return [#call] The Rack middleware
134
+ def self.mount_in_rails(app, options = {})
135
+ # Default options
136
+ name = options.delete(:name) || app.class.module_parent_name.underscore.dasherize
137
+ version = options.delete(:version) || '1.0.0'
138
+ logger = options[:logger] || Rails.logger
139
+ path_prefix = options.delete(:path_prefix) || '/mcp'
140
+ messages_route = options.delete(:messages_route) || 'messages'
141
+ sse_route = options.delete(:sse_route) || 'sse'
142
+ authenticate = options.delete(:authenticate) || false
143
+ allowed_origins = options[:allowed_origins] || default_rails_allowed_origins(app)
144
+
145
+ options[:logger] = logger
146
+ options[:allowed_origins] = allowed_origins
147
+
148
+ # Create or get the server
149
+ self.server = FastMcp::Server.new(name: name, version: version, logger: logger)
150
+ yield self.server if block_given?
151
+
152
+ # Choose the right middleware based on authentication
153
+ self.server.transport_klass = if authenticate
154
+ FastMcp::Transports::AuthenticatedRackTransport
155
+ else
156
+ FastMcp::Transports::RackTransport
157
+ end
158
+
159
+ # Insert the middleware in the Rails middleware stack
160
+ app.middleware.use(
161
+ self.server.transport_klass,
162
+ self.server,
163
+ options.merge(path_prefix: path_prefix, messages_route: messages_route, sse_route: sse_route)
164
+ )
165
+ end
166
+
167
+ def self.default_rails_allowed_origins(rail_app)
168
+ hosts = rail_app.config.hosts
169
+
170
+ hosts.map do |host|
171
+ if host.is_a?(String) && host.start_with?('.')
172
+ # Convert .domain to domain and *.domain
173
+ host_without_dot = host[1..]
174
+ [host_without_dot, Regexp.new(".*\.#{host_without_dot}")] # rubocop:disable Style/RedundantStringEscape
175
+ else
176
+ host
177
+ end
178
+ end.flatten.compact
179
+ end
180
+
181
+ # Notify the server that a resource has been updated
182
+ # @param uri [String] The URI of the resource
183
+ def self.notify_resource_updated(uri)
184
+ self.server.notify_resource_updated(uri)
185
+ end
69
186
  end