Class: VectorMCP::Server

Inherits:
Object
  • Object
show all
Includes:
Definitions, Capabilities, MessageHandling, Registry
Defined in:
lib/vector_mcp/server.rb,
lib/vector_mcp/server/registry.rb,
lib/vector_mcp/server/capabilities.rb,
lib/vector_mcp/server/message_handling.rb

Overview

The ‘Server` class is the central component for an MCP server implementation. It manages tools, resources, prompts, and handles the MCP message lifecycle.

A server instance is typically initialized, configured with capabilities (tools, resources, prompts), and then run with a chosen transport mechanism (e.g., HttpStream).

Examples:

Creating and running a simple server

server = VectorMCP::Server.new(name: "MySimpleServer", version: "1.0")

server.register_tool(
  name: "echo",
  description: "Echoes back the input string.",
  input_schema: { type: "object", properties: { message: { type: "string" } } }
) do |args|
  args["message"]
end

server.run # Runs with HttpStream transport by default

Defined Under Namespace

Modules: Capabilities, MessageHandling, Registry

Constant Summary collapse

PROTOCOL_VERSION =

The specific version of the Model Context Protocol this server implements.

"2025-11-25"
SUPPORTED_PROTOCOL_VERSIONS =

All protocol versions this server accepts via the MCP-Protocol-Version header.

%w[2025-11-25 2025-03-26 2024-11-05].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MessageHandling

#handle_message, #on_notification, #on_request

Methods included from Capabilities

#clear_prompts_list_changed, #clear_roots_list_changed, #notify_prompts_list_changed, #notify_roots_list_changed, #sampling_config, #server_capabilities, #server_info, #subscribe_prompts

Methods included from Registry

#register, #register_image_prompt, #register_image_resource, #register_image_resource_from_data, #register_image_tool, #register_prompt, #register_resource, #register_root, #register_root_from_path, #register_tool

Constructor Details

#initialize(name_pos = nil, name: nil, version: "0.1.0", **options) ⇒ Server

Initializes a new VectorMCP server.

Parameters:

  • name_pos (String) (defaults to: nil)

    Positional name argument (deprecated, use name: instead).

  • name (String) (defaults to: nil)

    The name of the server.

  • version (String) (defaults to: "0.1.0")

    The version of the server.

  • options (Hash)

    Additional server options:

    • :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).

    • :protocol_version [String] The MCP protocol version to use.

    • :sampling_config [Hash] Configuration for sampling capabilities. Available options:

      • :enabled [Boolean] Whether sampling is enabled (default: true)

      • :methods [Array<String>] Supported sampling methods (default: [“createMessage”])

      • :supports_streaming [Boolean] Whether streaming is supported (default: false)

      • :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)

      • :supports_images [Boolean] Whether image content is supported (default: false)

      • :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)

      • :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)

      • :context_inclusion_methods [Array<String>] Supported context inclusion methods (default: [“none”, “thisServer”])

      • :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)

Raises:

  • (ArgumentError)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/vector_mcp/server.rb', line 97

def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
  raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

  @name = name_pos || name || "UnnamedServer"
  @version = version
  @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
  @logger = VectorMCP.logger_for("server")
  # NOTE: log level should be configured via VectorMCP.configure_logging instead

  @transport = nil
  @tools = {}
  @resources = {}
  @prompts = {}
  @roots = {}
  @request_handlers = {}
  @notification_handlers = {}
  @in_flight_requests = {}
  @prompts_list_changed = false
  @prompt_subscribers = []
  @roots_list_changed = false

  # Configure sampling capabilities
  @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

  # Initialize security components
  @auth_manager = Security::AuthManager.new
  @authorization = Security::Authorization.new
  @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
  @oauth_resource_metadata_url = nil

  # Initialize middleware manager
  @middleware_manager = Middleware::Manager.new

  setup_default_handlers

  @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
end

Instance Attribute Details

#auth_managerObject (readonly)

Returns the value of attribute auth_manager.



74
75
76
# File 'lib/vector_mcp/server.rb', line 74

def auth_manager
  @auth_manager
end

#authorizationObject (readonly)

Returns the value of attribute authorization.



74
75
76
# File 'lib/vector_mcp/server.rb', line 74

def authorization
  @authorization
end

#in_flight_requestsHash (readonly)

Returns A hash tracking currently processing requests, for cancellation purposes.

Returns:

  • (Hash)

    A hash tracking currently processing requests, for cancellation purposes.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#loggerLogger (readonly)

Returns The logger instance for this server.

Returns:

  • (Logger)

    The logger instance for this server.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#middleware_managerObject (readonly)

Returns the value of attribute middleware_manager.



74
75
76
# File 'lib/vector_mcp/server.rb', line 74

def middleware_manager
  @middleware_manager
end

#nameString (readonly)

Returns The name of the server.

Returns:

  • (String)

    The name of the server.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#oauth_resource_metadata_urlObject (readonly)

Returns the value of attribute oauth_resource_metadata_url.



74
75
76
# File 'lib/vector_mcp/server.rb', line 74

def 
  @oauth_resource_metadata_url
end

#promptsHash<String, VectorMCP::Definitions::Prompt> (readonly)

Returns Registered prompts, keyed by name.

Returns:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#protocol_versionString (readonly)

Returns The MCP protocol version this server implements.

Returns:

  • (String)

    The MCP protocol version this server implements.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#resourcesHash<String, VectorMCP::Definitions::Resource> (readonly)

Returns Registered resources, keyed by URI string.

Returns:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#rootsHash<String, VectorMCP::Definitions::Root> (readonly)

Returns Registered roots, keyed by URI string.

Returns:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#security_middlewareObject (readonly)

Returns the value of attribute security_middleware.



74
75
76
# File 'lib/vector_mcp/server.rb', line 74

def security_middleware
  @security_middleware
end

#toolsHash<String, VectorMCP::Definitions::Tool> (readonly)

Returns Registered tools, keyed by name.

Returns:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#transportVectorMCP::Transport::Base?

Returns The active transport instance, if any.

Returns:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

#versionString (readonly)

Returns The version of the server software.

Returns:

  • (String)

    The version of the server software.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/vector_mcp/server.rb', line 62

class Server
  include Definitions # Make Tool, Resource, Prompt, Root structs easily available
  include Registry
  include Capabilities
  include MessageHandling

  # The specific version of the Model Context Protocol this server implements.
  PROTOCOL_VERSION = "2025-11-25"

  # All protocol versions this server accepts via the MCP-Protocol-Version header.
  SUPPORTED_PROTOCOL_VERSIONS = %w[2025-11-25 2025-03-26 2024-11-05].freeze

  attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
              :auth_manager, :authorization, :security_middleware, :middleware_manager, :oauth_resource_metadata_url
  attr_accessor :transport

  # Initializes a new VectorMCP server.
  #
  # @param name_pos [String] Positional name argument (deprecated, use name: instead).
  # @param name [String] The name of the server.
  # @param version [String] The version of the server.
  # @param options [Hash] Additional server options:
  #   - :log_level [Integer] The logging level (Logger::DEBUG, Logger::INFO, etc.).
  #   - :protocol_version [String] The MCP protocol version to use.
  #   - :sampling_config [Hash] Configuration for sampling capabilities. Available options:
  #     - :enabled [Boolean] Whether sampling is enabled (default: true)
  #     - :methods [Array<String>] Supported sampling methods (default: ["createMessage"])
  #     - :supports_streaming [Boolean] Whether streaming is supported (default: false)
  #     - :supports_tool_calls [Boolean] Whether tool calls are supported (default: false)
  #     - :supports_images [Boolean] Whether image content is supported (default: false)
  #     - :max_tokens_limit [Integer, nil] Maximum tokens limit (default: nil, no limit)
  #     - :timeout_seconds [Integer] Default timeout for sampling requests (default: 30)
  #     - :context_inclusion_methods [Array<String>] Supported context inclusion methods
  #       (default: ["none", "thisServer"])
  #     - :model_preferences_supported [Boolean] Whether model preferences are supported (default: true)
  def initialize(name_pos = nil, *, name: nil, version: "0.1.0", **options)
    raise ArgumentError, "Name provided both positionally (#{name_pos}) and as keyword argument (#{name})" if name_pos && name && name_pos != name

    @name = name_pos || name || "UnnamedServer"
    @version = version
    @protocol_version = options[:protocol_version] || PROTOCOL_VERSION
    @logger = VectorMCP.logger_for("server")
    # NOTE: log level should be configured via VectorMCP.configure_logging instead

    @transport = nil
    @tools = {}
    @resources = {}
    @prompts = {}
    @roots = {}
    @request_handlers = {}
    @notification_handlers = {}
    @in_flight_requests = {}
    @prompts_list_changed = false
    @prompt_subscribers = []
    @roots_list_changed = false

    # Configure sampling capabilities
    @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})

    # Initialize security components
    @auth_manager = Security::AuthManager.new
    @authorization = Security::Authorization.new
    @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
    @oauth_resource_metadata_url = nil

    # Initialize middleware manager
    @middleware_manager = Middleware::Manager.new

    setup_default_handlers

    @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
  end

  # --- Server Execution ---

  # Runs the server using the specified transport mechanism.
  #
  # @param transport [:http_stream, VectorMCP::Transport::Base] The transport to use.
  #   Can be the symbol `:http_stream` or an initialized transport instance.
  #   If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.
  # @param options [Hash] Transport-specific options (e.g., `:host`, `:port`).
  #   These are passed to the transport's constructor if a symbol is provided for `transport`.
  # @return [void]
  # @raise [ArgumentError] if an unsupported transport symbol is given.
  def run(transport: :http_stream, **)
    active_transport = case transport
                       when :http_stream
                         begin
                           require_relative "transport/http_stream"
                           VectorMCP::Transport::HttpStream.new(self, **)
                         rescue LoadError => e
                           logger.fatal("HttpStream transport requires additional dependencies.")
                           raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                         end
                       when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                         transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                         transport
                       else
                         logger.fatal("Unsupported transport type: #{transport.inspect}")
                         raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                       end
    self.transport = active_transport
    active_transport.run
  end

  # Returns the MCP server as a Rack application suitable for mounting inside
  # another Rack-based framework (e.g., Rails, Sinatra).
  #
  # Unlike {#run}, this method does NOT start its own HTTP server or block.
  # The returned object responds to `#call(env)` and can be mounted directly:
  #
  #   # config/routes.rb (Rails)
  #   mount MCP_APP => "/mcp"
  #
  # Call `server.transport.stop` on application shutdown to clean up resources.
  #
  # @param options [Hash] Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)
  # @return [VectorMCP::Transport::HttpStream] A Rack-compatible app
  def rack_app(**)
    require_relative "transport/http_stream"
    active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
    self.transport = active_transport
    active_transport
  end

  # --- Security Configuration ---

  # Enable authentication with specified strategy and configuration
  # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
  # @param options [Hash] strategy-specific configuration options
  # @option options [String] :resource_metadata_url OAuth 2.1 protected resource metadata URL
  #   (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport's MCP
  #   endpoint return HTTP 401 with a +WWW-Authenticate: Bearer+ header pointing at this URL,
  #   enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and
  #   initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface
  #   as JSON-RPC +-32401+ errors — existing behavior is preserved for non-OAuth deployments.
  # @return [void]
  def enable_authentication!(strategy: :api_key, **options, &block)
    clear_auth_strategies unless @auth_manager.strategies.empty?
    extract_oauth_metadata!(options)
    @auth_manager.enable!(default_strategy: strategy)
    register_auth_strategy(strategy, options, block || options.delete(:handler))
    @logger.info("Authentication enabled with strategy: #{strategy}")
  end

  # Disable authentication (return to pass-through mode)
  # @return [void]
  def disable_authentication!
    @auth_manager.disable!
    @oauth_resource_metadata_url = nil
    @logger.info("Authentication disabled")
  end

  # Enable authorization with optional policy configuration block
  # @param block [Proc] optional block for configuring authorization policies
  # @return [void]
  def enable_authorization!(&)
    @authorization.enable!
    instance_eval(&) if block_given?
    @logger.info("Authorization enabled")
  end

  # Disable authorization (return to pass-through mode)
  # @return [void]
  def disable_authorization!
    @authorization.disable!
    @logger.info("Authorization disabled")
  end

  # Add authorization policy for tools
  # @param block [Proc] policy block that receives (user, action, tool)
  # @return [void]
  def authorize_tools(&)
    @authorization.add_policy(:tool, &)
  end

  # Add authorization policy for resources
  # @param block [Proc] policy block that receives (user, action, resource)
  # @return [void]
  def authorize_resources(&)
    @authorization.add_policy(:resource, &)
  end

  # Add authorization policy for prompts
  # @param block [Proc] policy block that receives (user, action, prompt)
  # @return [void]
  def authorize_prompts(&)
    @authorization.add_policy(:prompt, &)
  end

  # Add authorization policy for roots
  # @param block [Proc] policy block that receives (user, action, root)
  # @return [void]
  def authorize_roots(&)
    @authorization.add_policy(:root, &)
  end

  # Check if security features are enabled
  # @return [Boolean] true if authentication or authorization is enabled
  def security_enabled?
    @security_middleware.security_enabled?
  end

  # Get current security status for debugging/monitoring
  # @return [Hash] security configuration status
  def security_status
    @security_middleware.security_status
  end

  # --- Middleware Management ---

  # Register middleware for specific hook types
  # @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
  # @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
  # @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
  # @param conditions [Hash] Conditions for when middleware should run
  # @option conditions [Array<String>] :only_operations Only run for these operations
  # @option conditions [Array<String>] :except_operations Don't run for these operations
  # @option conditions [Array<String>] :only_users Only run for these user IDs
  # @option conditions [Array<String>] :except_users Don't run for these user IDs
  # @option conditions [Boolean] :critical If true, errors in this middleware stop execution
  # @example
  #   server.use_middleware(MyMiddleware, :before_tool_call)
  #   server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
  #   server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
  def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
    @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
    @logger.debug("Registered middleware: #{middleware_class.name}")
  end

  # Remove all middleware hooks for a specific class
  # @param middleware_class [Class] Middleware class to remove
  def remove_middleware(middleware_class)
    @middleware_manager.unregister(middleware_class)
    @logger.debug("Removed middleware: #{middleware_class.name}")
  end

  # Get middleware statistics
  # @return [Hash] Statistics about registered middleware
  def middleware_stats
    @middleware_manager.stats
  end

  # Clear all middleware (useful for testing)
  def clear_middleware!
    @middleware_manager.clear!
    @logger.debug("Cleared all middleware")
  end

  private

  # Extract OAuth resource metadata URL from options before they reach strategy constructors
  # @param options [Hash] the options hash (mutated — :resource_metadata_url is removed)
  # @return [void]
  def extract_oauth_metadata!(options)
    @oauth_resource_metadata_url = options.delete(:resource_metadata_url)
    (@oauth_resource_metadata_url)
  end

  # Register the appropriate auth strategy based on the strategy name
  # @param strategy [Symbol] the strategy type
  # @param options [Hash] strategy-specific options
  # @param handler [Proc, nil] custom handler block (for :custom strategy)
  # @return [void]
  def register_auth_strategy(strategy, options, handler)
    case strategy
    when :api_key
      add_api_key_auth(options[:keys] || [], allow_query_params: options[:allow_query_params] || false)
    when :jwt
      add_jwt_auth(options)
    when :custom
      raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler

      add_custom_auth(&handler)
    else
      raise ArgumentError, "Unknown authentication strategy: #{strategy}"
    end
  end

  # Add API key authentication strategy
  # @param keys [Array<String>] array of valid API keys
  # @param allow_query_params [Boolean] whether to accept API keys from query parameters
  # @return [void]
  def add_api_key_auth(keys, allow_query_params: false)
    strategy = Security::Strategies::ApiKey.new(keys: keys, allow_query_params: allow_query_params)
    @auth_manager.add_strategy(:api_key, strategy)
  end

  # Add JWT authentication strategy
  # @param options [Hash] JWT configuration options
  # @return [void]
  def add_jwt_auth(options)
    strategy = Security::Strategies::JwtToken.new(**options)
    @auth_manager.add_strategy(:jwt, strategy)
  end

  # Add custom authentication strategy
  # @param handler [Proc] custom authentication handler block
  # @return [void]
  def add_custom_auth(&)
    strategy = Security::Strategies::Custom.new(&)
    @auth_manager.add_strategy(:custom, strategy)
  end

  # Clear all authentication strategies
  # @return [void]
  def clear_auth_strategies
    @auth_manager.strategies.each_key do |strategy_name|
      @auth_manager.remove_strategy(strategy_name)
    end
  end

  # Emit a warning when the OAuth resource metadata URL is not HTTPS.
  # We don't raise because local development against http://localhost is a valid use case.
  # @param url [String, nil] the configured metadata URL
  # @return [void]
  def (url)
    return if url.nil?
    return if url.start_with?("https://")

    @logger.warn do
      "[SECURITY] resource_metadata_url is not HTTPS (#{url}). " \
        "Use HTTPS in production; plaintext is only acceptable for local development."
    end
  end
end

Instance Method Details

#authorize_promptsvoid

This method returns an undefined value.

Add authorization policy for prompts

Parameters:

  • block (Proc)

    policy block that receives (user, action, prompt)



248
249
250
# File 'lib/vector_mcp/server.rb', line 248

def authorize_prompts(&)
  @authorization.add_policy(:prompt, &)
end

#authorize_resourcesvoid

This method returns an undefined value.

Add authorization policy for resources

Parameters:

  • block (Proc)

    policy block that receives (user, action, resource)



241
242
243
# File 'lib/vector_mcp/server.rb', line 241

def authorize_resources(&)
  @authorization.add_policy(:resource, &)
end

#authorize_rootsvoid

This method returns an undefined value.

Add authorization policy for roots

Parameters:

  • block (Proc)

    policy block that receives (user, action, root)



255
256
257
# File 'lib/vector_mcp/server.rb', line 255

def authorize_roots(&)
  @authorization.add_policy(:root, &)
end

#authorize_toolsvoid

This method returns an undefined value.

Add authorization policy for tools

Parameters:

  • block (Proc)

    policy block that receives (user, action, tool)



234
235
236
# File 'lib/vector_mcp/server.rb', line 234

def authorize_tools(&)
  @authorization.add_policy(:tool, &)
end

#clear_middleware!Object

Clear all middleware (useful for testing)



306
307
308
309
# File 'lib/vector_mcp/server.rb', line 306

def clear_middleware!
  @middleware_manager.clear!
  @logger.debug("Cleared all middleware")
end

#disable_authentication!void

This method returns an undefined value.

Disable authentication (return to pass-through mode)



209
210
211
212
213
# File 'lib/vector_mcp/server.rb', line 209

def disable_authentication!
  @auth_manager.disable!
  @oauth_resource_metadata_url = nil
  @logger.info("Authentication disabled")
end

#disable_authorization!void

This method returns an undefined value.

Disable authorization (return to pass-through mode)



226
227
228
229
# File 'lib/vector_mcp/server.rb', line 226

def disable_authorization!
  @authorization.disable!
  @logger.info("Authorization disabled")
end

#enable_authentication!(strategy: :api_key, **options, &block) ⇒ void

This method returns an undefined value.

Enable authentication with specified strategy and configuration

Parameters:

  • strategy (Symbol) (defaults to: :api_key)

    the authentication strategy (:api_key, :jwt, :custom)

  • options (Hash)

    strategy-specific configuration options

Options Hash (**options):

  • :resource_metadata_url (String)

    OAuth 2.1 protected resource metadata URL (RFC 9728). When provided, unauthenticated requests to the HTTP Stream transport’s MCP endpoint return HTTP 401 with a WWW-Authenticate: Bearer header pointing at this URL, enabling MCP clients (e.g. Claude Desktop) to discover the authorization server and initiate an OAuth 2.1 flow. When omitted (default), auth failures continue to surface as JSON-RPC -32401 errors — existing behavior is preserved for non-OAuth deployments.



199
200
201
202
203
204
205
# File 'lib/vector_mcp/server.rb', line 199

def enable_authentication!(strategy: :api_key, **options, &block)
  clear_auth_strategies unless @auth_manager.strategies.empty?
  extract_oauth_metadata!(options)
  @auth_manager.enable!(default_strategy: strategy)
  register_auth_strategy(strategy, options, block || options.delete(:handler))
  @logger.info("Authentication enabled with strategy: #{strategy}")
end

#enable_authorization!void

This method returns an undefined value.

Enable authorization with optional policy configuration block

Parameters:

  • block (Proc)

    optional block for configuring authorization policies



218
219
220
221
222
# File 'lib/vector_mcp/server.rb', line 218

def enable_authorization!(&)
  @authorization.enable!
  instance_eval(&) if block_given?
  @logger.info("Authorization enabled")
end

#middleware_statsHash

Get middleware statistics

Returns:

  • (Hash)

    Statistics about registered middleware



301
302
303
# File 'lib/vector_mcp/server.rb', line 301

def middleware_stats
  @middleware_manager.stats
end

#rack_appVectorMCP::Transport::HttpStream

Returns the MCP server as a Rack application suitable for mounting inside another Rack-based framework (e.g., Rails, Sinatra).

Unlike #run, this method does NOT start its own HTTP server or block. The returned object responds to ‘#call(env)` and can be mounted directly:

# config/routes.rb (Rails)
mount MCP_APP => "/mcp"

Call ‘server.transport.stop` on application shutdown to clean up resources.

Parameters:

  • options (Hash)

    Transport options (e.g., :session_timeout, :event_retention, :allowed_origins)

Returns:



180
181
182
183
184
185
# File 'lib/vector_mcp/server.rb', line 180

def rack_app(**)
  require_relative "transport/http_stream"
  active_transport = VectorMCP::Transport::HttpStream.new(self, mounted: true, **)
  self.transport = active_transport
  active_transport
end

#remove_middleware(middleware_class) ⇒ Object

Remove all middleware hooks for a specific class

Parameters:

  • middleware_class (Class)

    Middleware class to remove



294
295
296
297
# File 'lib/vector_mcp/server.rb', line 294

def remove_middleware(middleware_class)
  @middleware_manager.unregister(middleware_class)
  @logger.debug("Removed middleware: #{middleware_class.name}")
end

#run(transport: :http_stream) ⇒ void

This method returns an undefined value.

Runs the server using the specified transport mechanism.

Parameters:

  • transport (:http_stream, VectorMCP::Transport::Base) (defaults to: :http_stream)

    The transport to use. Can be the symbol ‘:http_stream` or an initialized transport instance. If `:http_stream` is provided, the method will instantiate the MCP-compliant streamable HTTP transport.

  • options (Hash)

    Transport-specific options (e.g., ‘:host`, `:port`). These are passed to the transport’s constructor if a symbol is provided for ‘transport`.

Raises:

  • (ArgumentError)

    if an unsupported transport symbol is given.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/vector_mcp/server.rb', line 146

def run(transport: :http_stream, **)
  active_transport = case transport
                     when :http_stream
                       begin
                         require_relative "transport/http_stream"
                         VectorMCP::Transport::HttpStream.new(self, **)
                       rescue LoadError => e
                         logger.fatal("HttpStream transport requires additional dependencies.")
                         raise NotImplementedError, "HttpStream transport dependencies not available: #{e.message}"
                       end
                     when VectorMCP::Transport::Base # Allow passing an initialized transport instance
                       transport.server = self if transport.respond_to?(:server=) && transport.server.nil? # Ensure server is set
                       transport
                     else
                       logger.fatal("Unsupported transport type: #{transport.inspect}")
                       raise ArgumentError, "Unsupported transport: #{transport.inspect}"
                     end
  self.transport = active_transport
  active_transport.run
end

#security_enabled?Boolean

Check if security features are enabled

Returns:

  • (Boolean)

    true if authentication or authorization is enabled



261
262
263
# File 'lib/vector_mcp/server.rb', line 261

def security_enabled?
  @security_middleware.security_enabled?
end

#security_statusHash

Get current security status for debugging/monitoring

Returns:

  • (Hash)

    security configuration status



267
268
269
# File 'lib/vector_mcp/server.rb', line 267

def security_status
  @security_middleware.security_status
end

#use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {}) ⇒ Object

Register middleware for specific hook types

Examples:

server.use_middleware(MyMiddleware, :before_tool_call)
server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })

Parameters:

  • middleware_class (Class)

    Middleware class inheriting from VectorMCP::Middleware::Base

  • hooks (Symbol, Array<Symbol>)

    Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])

  • priority (Integer) (defaults to: Middleware::Hook::DEFAULT_PRIORITY)

    Execution priority (lower numbers execute first, default: 100)

  • conditions (Hash) (defaults to: {})

    Conditions for when middleware should run

Options Hash (conditions:):

  • :only_operations (Array<String>)

    Only run for these operations

  • :except_operations (Array<String>)

    Don’t run for these operations

  • :only_users (Array<String>)

    Only run for these user IDs

  • :except_users (Array<String>)

    Don’t run for these user IDs

  • :critical (Boolean)

    If true, errors in this middleware stop execution



287
288
289
290
# File 'lib/vector_mcp/server.rb', line 287

def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
  @middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
  @logger.debug("Registered middleware: #{middleware_class.name}")
end