fast-mcp 0.1.0 → 1.0.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: bedabe91d57ecb3800b625ad786389c7e842b73e5bcd9df621f8f31b401fe93f
4
+ data.tar.gz: b8711a78f8f8928e5f66d94a1f271f9f6e6a5e101e0386eff81ed62e7153d2ae
5
5
  SHA512:
6
- metadata.gz: 1e26886cc6c006df64e913b7d517fbcffd1c2c4e6a27787e8fccbb3e0d5c172ec1e337ada9928cff1821fca96e2fcd173e4bf7bc4f492fc9eb4404463ed3e0a8
7
- data.tar.gz: c91ea6f439f68ae5f301f4cd5adb24aaebc1939cfe8f2458ec038310b294293b2eaf6b5f8518972fdef5f1bf6acdbda59ec14997d1a01cf46171d4605152cf93
6
+ metadata.gz: 564cadd64c076e784bc0779a3f697d82d50eed14c77b1403f03abeee0935972103e68bf82bb94ec7cd3443b90ddfa75d739dd6a5a0bda32590e8b31d8a708869
7
+ data.tar.gz: 731cc93d551842dfcec399d7d75d86c59d4a6af88d415925757a3ced8c5947dda7ae26eafc12b71423af496c6669a084cdbaabdc05cbbacef68476d39925991e
data/CHANGELOG.md CHANGED
@@ -5,13 +5,49 @@ 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.0.0] - 2025-03-30
9
+
10
+ ### Added
11
+ - Rails integration improvements via enhanced Railtie support
12
+ - Automatic tool and resource registration in Rails applications
13
+ - Extended Rails autoload paths for tools and resources directories
14
+ - Sample generator templates for resources and tools
15
+ - MCP Client configuration documentation as reported by [#8 @sivag-csod](https://github.com/yjacquin/fast-mcp/issues/8)
16
+ - Example Ruby on Rails app in the documentation
17
+ - `FastMcp.server` now exposes the MCP server to apps that may need it to access resources
18
+ - Automated Github Releases through Github Workflow
19
+
20
+ ### Fixed
21
+ - Fixed bug with Rack middlewares not being initialized properly.
22
+ - Fixed bug with STDIO logging preventing a proper connection with clients [# 11 @cs3b](https://github.com/yjacquin/fast-mcp/issues/11)
23
+ - Fixed Rails SSE streaming detection and handling
24
+ - Improved error handling in client reconnection scenarios
25
+ - Namespace consistency correction (FastMCP -> FastMcp) throughout the codebase
26
+
27
+ ### Improved
28
+ - ⚠️ [Breaking] Resource content declaration changes
29
+ - Now resources implement `content` over `default_content`
30
+ - `content` is dynamically called when calling a resource, this implies we can declare dynamic resource contents like:
31
+ ```ruby
32
+ class HighestScoringUsersResource < FastMcp::Resource
33
+ ...
34
+ def content
35
+ User.order(score: :desc).last(5).map(&:as_json)
36
+ end
37
+ end
38
+ ```
39
+ - More robust SSE connection lifecycle management
40
+ - Optimized test suite with faster execution times
41
+ - Better logging for debugging connection issues
42
+ - Documentation had outdated examples
43
+
8
44
  ## [0.1.0] - 2025-03-12
9
45
 
10
46
  ### Added
11
47
 
12
48
  - 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
49
+ - FastMcp::Tool class with multiple definition styles
50
+ - FastMcp::Server class with STDIO transport and HTTP / SSE transport
15
51
  - Rack Integration with authenticated and standard middleware options
16
52
  - Resource management with subscription capabilities
17
53
  - Binary resource support
data/README.md CHANGED
@@ -28,44 +28,49 @@ 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: 'recipe-ai', version: '1.0.0')
40
40
 
41
- # Define a tool by inheriting from MCP::Tool
42
- class GetRecipesTool < MCP::Tool
41
+ # Define a tool by inheriting from FastMcp::Tool
42
+ class CreateUserTool < FastMcp::Tool
43
43
  description "Find recipes based on ingredients"
44
44
 
45
- arguments do
46
45
  # These arguments will generate the needed JSON to be presented to the MCP Client
47
- # And they will be vaidated at run time.
46
+ # And they will be validated at run time.
48
47
  # 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")
48
+ arguments do
49
+ required(:first_name).filled(:string).description("First name of the user")
50
+ optional(:age).filled(:integer).description("Age of the user")
51
+ required(:address).hash do
52
+ optional(:street).filled(:string)
53
+ optional(:city).filled(:string)
54
+ optional(:zipcode).filled(:string)
55
+ end
51
56
  end
52
57
 
53
- def call(ingredients:, cuisine: nil)
54
- Recipe.find_by_ingredients(ingredients, cuisine: cuisine)
58
+ def call(first_name:, age: nil, address: {})
59
+ User.create!(first_name:, age:, address:)
55
60
  end
56
61
  end
57
62
 
58
63
  # Register the tool with the server
59
- server.register_tool(GetRecipesTool)
64
+ server.register_tool(CreateUserTool)
60
65
 
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"
66
+ # Share data resources with AI models by inheriting from FastMcp::Resource
67
+ class PopularUsers < FastMcp::Resource
68
+ uri "file://popular_users.json"
69
+ resource_name "Popular Users"
65
70
  mime_type "application/json"
66
71
 
67
- def default_content
68
- JSON.generate(Ingredient.popular.as_json)
72
+ def content
73
+ JSON.generate(User.popular.limit(5).as_json)
69
74
  end
70
75
  end
71
76
 
@@ -73,88 +78,109 @@ end
73
78
  server.register_resource(IngredientsResource)
74
79
 
75
80
  # Accessing the resource through the server
76
- server.read_resource("food/popular_ingredients")
81
+ server.read_resource(IngredientsResource.uri)
77
82
 
78
- # Updating the resource content through the server
79
- server.update_resource("food/popular_ingredients", JSON.generate({id: 1, name: 'tomato'}))
83
+ # Notify the resource content has been updated to clients
84
+ server.notify_resource_updated(IngredientsResource.uri)
85
+ ```
80
86
 
87
+ ### 🚂 Fast Ruby on Rails implementation
88
+ ```shell
89
+ bundle add fast-mcp
90
+ bin/rails generate fast_mcp:install
91
+ ```
81
92
 
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
93
+ This will add a configurable `fast_mcp.rb` initializer
91
94
 
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
- )
95
+ ```ruby
96
+ require 'fast_mcp'
98
97
 
99
- # Build real-time applications
100
- server.on_resource_update do |resource|
101
- ActionCable.server.broadcast("recipe_updates", resource.metadata)
98
+ FastMcp.mount_in_rails(
99
+ Rails.application,
100
+ name: Rails.application.class.module_parent_name.underscore.dasherize,
101
+ version: '1.0.0',
102
+ path_prefix: '/mcp' # This is the default path prefix
103
+ # authenticate: true, # Uncomment to enable authentication
104
+ # auth_token: 'your-token', # Required if authenticate: true
105
+ ) do |server|
106
+ Rails.application.config.after_initialize do
107
+ # FastMcp will automatically discover and register:
108
+ # - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
109
+ # - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
110
+ server.register_tools(*ApplicationTool.descendants)
111
+ server.register_resources(*ApplicationResource.descendants)
112
+ # alternatively, you can register tools and resources manually:
113
+ # server.register_tool(MyTool)
114
+ # server.register_resource(MyResource)
115
+ end
102
116
  end
103
117
  ```
118
+ The install script will also:
119
+ - add app/resources folder
120
+ - add app/tools folder
121
+ - add app/tools/sample_tool.rb
122
+ - add app/resources/sample_resource.rb
123
+ - add ApplicationTool to inherit from
124
+ - add ApplicationResource to inherit from as well
104
125
 
105
- ## 📦 Installation
126
+ #### Rails-friendly class naming conventions
106
127
 
107
- ```ruby
108
- # In your Gemfile
109
- gem 'fast-mcp'
128
+ For Rails applications, FastMCP provides Rails-style class names to better fit with Rails conventions:
110
129
 
111
- # Then run
112
- bundle install
130
+ - `ActionTool::Base` - An alias for `FastMcp::Tool`
131
+ - `ActionResource::Base` - An alias for `FastMcp::Resource`
113
132
 
114
- # Or install it yourself
115
- gem install fast-mcp
116
- ```
133
+ These are automatically set up in Rails applications. You can use either naming convention in your code:
117
134
 
118
- ## 🚀 Quick Start
135
+ ```ruby
136
+ # Using Rails-style naming:
137
+ class MyTool < ActionTool::Base
138
+ description "My awesome tool"
139
+
140
+ arguments do
141
+ required(:input).filled(:string)
142
+ end
143
+
144
+ def call(input:)
145
+ # Your implementation
146
+ end
147
+ end
119
148
 
120
- ### Testing with the inspector
149
+ # Using standard FastMcp naming:
150
+ class AnotherTool < FastMcp::Tool
151
+ # Both styles work interchangeably in Rails apps
152
+ end
153
+ ```
121
154
 
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 !
155
+ When creating new tools or resources, the generators will use the Rails naming convention by default:
125
156
 
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
132
- ```
157
+ ```ruby
158
+ # app/tools/application_tool.rb
159
+ class ApplicationTool < ActionTool::Base
160
+ # Base methods for all tools
161
+ end
133
162
 
134
- Or to test over SSE with an authenticated rack middleware:
135
- ```shell
136
- npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
163
+ # app/resources/application_resource.rb
164
+ class ApplicationResource < ActionResource::Base
165
+ # Base methods for all resources
166
+ end
137
167
  ```
138
168
 
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
169
+ ### Easy Sinatra setup
170
+ I'll let you check out the dedicated [sinatra integration docs](./docs/sinatra_integration.md).
143
171
 
144
- # Test with an HTTP / SSE server. In the UI select SSE and input your address.
145
- npx @modelcontextprotocol/inspector
146
- ```
172
+ ## 🚀 Quick Start
147
173
 
148
- ### Create a Server with Tools and Resources
174
+ ### Create a Server with Tools and Resources and STDIO transport
149
175
 
150
176
  ```ruby
151
177
  require 'fast_mcp'
152
178
 
153
179
  # Create an MCP server
154
- server = MCP::Server.new(name: 'my-ai-server', version: '1.0.0')
180
+ server = FastMcp::Server.new(name: 'my-ai-server', version: '1.0.0')
155
181
 
156
- # Define a tool by inheriting from MCP::Tool
157
- class SummarizeTool < MCP::Tool
182
+ # Define a tool by inheriting from FastMcp::Tool
183
+ class SummarizeTool < FastMcp::Tool
158
184
  description "Summarize a given text"
159
185
 
160
186
  arguments do
@@ -171,14 +197,14 @@ end
171
197
  # Register the tool with the server
172
198
  server.register_tool(SummarizeTool)
173
199
 
174
- # Create a resource by inheriting from MCP::Resource
175
- class StatisticsResource < MCP::Resource
200
+ # Create a resource by inheriting from FastMcp::Resource
201
+ class StatisticsResource < FastMcp::Resource
176
202
  uri "data/statistics"
177
- name "Usage Statistics"
203
+ resource_name "Usage Statistics"
178
204
  description "Current system statistics"
179
205
  mime_type "application/json"
180
206
 
181
- def default_content
207
+ def content
182
208
  JSON.generate({
183
209
  users_online: 120,
184
210
  queries_per_minute: 250,
@@ -188,30 +214,38 @@ class StatisticsResource < MCP::Resource
188
214
  end
189
215
 
190
216
  # Register the resource with the server
191
- server.register_resource(StatisticsResource.new)
217
+ server.register_resource(StatisticsResource)
192
218
 
193
219
  # Start the server
194
220
  server.start
195
221
  ```
196
222
 
197
- ### Integrate with Web Frameworks
223
+ ## 🧪 Testing with the inspector
198
224
 
199
- #### Rails
225
+ MCP has developed a very [useful inspector](https://github.com/modelcontextprotocol/inspector).
226
+ You can use it to validate your implementation. I suggest you use the examples I provided with this project as an easy boilerplate.
227
+ Clone this project, then give it a go !
200
228
 
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
229
+ ```shell
230
+ npx @modelcontextprotocol/inspector examples/server_with_stdio_transport.rb
231
+ ```
232
+ Or to test with an SSE transport using a rack middleware:
233
+ ```shell
234
+ npx @modelcontextprotocol/inspector examples/rack_middleware.rb
235
+ ```
236
+
237
+ Or to test over SSE with an authenticated rack middleware:
238
+ ```shell
239
+ npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
240
+ ```
241
+
242
+ You can test your custom implementation with the official MCP inspector by using:
243
+ ```shell
244
+ # Test with a stdio transport:
245
+ npx @modelcontextprotocol/inspector path/to/your_ruby_file.rb
246
+
247
+ # Test with an HTTP / SSE server. In the UI select SSE and input your address.
248
+ npx @modelcontextprotocol/inspector
215
249
  ```
216
250
 
217
251
  #### Sinatra
@@ -221,7 +255,7 @@ end
221
255
  require 'sinatra'
222
256
  require 'fast_mcp'
223
257
 
224
- use MCP::RackMiddleware.new(name: 'my-ai-server', version: '1.0.0') do |server|
258
+ use FastMcp::RackMiddleware.new(name: 'my-ai-server', version: '1.0.0') do |server|
225
259
  # Register tools and resources here
226
260
  server.register_tool(SummarizeTool)
227
261
  end
@@ -250,6 +284,9 @@ Add your server to your Claude Desktop configuration at:
250
284
  }
251
285
  ```
252
286
 
287
+ ## How to add a MCP server to Claude, Cursor, or other MCP clients?
288
+ Please refer to [configuring_mcp_clients](docs/configuring_mcp_clients.md)
289
+
253
290
  ## 📊 Supported Specifications
254
291
 
255
292
  | Feature | Status |
@@ -276,11 +313,8 @@ Add your server to your Claude Desktop configuration at:
276
313
  - [🧩 Integration Guide](docs/integration_guide.md)
277
314
  - [🛤️ Rails Integration](docs/rails_integration.md)
278
315
  - [🌐 Sinatra Integration](docs/sinatra_integration.md)
279
- - [🌸 Hanami Integration](docs/hanami_integration.md)
280
316
  - [📚 Resources](docs/resources.md)
281
317
  - [🛠️ Tools](docs/tools.md)
282
- - [🔌 Transports](docs/transports.md)
283
- - [📘 API Reference](docs/api_reference.md)
284
318
 
285
319
  ## 💻 Examples
286
320
 
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,7 +29,7 @@ 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
@@ -31,19 +38,22 @@ module MCP
31
38
  # @option options [String] :path_prefix The path prefix for the MCP endpoints
32
39
  # @option options [Logger] :logger The logger to use
33
40
  # @yield [server] A block to configure the server
34
- # @yieldparam server [MCP::Server] The server to configure
41
+ # @yieldparam server [FastMcp::Server] The server to configure
35
42
  # @return [#call] The Rack middleware
36
43
  def self.rack_middleware(app, options = {})
37
44
  name = options.delete(:name) || 'mcp-server'
38
45
  version = options.delete(:version) || '1.0.0'
39
46
  logger = options.delete(:logger) || Logger.new
40
47
 
41
- server = MCP::Server.new(name: name, version: version, logger: logger)
48
+ server = FastMcp::Server.new(name: name, version: version, logger: logger)
42
49
  yield server if block_given?
43
50
 
44
51
  # Store the server in the Sinatra settings if available
45
52
  app.settings.set(:mcp_server, server) if app.respond_to?(:settings) && app.settings.respond_to?(:mcp_server=)
46
53
 
54
+ # Store the server in the FastMcp module
55
+ self.server = server
56
+
47
57
  server.start_rack(app, options)
48
58
  end
49
59
 
@@ -54,16 +64,93 @@ module MCP
54
64
  # @option options [String] :version The version of the server
55
65
  # @option options [String] :auth_token The authentication token
56
66
  # @yield [server] A block to configure the server
57
- # @yieldparam server [MCP::Server] The server to configure
67
+ # @yieldparam server [FastMcp::Server] The server to configure
58
68
  # @return [#call] The Rack middleware
59
69
  def self.authenticated_rack_middleware(app, options = {})
60
70
  name = options.delete(:name) || 'mcp-server'
61
71
  version = options.delete(:version) || '1.0.0'
62
72
  logger = options.delete(:logger) || Logger.new
63
73
 
64
- server = MCP::Server.new(name: name, version: version, logger: logger)
74
+ server = FastMcp::Server.new(name: name, version: version, logger: logger)
65
75
  yield server if block_given?
66
76
 
77
+ # Store the server in the FastMcp module
78
+ self.server = server
79
+
67
80
  server.start_authenticated_rack(app, options)
68
81
  end
82
+
83
+ # Register a tool with the MCP server
84
+ # @param tool [FastMcp::Tool] The tool to register
85
+ # @return [FastMcp::Tool] The registered tool
86
+ def self.register_tool(tool)
87
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
88
+ self.server.register_tool(tool)
89
+ end
90
+
91
+ # Register multiple tools at once
92
+ # @param tools [Array<FastMcp::Tool>] The tools to register
93
+ # @return [Array<FastMcp::Tool>] The registered tools
94
+ def self.register_tools(*tools)
95
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
96
+ self.server.register_tools(*tools)
97
+ end
98
+
99
+ # Register a resource with the MCP server
100
+ # @param resource [FastMcp::Resource] The resource to register
101
+ # @return [FastMcp::Resource] The registered resource
102
+ def self.register_resource(resource)
103
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
104
+ self.server.register_resource(resource)
105
+ end
106
+
107
+ # Register multiple resources at once
108
+ # @param resources [Array<FastMcp::Resource>] The resources to register
109
+ # @return [Array<FastMcp::Resource>] The registered resources
110
+ def self.register_resources(*resources)
111
+ self.server ||= FastMcp::Server.new(name: 'mcp-server', version: '1.0.0')
112
+ self.server.register_resources(*resources)
113
+ end
114
+
115
+ # Mount the MCP middleware in a Rails application
116
+ # @param app [Rails::Application] The Rails application
117
+ # @param options [Hash] Options for the middleware
118
+ # @option options [String] :name The name of the server
119
+ # @option options [String] :version The version of the server
120
+ # @option options [String] :path_prefix The path prefix for the MCP endpoints
121
+ # @option options [Logger] :logger The logger to use
122
+ # @option options [Boolean] :authenticate Whether to use authentication
123
+ # @option options [String] :auth_token The authentication token
124
+ # @yield [server] A block to configure the server
125
+ # @yieldparam server [FastMcp::Server] The server to configure
126
+ # @return [#call] The Rack middleware
127
+ def self.mount_in_rails(app, options = {})
128
+ # Default options
129
+ name = options.delete(:name) || app.class.module_parent_name.underscore.dasherize
130
+ version = options.delete(:version) || '1.0.0'
131
+ logger = options[:logger] || Rails.logger
132
+ path_prefix = options.delete(:path_prefix) || '/mcp'
133
+ authenticate = options.delete(:authenticate) || false
134
+
135
+ options[:logger] = logger
136
+ # Create or get the server
137
+ self.server = FastMcp::Server.new(name: name, version: version, logger: logger)
138
+ yield self.server if block_given?
139
+
140
+ # Choose the right middleware based on authentication
141
+ self.server.transport_klass = if authenticate
142
+ FastMcp::Transports::AuthenticatedRackTransport
143
+ else
144
+ FastMcp::Transports::RackTransport
145
+ end
146
+
147
+ # Insert the middleware in the Rails middleware stack
148
+ app.middleware.use self.server.transport_klass, self.server, options.merge(path_prefix: path_prefix)
149
+ end
150
+
151
+ # Notify the server that a resource has been updated
152
+ # @param uri [String] The URI of the resource
153
+ def self.notify_resource_updated(uri)
154
+ self.server.notify_resource_updated(uri)
155
+ end
69
156
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ module FastMcp
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ desc 'Creates a FastMcp initializer for Rails applications'
11
+
12
+ def copy_initializer
13
+ template 'fast_mcp_initializer.rb', 'config/initializers/fast_mcp.rb'
14
+ end
15
+
16
+ def create_directories
17
+ empty_directory 'app/tools'
18
+ empty_directory 'app/resources'
19
+ end
20
+
21
+ def copy_application_tool
22
+ template 'application_tool.rb', 'app/tools/application_tool.rb'
23
+ end
24
+
25
+ def copy_application_resource
26
+ template 'application_resource.rb', 'app/resources/application_resource.rb'
27
+ end
28
+
29
+ def copy_sample_tool
30
+ template 'sample_tool.rb', 'app/tools/sample_tool.rb'
31
+ end
32
+
33
+ def copy_sample_resource
34
+ template 'sample_resource.rb', 'app/resources/sample_resource.rb'
35
+ end
36
+
37
+ def display_post_install_message
38
+ say "\n========================================================="
39
+ say 'FastMcp was successfully installed! 🎉'
40
+ say "=========================================================\n"
41
+ say 'You can now create:'
42
+ say ' • Tools in app/tools/'
43
+ say ' • Resources in app/resources/'
44
+ say "\n"
45
+ say 'Check config/initializers/fast_mcp.rb to configure the middleware.'
46
+ say "=========================================================\n"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationResource < ActionResource::Base
4
+ # write your custom logic to be shared across all resources here
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationTool < ActionTool::Base
4
+ # write your custom logic to be shared across all tools here
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # FastMcp - Model Context Protocol for Rails
4
+ # This initializer sets up the MCP middleware in your Rails application.
5
+ #
6
+ # In Rails applications, you can use:
7
+ # - ActionTool::Base as an alias for FastMcp::Tool
8
+ # - ActionResource::Base as an alias for FastMcp::Resource
9
+ #
10
+ # All your tools should inherit from ApplicationTool which already uses ActionTool::Base,
11
+ # and all your resources should inherit from ApplicationResource which uses ActionResource::Base.
12
+
13
+ # Mount the MCP middleware in your Rails application
14
+ # You can customize the options below to fit your needs.
15
+ require 'fast_mcp'
16
+
17
+ FastMcp.mount_in_rails(
18
+ Rails.application,
19
+ name: Rails.application.class.module_parent_name.underscore.dasherize,
20
+ version: '1.0.0',
21
+ path_prefix: '/mcp' # This is the default path prefix
22
+ # authenticate: true, # Uncomment to enable authentication
23
+ # auth_token: 'your-token', # Required if authenticate: true
24
+ ) do |server|
25
+ Rails.application.config.after_initialize do
26
+ # FastMcp will automatically discover and register:
27
+ # - All classes that inherit from ApplicationTool (which uses ActionTool::Base)
28
+ # - All classes that inherit from ApplicationResource (which uses ActionResource::Base)
29
+ server.register_tools(*ApplicationTool.descendants)
30
+ server.register_resources(*ApplicationResource.descendants)
31
+ # alternatively, you can register tools and resources manually:
32
+ # server.register_tool(MyTool)
33
+ # server.register_resource(MyResource)
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SampleResource < ApplicationResource
4
+ uri 'examples/users'
5
+ resource_name 'Users'
6
+ description 'A user resource for demonstration'
7
+ mime_type 'application/json'
8
+
9
+ def content
10
+ JSON.generate(User.all.as_json)
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SampleTool < ApplicationTool
4
+ description 'Greet a user'
5
+
6
+ arguments do
7
+ required(:id).filled(:integer).description('ID of the user to greet')
8
+ optional(:prefix).filled(:string).description('Prefix to add to the greeting')
9
+ end
10
+
11
+ def call(id:, prefix: 'Hey')
12
+ user = User.find(id)
13
+
14
+ "#{prefix} #{user.first_name} !"
15
+ end
16
+ end
data/lib/mcp/logger.rb CHANGED
@@ -1,33 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # This class is not used yet.
4
- module MCP
4
+ module FastMcp
5
5
  class Logger < Logger
6
- def initialize
6
+ def initialize(transport: :stdio)
7
7
  @client_initialized = false
8
- @transport = nil
8
+ @transport = transport
9
9
 
10
- super($stdout)
10
+ # we don't want to log to stdout if we're using the stdio transport
11
+ super($stdout) unless stdio_transport?
11
12
  end
12
13
 
13
14
  attr_accessor :transport, :client_initialized
14
-
15
- def client_initialized?
16
- client_initialized
17
- end
15
+ alias client_initialized? client_initialized
18
16
 
19
17
  def stdio_transport?
20
18
  transport == :stdio
21
19
  end
22
20
 
21
+ def add(severity, message = nil, progname = nil, &block)
22
+ return if stdio_transport? # we don't want to log to stdout if we're using the stdio transport
23
+
24
+ # TODO: implement logging as the specification requires
25
+ super
26
+ end
27
+
23
28
  def rack_transport?
24
29
  transport == :rack
25
30
  end
26
-
27
- # def add(severity, message = nil, progname = nil, &block)
28
- # # return unless client_initialized? && rack_transport?
29
-
30
- # super
31
- # end
32
31
  end
33
32
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'fileutils'
5
+ require_relative '../mcp/server'
6
+
7
+ # Create ActionTool and ActionResource modules at load time
8
+ unless defined?(ActionTool)
9
+ module ::ActionTool
10
+ Base = FastMcp::Tool
11
+ end
12
+ end
13
+
14
+ unless defined?(ActionResource)
15
+ module ::ActionResource
16
+ Base = FastMcp::Resource
17
+ end
18
+ end
19
+
20
+ module FastMcp
21
+ # Railtie for integrating Fast MCP with Rails applications
22
+ class Railtie < Rails::Railtie
23
+ # Add tools and resources directories to autoload paths
24
+ initializer 'fast_mcp.setup_autoload_paths' do |app|
25
+ app.config.autoload_paths += %W[
26
+ #{app.root}/app/tools
27
+ #{app.root}/app/resources
28
+ ]
29
+ end
30
+
31
+ # Auto-register all tools and resources after the application is fully loaded
32
+ config.after_initialize do
33
+ # Load all files in app/tools and app/resources directories
34
+ Dir[Rails.root.join('app', 'tools', '**', '*.rb')].each { |f| require f }
35
+ Dir[Rails.root.join('app', 'resources', '**', '*.rb')].each { |f| require f }
36
+ end
37
+
38
+ # Add rake tasks
39
+ rake_tasks do
40
+ # Path to the tasks directory in the gem
41
+ path = File.expand_path('../tasks', __dir__)
42
+ Dir.glob("#{path}/**/*.rake").each { |f| load f }
43
+ end
44
+ end
45
+ end
data/lib/mcp/resource.rb CHANGED
@@ -5,11 +5,13 @@ require 'base64'
5
5
  require 'mime/types'
6
6
  require 'singleton'
7
7
 
8
- module MCP
8
+ module FastMcp
9
9
  # Resource class for MCP Resources feature
10
10
  # Represents a resource that can be exposed to clients
11
11
  class Resource
12
12
  class << self
13
+ attr_accessor :server
14
+
13
15
  # Define URI for this resource
14
16
  # @param value [String, nil] The URI for this resource
15
17
  # @return [String] The URI for this resource
@@ -74,7 +76,7 @@ module MCP
74
76
  end
75
77
 
76
78
  # Override content method to load from file
77
- define_method :default_content do
79
+ define_method :content do
78
80
  if binary?
79
81
  File.binread(file_path)
80
82
  else
@@ -87,13 +89,6 @@ module MCP
87
89
 
88
90
  include Singleton
89
91
 
90
- attr_accessor :content
91
-
92
- # Initialize a resource singleton instance
93
- def initialize
94
- @content = default_content
95
- end
96
-
97
92
  # URI of the resource - delegates to class method
98
93
  # @return [String, nil] The URI for this resource
99
94
  def uri
@@ -120,7 +115,7 @@ module MCP
120
115
 
121
116
  # Method to be overridden by subclasses to dynamically generate content
122
117
  # @return [String, nil] Generated content for this resource
123
- def default_content
118
+ def content
124
119
  raise NotImplementedError, 'Subclasses must implement content'
125
120
  end
126
121
 
data/lib/mcp/server.rb CHANGED
@@ -9,9 +9,9 @@ require_relative 'transports/rack_transport'
9
9
  require_relative 'transports/authenticated_rack_transport'
10
10
  require_relative 'logger'
11
11
 
12
- module MCP
12
+ module FastMcp
13
13
  class Server
14
- attr_reader :name, :version, :tools, :resources, :logger, :transport, :capabilities
14
+ attr_reader :name, :version, :tools, :resources, :capabilities
15
15
 
16
16
  DEFAULT_CAPABILITIES = {
17
17
  resources: {
@@ -23,7 +23,7 @@ module MCP
23
23
  }
24
24
  }.freeze
25
25
 
26
- def initialize(name:, version:, logger: MCP::Logger.new, capabilities: {})
26
+ def initialize(name:, version:, logger: FastMcp::Logger.new, capabilities: {})
27
27
  @name = name
28
28
  @version = version
29
29
  @tools = {}
@@ -32,13 +32,17 @@ module MCP
32
32
  @logger = logger
33
33
  @logger.level = Logger::INFO
34
34
  @request_id = 0
35
+ @transport_klass = nil
35
36
  @transport = nil
36
37
  @capabilities = DEFAULT_CAPABILITIES.dup
37
38
 
38
39
  # Merge with provided capabilities
39
40
  @capabilities.merge!(capabilities) if capabilities.is_a?(Hash)
40
41
  end
42
+ attr_accessor :transport, :transport_klass, :logger
41
43
 
44
+ # Register multiple tools at once
45
+ # @param tools [Array<Tool>] Tools to register
42
46
  def register_tools(*tools)
43
47
  tools.each do |tool|
44
48
  register_tool(tool)
@@ -49,6 +53,7 @@ module MCP
49
53
  def register_tool(tool)
50
54
  @tools[tool.tool_name] = tool
51
55
  @logger.info("Registered tool: #{tool.tool_name}")
56
+ tool.server = self
52
57
  end
53
58
 
54
59
  # Register multiple resources at once
@@ -63,7 +68,7 @@ module MCP
63
68
  def register_resource(resource)
64
69
  @resources[resource.uri] = resource
65
70
  @logger.info("Registered resource: #{resource.name} (#{resource.uri})")
66
-
71
+ resource.server = self
67
72
  # Notify subscribers about the list change
68
73
  notify_resource_list_changed if @transport
69
74
 
@@ -93,7 +98,8 @@ module MCP
93
98
  @logger.info("Available resources: #{@resources.keys.join(', ')}")
94
99
 
95
100
  # Use STDIO transport by default
96
- @transport = MCP::Transports::StdioTransport.new(self, logger: @logger)
101
+ @transport_klass = FastMcp::Transports::StdioTransport
102
+ @transport = @transport_klass.new(self, logger: @logger)
97
103
  @transport.start
98
104
  end
99
105
 
@@ -104,7 +110,8 @@ module MCP
104
110
  @logger.info("Available resources: #{@resources.keys.join(', ')}")
105
111
 
106
112
  # Use Rack transport
107
- @transport = MCP::Transports::RackTransport.new(self, app, options.merge(logger: @logger))
113
+ transport_klass = FastMcp::Transports::RackTransport
114
+ @transport = transport_klass.new(app, self, options.merge(logger: @logger))
108
115
  @transport.start
109
116
 
110
117
  # Return the transport as middleware
@@ -117,7 +124,8 @@ module MCP
117
124
  @logger.info("Available resources: #{@resources.keys.join(', ')}")
118
125
 
119
126
  # Use Rack transport
120
- @transport = MCP::Transports::AuthenticatedRackTransport.new(self, app, options.merge(logger: @logger))
127
+ transport_klass = FastMcp::Transports::AuthenticatedRackTransport
128
+ @transport = transport_klass.new(app, self, options.merge(logger: @logger))
121
129
  @transport.start
122
130
 
123
131
  # Return the transport as middleware
@@ -180,47 +188,6 @@ module MCP
180
188
  end
181
189
  end
182
190
 
183
- # Register a callback for resource updates
184
- def on_resource_update(&block)
185
- @resource_update_callbacks ||= []
186
- callback_id = SecureRandom.uuid
187
- @resource_update_callbacks << { id: callback_id, callback: block }
188
- callback_id
189
- end
190
-
191
- # Remove a resource update callback
192
- def remove_resource_update_callback(callback_id)
193
- @resource_update_callbacks ||= []
194
- @resource_update_callbacks.reject! { |cb| cb[:id] == callback_id }
195
- end
196
-
197
- # Update a resource and notify subscribers
198
- def update_resource(uri, content)
199
- return false unless @resources.key?(uri)
200
-
201
- resource = @resources[uri]
202
- resource.instance.content = content
203
-
204
- # Notify subscribers
205
- notify_resource_updated(uri) if @transport && @resource_subscriptions.key?(uri)
206
-
207
- # Notify resource update callbacks
208
- if @resource_update_callbacks && !@resource_update_callbacks.empty?
209
- @resource_update_callbacks.each do |cb|
210
- cb[:callback].call(
211
- {
212
- uri: uri,
213
- name: resource.name,
214
- mime_type: resource.mime_type,
215
- content: content
216
- }
217
- )
218
- end
219
- end
220
-
221
- true
222
- end
223
-
224
191
  # Read a resource directly
225
192
  def read_resource(uri)
226
193
  resource = @resources[uri]
@@ -229,13 +196,32 @@ module MCP
229
196
  resource
230
197
  end
231
198
 
199
+ # Notify subscribers about a resource update
200
+ def notify_resource_updated(uri)
201
+ @logger.warn("Notifying subscribers about resource update: #{uri}, #{@resource_subscriptions.inspect}")
202
+ return unless @client_initialized && @resource_subscriptions.key?(uri)
203
+
204
+ resource = @resources[uri]
205
+ notification = {
206
+ jsonrpc: '2.0',
207
+ method: 'notifications/resources/updated',
208
+ params: {
209
+ uri: uri,
210
+ name: resource.name,
211
+ mimeType: resource.mime_type
212
+ }
213
+ }
214
+
215
+ @transport.send_message(notification)
216
+ end
217
+
232
218
  private
233
219
 
234
220
  PROTOCOL_VERSION = '2024-11-05'
235
221
 
236
222
  def handle_initialize(params, id)
237
- params['protocolVersion']
238
- client_capabilities = params['capabilities'] || {}
223
+ # Store client capabilities for later use
224
+ @client_capabilities = params['capabilities'] || {}
239
225
  client_info = params['clientInfo'] || {}
240
226
 
241
227
  # Log client information
@@ -254,9 +240,6 @@ module MCP
254
240
 
255
241
  @logger.info("Server response: #{response.inspect}")
256
242
 
257
- # Store client capabilities for later use
258
- @client_capabilities = client_capabilities
259
-
260
243
  send_result(response, id)
261
244
  end
262
245
 
@@ -290,8 +273,9 @@ module MCP
290
273
  # The client is now ready for normal operation
291
274
  # No response needed for notifications
292
275
  @client_initialized = true
293
- @logger.set_client_initialized
294
276
  @logger.info('Client initialized, beginning normal operation')
277
+
278
+ nil
295
279
  end
296
280
 
297
281
  # Handle tools/list request
@@ -324,7 +308,7 @@ module MCP
324
308
 
325
309
  # Format and send the result
326
310
  send_formatted_result(result, id)
327
- rescue MCP::Tool::InvalidArgumentsError => e
311
+ rescue FastMcp::Tool::InvalidArgumentsError => e
328
312
  @logger.error("Invalid arguments for tool #{tool_name}: #{e.message}")
329
313
  send_error_result(e.message, id)
330
314
  rescue StandardError => e
@@ -410,24 +394,6 @@ module MCP
410
394
  send_result({ unsubscribed: true }, id)
411
395
  end
412
396
 
413
- # Notify subscribers about a resource update
414
- def notify_resource_updated(uri)
415
- return unless @client_initialized && @resource_subscriptions.key?(uri)
416
-
417
- resource = @resources[uri]
418
- notification = {
419
- jsonrpc: '2.0',
420
- method: 'notifications/resources/updated',
421
- params: {
422
- uri: uri,
423
- name: resource.name,
424
- mimeType: resource.mime_type
425
- }
426
- }
427
-
428
- @transport.send_message(notification)
429
- end
430
-
431
397
  # Notify clients about resource list changes
432
398
  def notify_resource_list_changed
433
399
  return unless @client_initialized
@@ -470,10 +436,10 @@ module MCP
470
436
  def send_response(response)
471
437
  if @transport
472
438
  @logger.info("Sending response: #{response.inspect}")
473
- @logger.info("Transport: #{@transport.inspect}")
474
439
  @transport.send_message(response)
475
440
  else
476
441
  @logger.warn("No transport available to send response: #{response.inspect}")
442
+ @logger.warn("Transport: #{@transport.inspect}, transport_klass: #{@transport_klass.inspect}")
477
443
  end
478
444
  end
479
445
 
data/lib/mcp/tool.rb CHANGED
@@ -62,12 +62,14 @@ module Dry
62
62
  end
63
63
  end
64
64
 
65
- module MCP
65
+ module FastMcp
66
66
  # Main Tool class that represents an MCP Tool
67
67
  class Tool
68
68
  class InvalidArgumentsError < StandardError; end
69
69
 
70
70
  class << self
71
+ attr_accessor :server
72
+
71
73
  def arguments(&block)
72
74
  @input_schema = Dry::Schema.JSON(&block)
73
75
  end
@@ -100,6 +102,10 @@ module MCP
100
102
  end
101
103
  end
102
104
 
105
+ def notify_resource_updated(uri)
106
+ self.class.server.notify_resource_updated(uri)
107
+ end
108
+
103
109
  def call_with_schema_validation!(**args)
104
110
  arg_validation = self.class.input_schema.call(args)
105
111
  raise InvalidArgumentsError, arg_validation.errors.to_h.to_json if arg_validation.errors.any?
@@ -788,7 +794,7 @@ module MCP
788
794
  end
789
795
 
790
796
  # Example
791
- # class ExampleTool < MCP::Tool
797
+ # class ExampleTool < FastMcp::Tool
792
798
  # description 'An example tool'
793
799
 
794
800
  # arguments do
@@ -2,10 +2,10 @@
2
2
 
3
3
  require_relative 'rack_transport'
4
4
 
5
- module MCP
5
+ module FastMcp
6
6
  module Transports
7
7
  class AuthenticatedRackTransport < RackTransport
8
- def initialize(server, app, options = {})
8
+ def initialize(app, server, options = {})
9
9
  super
10
10
 
11
11
  @auth_token = options[:auth_token]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module MCP
3
+ module FastMcp
4
4
  module Transports
5
5
  # Base class for all MCP transports
6
6
  # This defines the interface that all transports must implement
@@ -5,7 +5,7 @@ require 'securerandom'
5
5
  require 'rack'
6
6
  require_relative 'base_transport'
7
7
 
8
- module MCP
8
+ module FastMcp
9
9
  module Transports
10
10
  # Rack middleware transport for MCP
11
11
  # This transport can be mounted in any Rack-compatible web framework
@@ -14,7 +14,7 @@ module MCP
14
14
 
15
15
  attr_reader :app, :path_prefix, :sse_clients
16
16
 
17
- def initialize(server, app, options = {})
17
+ def initialize(app, server, options = {}, &_block)
18
18
  super(server, logger: options[:logger])
19
19
  @app = app
20
20
  @path_prefix = options[:path_prefix] || DEFAULT_PATH_PREFIX
@@ -24,13 +24,13 @@ module MCP
24
24
 
25
25
  # Start the transport
26
26
  def start
27
- @logger.info("Starting Rack transport with path prefix: #{@path_prefix}")
27
+ @logger.debug("Starting Rack transport with path prefix: #{@path_prefix}")
28
28
  @running = true
29
29
  end
30
30
 
31
31
  # Stop the transport
32
32
  def stop
33
- @logger.info('Stopping Rack transport')
33
+ @logger.debug('Stopping Rack transport')
34
34
  @running = false
35
35
 
36
36
  # Close all SSE connections
@@ -45,7 +45,7 @@ module MCP
45
45
  # Send a message to all connected SSE clients
46
46
  def send_message(message)
47
47
  json_message = message.is_a?(String) ? message : JSON.generate(message)
48
- @logger.info("Broadcasting message to #{@sse_clients.size} SSE clients: #{json_message}")
48
+ @logger.debug("Broadcasting message to #{@sse_clients.size} SSE clients: #{json_message}")
49
49
 
50
50
  clients_to_remove = []
51
51
 
@@ -85,10 +85,12 @@ module MCP
85
85
  def call(env)
86
86
  request = Rack::Request.new(env)
87
87
  path = request.path
88
- @logger.info("Rack request path: #{path}")
88
+ @logger.debug("Rack request path: #{path}")
89
89
 
90
90
  # Check if the request is for our MCP endpoints
91
91
  if path.start_with?(@path_prefix)
92
+ @logger.debug('Setting server transport to RackTransport')
93
+ @server.transport = self
92
94
  handle_mcp_request(request, env)
93
95
  else
94
96
  # Pass through to the main application
@@ -107,7 +109,6 @@ module MCP
107
109
  when '/sse'
108
110
  handle_sse_request(request, env)
109
111
  when '/messages'
110
- @logger.info('Received message request')
111
112
  handle_message_request(request)
112
113
  else
113
114
  @logger.info('Received unknown request')
@@ -270,11 +271,11 @@ module MCP
270
271
  # Handle SSE with Rack hijacking (e.g., Puma)
271
272
  def handle_rack_hijack_sse(env, headers)
272
273
  client_id = extract_client_id(env)
273
- @logger.info("Setting up Rack hijack SSE connection for client #{client_id}")
274
+ @logger.debug("Setting up Rack hijack SSE connection for client #{client_id}")
274
275
 
275
276
  env['rack.hijack'].call
276
277
  io = env['rack.hijack_io']
277
- @logger.info("Obtained hijack IO for client #{client_id}")
278
+ @logger.debug("Obtained hijack IO for client #{client_id}")
278
279
 
279
280
  setup_sse_connection(client_id, io, headers)
280
281
  start_keep_alive_thread(client_id, io)
@@ -286,7 +287,7 @@ module MCP
286
287
  # Set up the SSE connection
287
288
  def setup_sse_connection(client_id, io, headers)
288
289
  # Send headers
289
- @logger.info("Sending HTTP headers for SSE connection #{client_id}")
290
+ @logger.debug("Sending HTTP headers for SSE connection #{client_id}")
290
291
  io.write("HTTP/1.1 200 OK\r\n")
291
292
  headers.each { |k, v| io.write("#{k}: #{v}\r\n") }
292
293
  io.write("\r\n")
@@ -300,7 +301,7 @@ module MCP
300
301
 
301
302
  # Send endpoint information as the first message
302
303
  endpoint = "#{@path_prefix}/messages"
303
- @logger.info("Sending endpoint information to client #{client_id}: #{endpoint}")
304
+ @logger.debug("Sending endpoint information to client #{client_id}: #{endpoint}")
304
305
  io.write("event: endpoint\ndata: #{endpoint}\n\n")
305
306
 
306
307
  # Send a retry directive with a very short reconnect time
@@ -332,6 +333,7 @@ module MCP
332
333
  ping_count = 0
333
334
  ping_interval = 1 # Send a ping every 1 second
334
335
  max_ping_count = 30 # Reset connection after 30 pings (about 30 seconds)
336
+ @running = true
335
337
 
336
338
  while @running && !io.closed?
337
339
  begin
@@ -357,13 +359,13 @@ module MCP
357
359
 
358
360
  # Only send actual ping events every 5 counts to reduce overhead
359
361
  if (ping_count % 5).zero?
360
- @logger.info("Sending ping ##{ping_count} to SSE client #{client_id}")
362
+ @logger.debug("Sending ping ##{ping_count} to SSE client #{client_id}")
361
363
  send_ping_event(io)
362
364
  end
363
365
 
364
366
  # If we've reached the max ping count, force a reconnection
365
367
  if ping_count >= max_ping_count
366
- @logger.info("Reached max ping count (#{max_ping_count}) for client #{client_id}, forcing reconnection")
368
+ @logger.debug("Reached max ping count (#{max_ping_count}) for client #{client_id}, forcing reconnection")
367
369
  send_reconnect_event(io)
368
370
  end
369
371
 
@@ -414,7 +416,7 @@ module MCP
414
416
 
415
417
  # Handle message POST request
416
418
  def handle_message_request(request)
417
- @logger.info('Received message request')
419
+ @logger.debug('Received message request')
418
420
  return method_not_allowed_response unless request.post?
419
421
 
420
422
  begin
@@ -431,9 +433,10 @@ module MCP
431
433
  # Parse the request body
432
434
  body = request.body.read
433
435
 
434
- response = process_message(body)
436
+ response = process_message(body) || ''
435
437
  @logger.info("Response: #{response}")
436
- [200, { 'Content-Type' => 'application/json' }, [response]]
438
+
439
+ [200, { 'Content-Type' => 'application/json' }, response]
437
440
  end
438
441
 
439
442
  # Return a method not allowed error response
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'base_transport'
4
4
 
5
- module MCP
5
+ module FastMcp
6
6
  module Transports
7
7
  # STDIO transport for MCP
8
8
  # This transport uses standard input/output for communication
data/lib/mcp/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- module FastMCP
5
- VERSION = '0.1.0'
4
+ module FastMcp
5
+ VERSION = '1.0.0'
6
6
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yorick Jacquin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-23 00:00:00.000000000 Z
11
+ date: 2025-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: dry-schema
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -64,7 +78,14 @@ files:
64
78
  - LICENSE
65
79
  - README.md
66
80
  - lib/fast_mcp.rb
81
+ - lib/generators/fast_mcp/install/install_generator.rb
82
+ - lib/generators/fast_mcp/install/templates/application_resource.rb
83
+ - lib/generators/fast_mcp/install/templates/application_tool.rb
84
+ - lib/generators/fast_mcp/install/templates/fast_mcp_initializer.rb
85
+ - lib/generators/fast_mcp/install/templates/sample_resource.rb
86
+ - lib/generators/fast_mcp/install/templates/sample_tool.rb
67
87
  - lib/mcp/logger.rb
88
+ - lib/mcp/railtie.rb
68
89
  - lib/mcp/resource.rb
69
90
  - lib/mcp/server.rb
70
91
  - lib/mcp/tool.rb
@@ -80,6 +101,7 @@ metadata:
80
101
  homepage_uri: https://github.com/yjacquin/fast_mcp
81
102
  source_code_uri: https://github.com/yjacquin/fast_mcp
82
103
  changelog_uri: https://github.com/yjacquin/fast_mcp/blob/main/CHANGELOG.md
104
+ rubygems_mfa_required: 'true'
83
105
  post_install_message:
84
106
  rdoc_options: []
85
107
  require_paths: