fast-mcp 1.1.0 → 1.2.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 +4 -4
 - data/CHANGELOG.md +8 -1
 - data/README.md +6 -1
 - data/lib/fast_mcp.rb +3 -0
 - data/lib/generators/fast_mcp/install/templates/fast_mcp_initializer.rb +4 -1
 - data/lib/mcp/transports/authenticated_rack_transport.rb +2 -3
 - data/lib/mcp/transports/rack_transport.rb +40 -42
 - data/lib/mcp/version.rb +1 -1
 - metadata +2 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: e9a99e9fd2c611e1645bfda1cd3d4c924f86b4284e1018e53d8f55c7612c6d48
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: bc9def81c86fb8db4ecdb32091209050a7d860c64d7204158ecfb98e27d01156
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 44ba180b84383e3f0cff990136551101cea90181cb384634abecf72ab96ae0316ab53cf9e05eb09626642f647dac8cf2039edc808339dd11c579cd14a0333d13
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 03b61251ec444d33748a23c1a786f70fea8d54228017b2b1dec0eeae93cb1900afc9af8b19d3fb3c55bbfba26a8e52e2d5ddc266fd6a19f72de692b3ef880382
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -5,6 +5,13 @@ 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.2.0] - UNRELEASED
         
     | 
| 
      
 9 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Security enhancement: Bing only to localhost by default [#44 @yjacquin](https://github.com/yjacquin/fast-mcp/pull/44)
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Prevent AuthenticatedRackMiddleware from blocking other rails routes[#35 @JulianPasquale](https://github.com/yjacquin/fast-mcp/pull/35)
         
     | 
| 
      
 12 
     | 
    
         
            +
            - Stop Forcing reconnections after 30 pings [#42 @zoedsoupe](https://github.com/yjacquin/fast-mcp/pull/42)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       8 
15 
     | 
    
         
             
            ## [1.1.0] - 2025-04-13
         
     | 
| 
       9 
16 
     | 
    
         
             
            ### Added
         
     | 
| 
       10 
17 
     | 
    
         
             
            - Security enhancement: Added DNS rebinding protection by validating Origin headers [#32 @yjacquin](https://github.com/yjacquin/fast-mcp/pull/32/files)
         
     | 
| 
         @@ -60,5 +67,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 
     | 
|
| 
       60 
67 
     | 
    
         
             
            - Resource management with subscription capabilities
         
     | 
| 
       61 
68 
     | 
    
         
             
            - Binary resource support
         
     | 
| 
       62 
69 
     | 
    
         
             
            - Examples with STDIO Transport, HTTP & SSE, Rack app
         
     | 
| 
       63 
     | 
    
         
            -
            - Initialize lifecycle with capabilities 
     | 
| 
      
 70 
     | 
    
         
            +
            - Initialize lifecycle with capabilities
         
     | 
| 
       64 
71 
     | 
    
         
             
            - Comprehensive test suite with RSpec
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -103,6 +103,9 @@ FastMcp.mount_in_rails( 
     | 
|
| 
       103 
103 
     | 
    
         
             
              sse_route: 'sse', # This is the default route for the SSE endpoint
         
     | 
| 
       104 
104 
     | 
    
         
             
              # Add allowed origins below, it defaults to Rails.application.config.hosts
         
     | 
| 
       105 
105 
     | 
    
         
             
              # allowed_origins: ['localhost', '127.0.0.1', 'example.com', /.*\.example\.com/],
         
     | 
| 
      
 106 
     | 
    
         
            +
              # localhost_only: true, # Set to false to allow connections from other hosts
         
     | 
| 
      
 107 
     | 
    
         
            +
              # whitelist specific ips to if you want to run on localhost and allow connections from other IPs
         
     | 
| 
      
 108 
     | 
    
         
            +
              # allowed_ips: ['127.0.0.1', '::1']
         
     | 
| 
       106 
109 
     | 
    
         
             
              # authenticate: true,       # Uncomment to enable authentication
         
     | 
| 
       107 
110 
     | 
    
         
             
              # auth_token: 'your-token' # Required if authenticate: true
         
     | 
| 
       108 
111 
     | 
    
         
             
            ) do |server|
         
     | 
| 
         @@ -320,8 +323,10 @@ The HTTP/SSE transport validates the Origin header on all incoming connections t 
     | 
|
| 
       320 
323 
     | 
    
         | 
| 
       321 
324 
     | 
    
         
             
            ```ruby
         
     | 
| 
       322 
325 
     | 
    
         
             
            # Configure allowed origins (defaults to ['localhost', '127.0.0.1'])
         
     | 
| 
       323 
     | 
    
         
            -
            FastMcp.rack_middleware(app, 
     | 
| 
      
 326 
     | 
    
         
            +
            FastMcp.rack_middleware(app,
         
     | 
| 
       324 
327 
     | 
    
         
             
              allowed_origins: ['localhost', '127.0.0.1', 'your-domain.com', /.*\.your-domain\.com/],
         
     | 
| 
      
 328 
     | 
    
         
            +
              localhost_only: false,
         
     | 
| 
      
 329 
     | 
    
         
            +
              allowed_ips: ['192.168.1.1', '10.0.0.1'],
         
     | 
| 
       325 
330 
     | 
    
         
             
              # other options...
         
     | 
| 
       326 
331 
     | 
    
         
             
            )
         
     | 
| 
       327 
332 
     | 
    
         
             
            ```
         
     | 
    
        data/lib/fast_mcp.rb
    CHANGED
    
    | 
         @@ -141,7 +141,10 @@ module FastMcp 
     | 
|
| 
       141 
141 
     | 
    
         
             
                sse_route = options.delete(:sse_route) || 'sse'
         
     | 
| 
       142 
142 
     | 
    
         
             
                authenticate = options.delete(:authenticate) || false
         
     | 
| 
       143 
143 
     | 
    
         
             
                allowed_origins = options[:allowed_origins] || default_rails_allowed_origins(app)
         
     | 
| 
      
 144 
     | 
    
         
            +
                allowed_ips = options[:allowed_ips] || FastMcp::Transports::RackTransport::DEFAULT_ALLOWED_IPS
         
     | 
| 
       144 
145 
     | 
    
         | 
| 
      
 146 
     | 
    
         
            +
                options[:localhost_only] = Rails.env.local? if options[:localhost_only].nil?
         
     | 
| 
      
 147 
     | 
    
         
            +
                options[:allowed_ips] = allowed_ips
         
     | 
| 
       145 
148 
     | 
    
         
             
                options[:logger] = logger
         
     | 
| 
       146 
149 
     | 
    
         
             
                options[:allowed_origins] = allowed_origins
         
     | 
| 
       147 
150 
     | 
    
         | 
| 
         @@ -22,7 +22,10 @@ FastMcp.mount_in_rails( 
     | 
|
| 
       22 
22 
     | 
    
         
             
              messages_route: 'messages', # This is the default route for the messages endpoint
         
     | 
| 
       23 
23 
     | 
    
         
             
              sse_route: 'sse' # This is the default route for the SSE endpoint
         
     | 
| 
       24 
24 
     | 
    
         
             
              # Add allowed origins below, it defaults to Rails.application.config.hosts
         
     | 
| 
       25 
     | 
    
         
            -
              # allowed_origins: ['localhost', '127.0.0.1', 'example.com', /.*\.example\.com/],
         
     | 
| 
      
 25 
     | 
    
         
            +
              # allowed_origins: ['localhost', '127.0.0.1', '[::1]', 'example.com', /.*\.example\.com/],
         
     | 
| 
      
 26 
     | 
    
         
            +
              # localhost_only: true, # Set to false to allow connections from other hosts
         
     | 
| 
      
 27 
     | 
    
         
            +
              # whitelist specific ips to if you want to run on localhost and allow connections from other IPs
         
     | 
| 
      
 28 
     | 
    
         
            +
              # allowed_ips: ['127.0.0.1', '::1']
         
     | 
| 
       26 
29 
     | 
    
         
             
              # authenticate: true,       # Uncomment to enable authentication
         
     | 
| 
       27 
30 
     | 
    
         
             
              # auth_token: 'your-token', # Required if authenticate: true
         
     | 
| 
       28 
31 
     | 
    
         
             
            ) do |server|
         
     | 
| 
         @@ -14,9 +14,7 @@ module FastMcp 
     | 
|
| 
       14 
14 
     | 
    
         
             
                    @auth_enabled = !@auth_token.nil?
         
     | 
| 
       15 
15 
     | 
    
         
             
                  end
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
                  def  
     | 
| 
       18 
     | 
    
         
            -
                    request = Rack::Request.new(env)
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def handle_mcp_request(request, env)
         
     | 
| 
       20 
18 
     | 
    
         
             
                    if auth_enabled? && !exempt_from_auth?(request.path)
         
     | 
| 
       21 
19 
     | 
    
         
             
                      auth_header = request.env["HTTP_#{@auth_header_name.upcase.gsub('-', '_')}"]
         
     | 
| 
       22 
20 
     | 
    
         
             
                      token = auth_header&.gsub('Bearer ', '')
         
     | 
| 
         @@ -42,6 +40,7 @@ module FastMcp 
     | 
|
| 
       42 
40 
     | 
    
         
             
                  end
         
     | 
| 
       43 
41 
     | 
    
         | 
| 
       44 
42 
     | 
    
         
             
                  def unauthorized_response(request)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @logger.error('Unauthorized request: Invalid or missing authentication token')
         
     | 
| 
       45 
44 
     | 
    
         
             
                    body = JSON.generate(
         
     | 
| 
       46 
45 
     | 
    
         
             
                      {
         
     | 
| 
       47 
46 
     | 
    
         
             
                        jsonrpc: '2.0',
         
     | 
| 
         @@ -11,9 +11,10 @@ module FastMcp 
     | 
|
| 
       11 
11 
     | 
    
         
             
                # This transport can be mounted in any Rack-compatible web framework
         
     | 
| 
       12 
12 
     | 
    
         
             
                class RackTransport < BaseTransport # rubocop:disable Metrics/ClassLength
         
     | 
| 
       13 
13 
     | 
    
         
             
                  DEFAULT_PATH_PREFIX = '/mcp'
         
     | 
| 
       14 
     | 
    
         
            -
                  DEFAULT_ALLOWED_ORIGINS = ['localhost', '127.0.0.1'].freeze
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                  attr_reader :app, :path_prefix, :sse_clients, :messages_route, :sse_route, :allowed_origins
         
     | 
| 
      
 14 
     | 
    
         
            +
                  DEFAULT_ALLOWED_ORIGINS = ['localhost', '127.0.0.1', '[::1]'].freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
                  DEFAULT_ALLOWED_IPS = ['127.0.0.1', '::1'].freeze
         
     | 
| 
      
 16 
     | 
    
         
            +
                  attr_reader :app, :path_prefix, :sse_clients, :messages_route, :sse_route, :allowed_origins, :localhost_only,
         
     | 
| 
      
 17 
     | 
    
         
            +
                              :allowed_ips
         
     | 
| 
       17 
18 
     | 
    
         | 
| 
       18 
19 
     | 
    
         
             
                  def initialize(app, server, options = {}, &_block)
         
     | 
| 
       19 
20 
     | 
    
         
             
                    super(server, logger: options[:logger])
         
     | 
| 
         @@ -22,6 +23,8 @@ module FastMcp 
     | 
|
| 
       22 
23 
     | 
    
         
             
                    @messages_route = options[:messages_route] || 'messages'
         
     | 
| 
       23 
24 
     | 
    
         
             
                    @sse_route = options[:sse_route] || 'sse'
         
     | 
| 
       24 
25 
     | 
    
         
             
                    @allowed_origins = options[:allowed_origins] || DEFAULT_ALLOWED_ORIGINS
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @localhost_only = options.fetch(:localhost_only, true) # Default to localhost-only mode
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @allowed_ips = options[:allowed_ips] || DEFAULT_ALLOWED_IPS
         
     | 
| 
       25 
28 
     | 
    
         
             
                    @sse_clients = {}
         
     | 
| 
       26 
29 
     | 
    
         
             
                    @running = false
         
     | 
| 
       27 
30 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -105,6 +108,18 @@ module FastMcp 
     | 
|
| 
       105 
108 
     | 
    
         | 
| 
       106 
109 
     | 
    
         
             
                  private
         
     | 
| 
       107 
110 
     | 
    
         | 
| 
      
 111 
     | 
    
         
            +
                  def validate_client_ip(request)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    client_ip = request.ip
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                    # Check if we're in localhost-only mode
         
     | 
| 
      
 115 
     | 
    
         
            +
                    if @localhost_only && !@allowed_ips.include?(client_ip)
         
     | 
| 
      
 116 
     | 
    
         
            +
                      @logger.warn("Blocked connection from non-localhost IP: #{client_ip}")
         
     | 
| 
      
 117 
     | 
    
         
            +
                      return false
         
     | 
| 
      
 118 
     | 
    
         
            +
                    end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    true
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
       108 
123 
     | 
    
         
             
                  # Validate the Origin header to prevent DNS rebinding attacks
         
     | 
| 
       109 
124 
     | 
    
         
             
                  def validate_origin(request, env)
         
     | 
| 
       110 
125 
     | 
    
         
             
                    origin = env['HTTP_ORIGIN']
         
     | 
| 
         @@ -117,7 +132,7 @@ module FastMcp 
     | 
|
| 
       117 
132 
     | 
    
         | 
| 
       118 
133 
     | 
    
         
             
                    # If we have a hostname and allowed_origins is not empty
         
     | 
| 
       119 
134 
     | 
    
         
             
                    if hostname && !allowed_origins.empty?
         
     | 
| 
       120 
     | 
    
         
            -
                      @logger. 
     | 
| 
      
 135 
     | 
    
         
            +
                      @logger.debug("Validating origin: #{hostname}")
         
     | 
| 
       121 
136 
     | 
    
         | 
| 
       122 
137 
     | 
    
         
             
                      # Check if the hostname matches any allowed origin
         
     | 
| 
       123 
138 
     | 
    
         
             
                      is_allowed = allowed_origins.any? do |allowed|
         
     | 
| 
         @@ -160,20 +175,11 @@ module FastMcp 
     | 
|
| 
       160 
175 
     | 
    
         | 
| 
       161 
176 
     | 
    
         
             
                  # Handle MCP-specific requests
         
     | 
| 
       162 
177 
     | 
    
         
             
                  def handle_mcp_request(request, env)
         
     | 
| 
      
 178 
     | 
    
         
            +
                    # Validate client IP to ensure it's connecting from allowed sources
         
     | 
| 
      
 179 
     | 
    
         
            +
                    return forbidden_response('Forbidden: Remote IP not allowed') unless validate_client_ip(request)
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
       163 
181 
     | 
    
         
             
                    # Validate Origin header to prevent DNS rebinding attacks
         
     | 
| 
       164 
     | 
    
         
            -
                    unless validate_origin(request, env)
         
     | 
| 
       165 
     | 
    
         
            -
                      return [403, { 'Content-Type' => 'application/json' },
         
     | 
| 
       166 
     | 
    
         
            -
                              [JSON.generate(
         
     | 
| 
       167 
     | 
    
         
            -
                                {
         
     | 
| 
       168 
     | 
    
         
            -
                                  jsonrpc: '2.0',
         
     | 
| 
       169 
     | 
    
         
            -
                                  error: {
         
     | 
| 
       170 
     | 
    
         
            -
                                    code: -32_600,
         
     | 
| 
       171 
     | 
    
         
            -
                                    message: 'Forbidden: Origin validation failed'
         
     | 
| 
       172 
     | 
    
         
            -
                                  },
         
     | 
| 
       173 
     | 
    
         
            -
                                  id: nil
         
     | 
| 
       174 
     | 
    
         
            -
                                }
         
     | 
| 
       175 
     | 
    
         
            -
                              )]]
         
     | 
| 
       176 
     | 
    
         
            -
                    end
         
     | 
| 
      
 182 
     | 
    
         
            +
                    return forbidden_response('Forbidden: Origin validation failed') unless validate_origin(request, env)
         
     | 
| 
       177 
183 
     | 
    
         | 
| 
       178 
184 
     | 
    
         
             
                    subpath = request.path[@path_prefix.length..]
         
     | 
| 
       179 
185 
     | 
    
         
             
                    @logger.info("MCP request subpath: '#{subpath.inspect}'")
         
     | 
| 
         @@ -190,6 +196,20 @@ module FastMcp 
     | 
|
| 
       190 
196 
     | 
    
         
             
                    end
         
     | 
| 
       191 
197 
     | 
    
         
             
                  end
         
     | 
| 
       192 
198 
     | 
    
         | 
| 
      
 199 
     | 
    
         
            +
                  def forbidden_response(message)
         
     | 
| 
      
 200 
     | 
    
         
            +
                    [403, { 'Content-Type' => 'application/json' },
         
     | 
| 
      
 201 
     | 
    
         
            +
                     [JSON.generate(
         
     | 
| 
      
 202 
     | 
    
         
            +
                       {
         
     | 
| 
      
 203 
     | 
    
         
            +
                         jsonrpc: '2.0',
         
     | 
| 
      
 204 
     | 
    
         
            +
                         error: {
         
     | 
| 
      
 205 
     | 
    
         
            +
                           code: -32_600,
         
     | 
| 
      
 206 
     | 
    
         
            +
                           message: message
         
     | 
| 
      
 207 
     | 
    
         
            +
                         },
         
     | 
| 
      
 208 
     | 
    
         
            +
                         id: nil
         
     | 
| 
      
 209 
     | 
    
         
            +
                       }
         
     | 
| 
      
 210 
     | 
    
         
            +
                     )]]
         
     | 
| 
      
 211 
     | 
    
         
            +
                  end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
       193 
213 
     | 
    
         
             
                  # Return a 404 endpoint not found response
         
     | 
| 
       194 
214 
     | 
    
         
             
                  def endpoint_not_found_response
         
     | 
| 
       195 
215 
     | 
    
         
             
                    [404, { 'Content-Type' => 'application/json' },
         
     | 
| 
         @@ -286,9 +306,6 @@ module FastMcp 
     | 
|
| 
       286 
306 
     | 
    
         
             
                    browser_type = detect_browser_type(user_agent)
         
     | 
| 
       287 
307 
     | 
    
         
             
                    @logger.info("Client connection from: #{user_agent} (#{browser_type})")
         
     | 
| 
       288 
308 
     | 
    
         | 
| 
       289 
     | 
    
         
            -
                    # Handle MCP inspector with fixed client ID
         
     | 
| 
       290 
     | 
    
         
            -
                    @logger.info("MCP Inspector detected, using fixed client ID: #{client_id}") if mcp_inspector?(user_agent, env)
         
     | 
| 
       291 
     | 
    
         
            -
             
     | 
| 
       292 
309 
     | 
    
         
             
                    # Handle reconnection
         
     | 
| 
       293 
310 
     | 
    
         
             
                    if client_id && @sse_clients.key?(client_id)
         
     | 
| 
       294 
311 
     | 
    
         
             
                      handle_client_reconnection(client_id, browser_type)
         
     | 
| 
         @@ -321,11 +338,6 @@ module FastMcp 
     | 
|
| 
       321 
338 
     | 
    
         
             
                    end
         
     | 
| 
       322 
339 
     | 
    
         
             
                  end
         
     | 
| 
       323 
340 
     | 
    
         | 
| 
       324 
     | 
    
         
            -
                  # Check if client is MCP inspector
         
     | 
| 
       325 
     | 
    
         
            -
                  def mcp_inspector?(user_agent, env)
         
     | 
| 
       326 
     | 
    
         
            -
                    user_agent.include?('mcp-inspector') || (env['mcp.client_name'] == 'mcp-inspector')
         
     | 
| 
       327 
     | 
    
         
            -
                  end
         
     | 
| 
       328 
     | 
    
         
            -
             
     | 
| 
       329 
341 
     | 
    
         
             
                  # Handle client reconnection
         
     | 
| 
       330 
342 
     | 
    
         
             
                  def handle_client_reconnection(client_id, browser_type)
         
     | 
| 
       331 
343 
     | 
    
         
             
                    @logger.info("Client #{client_id} is reconnecting (#{browser_type})")
         
     | 
| 
         @@ -405,13 +417,11 @@ module FastMcp 
     | 
|
| 
       405 
417 
     | 
    
         
             
                    @logger.info("Starting keep-alive loop for SSE connection #{client_id}")
         
     | 
| 
       406 
418 
     | 
    
         
             
                    ping_count = 0
         
     | 
| 
       407 
419 
     | 
    
         
             
                    ping_interval = 1 # Send a ping every 1 second
         
     | 
| 
       408 
     | 
    
         
            -
                    max_ping_count = 30 # Reset connection after 30 pings (about 30 seconds)
         
     | 
| 
       409 
420 
     | 
    
         
             
                    @running = true
         
     | 
| 
       410 
421 
     | 
    
         | 
| 
       411 
422 
     | 
    
         
             
                    while @running && !io.closed?
         
     | 
| 
       412 
423 
     | 
    
         
             
                      begin
         
     | 
| 
       413 
     | 
    
         
            -
                        ping_count = send_keep_alive_ping(io, client_id, ping_count 
     | 
| 
       414 
     | 
    
         
            -
                        break if ping_count >= max_ping_count
         
     | 
| 
      
 424 
     | 
    
         
            +
                        ping_count = send_keep_alive_ping(io, client_id, ping_count)
         
     | 
| 
       415 
425 
     | 
    
         | 
| 
       416 
426 
     | 
    
         
             
                        sleep ping_interval
         
     | 
| 
       417 
427 
     | 
    
         
             
                      rescue Errno::EPIPE, IOError => e
         
     | 
| 
         @@ -423,7 +433,7 @@ module FastMcp 
     | 
|
| 
       423 
433 
     | 
    
         
             
                  end
         
     | 
| 
       424 
434 
     | 
    
         | 
| 
       425 
435 
     | 
    
         
             
                  # Send a keep-alive ping and return the updated ping count
         
     | 
| 
       426 
     | 
    
         
            -
                  def send_keep_alive_ping(io, client_id, ping_count 
     | 
| 
      
 436 
     | 
    
         
            +
                  def send_keep_alive_ping(io, client_id, ping_count)
         
     | 
| 
       427 
437 
     | 
    
         
             
                    ping_count += 1
         
     | 
| 
       428 
438 
     | 
    
         | 
| 
       429 
439 
     | 
    
         
             
                    # Send a comment before each ping to keep the connection alive
         
     | 
| 
         @@ -436,12 +446,6 @@ module FastMcp 
     | 
|
| 
       436 
446 
     | 
    
         
             
                      send_ping_event(io)
         
     | 
| 
       437 
447 
     | 
    
         
             
                    end
         
     | 
| 
       438 
448 
     | 
    
         | 
| 
       439 
     | 
    
         
            -
                    # If we've reached the max ping count, force a reconnection
         
     | 
| 
       440 
     | 
    
         
            -
                    if ping_count >= max_ping_count
         
     | 
| 
       441 
     | 
    
         
            -
                      @logger.debug("Reached max ping count (#{max_ping_count}) for client #{client_id}, forcing reconnection")
         
     | 
| 
       442 
     | 
    
         
            -
                      send_reconnect_event(io)
         
     | 
| 
       443 
     | 
    
         
            -
                    end
         
     | 
| 
       444 
     | 
    
         
            -
             
     | 
| 
       445 
449 
     | 
    
         
             
                    ping_count
         
     | 
| 
       446 
450 
     | 
    
         
             
                  end
         
     | 
| 
       447 
451 
     | 
    
         | 
| 
         @@ -450,18 +454,12 @@ module FastMcp 
     | 
|
| 
       450 
454 
     | 
    
         
             
                    ping_message = {
         
     | 
| 
       451 
455 
     | 
    
         
             
                      jsonrpc: '2.0',
         
     | 
| 
       452 
456 
     | 
    
         
             
                      method: 'ping',
         
     | 
| 
       453 
     | 
    
         
            -
                      id:  
     | 
| 
      
 457 
     | 
    
         
            +
                      id: rand(1_000_000)
         
     | 
| 
       454 
458 
     | 
    
         
             
                    }
         
     | 
| 
       455 
459 
     | 
    
         
             
                    io.write("event: ping\ndata: #{JSON.generate(ping_message)}\n\n")
         
     | 
| 
       456 
460 
     | 
    
         
             
                    io.flush
         
     | 
| 
       457 
461 
     | 
    
         
             
                  end
         
     | 
| 
       458 
462 
     | 
    
         | 
| 
       459 
     | 
    
         
            -
                  # Send a reconnect event
         
     | 
| 
       460 
     | 
    
         
            -
                  def send_reconnect_event(io)
         
     | 
| 
       461 
     | 
    
         
            -
                    io.write("event: reconnect\ndata: {\"reason\":\"timeout prevention\"}\n\n")
         
     | 
| 
       462 
     | 
    
         
            -
                    io.flush
         
     | 
| 
       463 
     | 
    
         
            -
                  end
         
     | 
| 
       464 
     | 
    
         
            -
             
     | 
| 
       465 
463 
     | 
    
         
             
                  # Clean up SSE connection
         
     | 
| 
       466 
464 
     | 
    
         
             
                  def cleanup_sse_connection(client_id, io)
         
     | 
| 
       467 
465 
     | 
    
         
             
                    @logger.info("Cleaning up SSE connection for client #{client_id}")
         
     | 
    
        data/lib/mcp/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: fast-mcp
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.2.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-04- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2025-04-21 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: base64
         
     |