vector_mcp 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "jwt"
5
+ rescue LoadError
6
+ # JWT gem is optional - will raise error when trying to use JWT strategy
7
+ end
8
+
9
+ module VectorMCP
10
+ module Security
11
+ module Strategies
12
+ # JWT Token authentication strategy
13
+ # Provides stateless authentication using JSON Web Tokens
14
+ class JwtToken
15
+ attr_reader :secret, :algorithm, :options
16
+
17
+ # Initialize JWT strategy
18
+ # @param secret [String] the secret key for JWT verification
19
+ # @param algorithm [String] the JWT algorithm (default: HS256)
20
+ # @param options [Hash] additional JWT verification options
21
+ def initialize(secret:, algorithm: "HS256", **options)
22
+ raise LoadError, "JWT gem is required for JWT authentication strategy" unless defined?(JWT)
23
+
24
+ @secret = secret
25
+ @algorithm = algorithm
26
+ @options = {
27
+ algorithm: @algorithm,
28
+ verify_expiration: true,
29
+ verify_iat: true,
30
+ verify_iss: false,
31
+ verify_aud: false
32
+ }.merge(options)
33
+ end
34
+
35
+ # Authenticate a request using JWT token
36
+ # @param request [Hash] the request object
37
+ # @return [Hash, false] decoded JWT payload or false if authentication failed
38
+ def authenticate(request)
39
+ token = extract_token(request)
40
+ return false unless token
41
+
42
+ begin
43
+ decoded = JWT.decode(token, @secret, true, @options)
44
+ payload = decoded[0] # First element is the payload
45
+ headers = decoded[1] # Second element is the headers
46
+
47
+ # Return user info from JWT payload
48
+ {
49
+ **payload,
50
+ strategy: "jwt",
51
+ authenticated_at: Time.now,
52
+ jwt_headers: headers
53
+ }
54
+ rescue JWT::ExpiredSignature, JWT::InvalidIssuerError,
55
+ JWT::DecodeError, StandardError
56
+ false # Token validation failed
57
+ end
58
+ end
59
+
60
+ # Generate a JWT token (utility method for testing/development)
61
+ # @param payload [Hash] the payload to encode
62
+ # @param expires_in [Integer] expiration time in seconds from now
63
+ # @return [String] the generated JWT token
64
+ def generate_token(payload, expires_in: 3600)
65
+ exp_payload = payload.merge(
66
+ exp: Time.now.to_i + expires_in,
67
+ iat: Time.now.to_i
68
+ )
69
+ JWT.encode(exp_payload, @secret, @algorithm)
70
+ end
71
+
72
+ # Check if JWT gem is available
73
+ # @return [Boolean] true if JWT gem is loaded
74
+ def self.available?
75
+ defined?(JWT)
76
+ end
77
+
78
+ private
79
+
80
+ # Extract JWT token from request
81
+ # @param request [Hash] the request object
82
+ # @return [String, nil] the extracted token
83
+ def extract_token(request)
84
+ headers = request[:headers] || request["headers"] || {}
85
+ params = request[:params] || request["params"] || {}
86
+
87
+ extract_from_auth_header(headers) ||
88
+ extract_from_jwt_header(headers) ||
89
+ extract_from_params(params)
90
+ end
91
+
92
+ # Extract token from Authorization header
93
+ # @param headers [Hash] request headers
94
+ # @return [String, nil] the token if found
95
+ def extract_from_auth_header(headers)
96
+ auth_header = headers["Authorization"] || headers["authorization"]
97
+ return nil unless auth_header&.start_with?("Bearer ")
98
+
99
+ auth_header[7..] # Remove 'Bearer ' prefix
100
+ end
101
+
102
+ # Extract token from custom JWT header
103
+ # @param headers [Hash] request headers
104
+ # @return [String, nil] the token if found
105
+ def extract_from_jwt_header(headers)
106
+ headers["X-JWT-Token"] || headers["x-jwt-token"]
107
+ end
108
+
109
+ # Extract token from query parameters
110
+ # @param params [Hash] request parameters
111
+ # @return [String, nil] the token if found
112
+ def extract_from_params(params)
113
+ params["jwt_token"] || params["token"]
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Security namespace for VectorMCP
4
+ # Contains authentication, authorization, and security middleware components
5
+
6
+ require_relative "security/auth_manager"
7
+ require_relative "security/authorization"
8
+ require_relative "security/middleware"
9
+ require_relative "security/session_context"
10
+ require_relative "security/strategies/api_key"
11
+ require_relative "security/strategies/jwt_token"
12
+ require_relative "security/strategies/custom"
13
+
14
+ module VectorMCP
15
+ # Security components for VectorMCP servers
16
+ # Provides opt-in authentication and authorization
17
+ module Security
18
+ # Get default authentication manager
19
+ # @return [AuthManager] a new authentication manager instance
20
+ def self.auth_manager
21
+ AuthManager.new
22
+ end
23
+
24
+ # Get default authorization manager
25
+ # @return [Authorization] a new authorization manager instance
26
+ def self.authorization
27
+ Authorization.new
28
+ end
29
+
30
+ # Create security middleware with default components
31
+ # @param auth_manager [AuthManager] optional custom auth manager
32
+ # @param authorization [Authorization] optional custom authorization
33
+ # @return [Middleware] configured security middleware
34
+ def self.middleware(auth_manager: nil, authorization: nil)
35
+ auth_manager ||= self.auth_manager
36
+ authorization ||= self.authorization
37
+ Middleware.new(auth_manager, authorization)
38
+ end
39
+
40
+ # Check if JWT support is available
41
+ # @return [Boolean] true if JWT gem is loaded
42
+ def self.jwt_available?
43
+ Strategies::JwtToken.available?
44
+ end
45
+ end
46
+ end
@@ -12,6 +12,13 @@ require_relative "util" # Needed if not using Handlers::Core
12
12
  require_relative "server/registry"
13
13
  require_relative "server/capabilities"
14
14
  require_relative "server/message_handling"
15
+ require_relative "security/auth_manager"
16
+ require_relative "security/authorization"
17
+ require_relative "security/middleware"
18
+ require_relative "security/session_context"
19
+ require_relative "security/strategies/api_key"
20
+ require_relative "security/strategies/jwt_token"
21
+ require_relative "security/strategies/custom"
15
22
 
16
23
  module VectorMCP
17
24
  # The `Server` class is the central component for an MCP server implementation.
@@ -62,7 +69,8 @@ module VectorMCP
62
69
  # The specific version of the Model Context Protocol this server implements.
63
70
  PROTOCOL_VERSION = "2024-11-05"
64
71
 
65
- attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests
72
+ attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
73
+ :auth_manager, :authorization, :security_middleware
66
74
  attr_accessor :transport
67
75
 
68
76
  # Initializes a new VectorMCP server.
@@ -108,6 +116,11 @@ module VectorMCP
108
116
  # Configure sampling capabilities
109
117
  @sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})
110
118
 
119
+ # Initialize security components
120
+ @auth_manager = Security::AuthManager.new
121
+ @authorization = Security::Authorization.new
122
+ @security_middleware = Security::Middleware.new(@auth_manager, @authorization)
123
+
111
124
  setup_default_handlers
112
125
 
113
126
  @logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
@@ -148,6 +161,133 @@ module VectorMCP
148
161
  self.transport = active_transport
149
162
  active_transport.run
150
163
  end
164
+
165
+ # --- Security Configuration ---
166
+
167
+ # Enable authentication with specified strategy and configuration
168
+ # @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
169
+ # @param options [Hash] strategy-specific configuration options
170
+ # @return [void]
171
+ def enable_authentication!(strategy: :api_key, **options, &block)
172
+ # Clear existing strategies when switching to a new configuration
173
+ clear_auth_strategies unless @auth_manager.strategies.empty?
174
+
175
+ @auth_manager.enable!(default_strategy: strategy)
176
+
177
+ case strategy
178
+ when :api_key
179
+ add_api_key_auth(options[:keys] || [])
180
+ when :jwt
181
+ add_jwt_auth(options)
182
+ when :custom
183
+ handler = block || options[:handler]
184
+ raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler
185
+
186
+ add_custom_auth(&handler)
187
+
188
+ else
189
+ raise ArgumentError, "Unknown authentication strategy: #{strategy}"
190
+ end
191
+
192
+ @logger.info("Authentication enabled with strategy: #{strategy}")
193
+ end
194
+
195
+ # Disable authentication (return to pass-through mode)
196
+ # @return [void]
197
+ def disable_authentication!
198
+ @auth_manager.disable!
199
+ @logger.info("Authentication disabled")
200
+ end
201
+
202
+ # Enable authorization with optional policy configuration block
203
+ # @param block [Proc] optional block for configuring authorization policies
204
+ # @return [void]
205
+ def enable_authorization!(&)
206
+ @authorization.enable!
207
+ instance_eval(&) if block_given?
208
+ @logger.info("Authorization enabled")
209
+ end
210
+
211
+ # Disable authorization (return to pass-through mode)
212
+ # @return [void]
213
+ def disable_authorization!
214
+ @authorization.disable!
215
+ @logger.info("Authorization disabled")
216
+ end
217
+
218
+ # Add authorization policy for tools
219
+ # @param block [Proc] policy block that receives (user, action, tool)
220
+ # @return [void]
221
+ def authorize_tools(&)
222
+ @authorization.add_policy(:tool, &)
223
+ end
224
+
225
+ # Add authorization policy for resources
226
+ # @param block [Proc] policy block that receives (user, action, resource)
227
+ # @return [void]
228
+ def authorize_resources(&)
229
+ @authorization.add_policy(:resource, &)
230
+ end
231
+
232
+ # Add authorization policy for prompts
233
+ # @param block [Proc] policy block that receives (user, action, prompt)
234
+ # @return [void]
235
+ def authorize_prompts(&)
236
+ @authorization.add_policy(:prompt, &)
237
+ end
238
+
239
+ # Add authorization policy for roots
240
+ # @param block [Proc] policy block that receives (user, action, root)
241
+ # @return [void]
242
+ def authorize_roots(&)
243
+ @authorization.add_policy(:root, &)
244
+ end
245
+
246
+ # Check if security features are enabled
247
+ # @return [Boolean] true if authentication or authorization is enabled
248
+ def security_enabled?
249
+ @security_middleware.security_enabled?
250
+ end
251
+
252
+ # Get current security status for debugging/monitoring
253
+ # @return [Hash] security configuration status
254
+ def security_status
255
+ @security_middleware.security_status
256
+ end
257
+
258
+ private
259
+
260
+ # Add API key authentication strategy
261
+ # @param keys [Array<String>] array of valid API keys
262
+ # @return [void]
263
+ def add_api_key_auth(keys)
264
+ strategy = Security::Strategies::ApiKey.new(keys: keys)
265
+ @auth_manager.add_strategy(:api_key, strategy)
266
+ end
267
+
268
+ # Add JWT authentication strategy
269
+ # @param options [Hash] JWT configuration options
270
+ # @return [void]
271
+ def add_jwt_auth(options)
272
+ strategy = Security::Strategies::JwtToken.new(**options)
273
+ @auth_manager.add_strategy(:jwt, strategy)
274
+ end
275
+
276
+ # Add custom authentication strategy
277
+ # @param handler [Proc] custom authentication handler block
278
+ # @return [void]
279
+ def add_custom_auth(&)
280
+ strategy = Security::Strategies::Custom.new(&)
281
+ @auth_manager.add_strategy(:custom, strategy)
282
+ end
283
+
284
+ # Clear all authentication strategies
285
+ # @return [void]
286
+ def clear_auth_strategies
287
+ @auth_manager.strategies.each_key do |strategy_name|
288
+ @auth_manager.remove_strategy(strategy_name)
289
+ end
290
+ end
151
291
  end
152
292
 
153
293
  module Transport
@@ -2,5 +2,5 @@
2
2
 
3
3
  module VectorMCP
4
4
  # The current version of the VectorMCP gem.
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.1"
6
6
  end
data/lib/vector_mcp.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "vector_mcp/image_util"
12
12
  require_relative "vector_mcp/handlers/core"
13
13
  require_relative "vector_mcp/transport/stdio"
14
14
  # require_relative "vector_mcp/transport/sse" # Load on demand to avoid async dependencies
15
+ require_relative "vector_mcp/logging"
15
16
  require_relative "vector_mcp/server"
16
17
 
17
18
  # The VectorMCP module provides a full-featured, opinionated Ruby implementation
@@ -47,10 +48,42 @@ module VectorMCP
47
48
  # @return [Logger] the shared logger instance for the library.
48
49
  @logger = Logger.new($stderr, level: Logger::INFO, progname: "VectorMCP")
49
50
 
51
+ # @return [VectorMCP::Logging::Core] the new structured logging system
52
+ @logging_core = nil
53
+
50
54
  class << self
51
55
  # @!attribute [r] logger
52
- # @return [Logger] the shared logger instance for the library.
53
- attr_reader :logger
56
+ # @return [Logger] the shared logger instance for the library (legacy compatibility).
57
+ def logger
58
+ if @logging_core
59
+ @logging_core.legacy_logger
60
+ else
61
+ @logger
62
+ end
63
+ end
64
+
65
+ # Initialize the new structured logging system
66
+ # @param config [Hash, VectorMCP::Logging::Configuration] logging configuration
67
+ # @return [VectorMCP::Logging::Core] the logging core instance
68
+ def setup_logging(config = {})
69
+ configuration = config.is_a?(Logging::Configuration) ? config : Logging::Configuration.new(config)
70
+ @logging_core = Logging::Core.new(configuration)
71
+ end
72
+
73
+ # Get a component-specific logger
74
+ # @param component [String, Symbol] the component name
75
+ # @return [VectorMCP::Logging::Component] component logger
76
+ def logger_for(component)
77
+ setup_logging unless @logging_core
78
+ @logging_core.logger_for(component)
79
+ end
80
+
81
+ # Configure the logging system
82
+ # @yield [VectorMCP::Logging::Configuration] configuration block
83
+ def configure_logging(&)
84
+ setup_logging unless @logging_core
85
+ @logging_core.configure(&)
86
+ end
54
87
 
55
88
  # Creates a new {VectorMCP::Server} instance. This is a **thin wrapper** around
56
89
  # `VectorMCP::Server.new`; it exists purely for syntactic sugar so you can write
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vector_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Bayona
@@ -79,8 +79,38 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '6.4'
82
- description: Server-side tools for implementing the Model Context Protocol in Ruby
83
- applications
82
+ - !ruby/object:Gem::Dependency
83
+ name: rack
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: jwt
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.7'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.7'
110
+ description: A Ruby gem implementing the Model Context Protocol (MCP) server-side
111
+ specification. Provides a framework for creating MCP servers that expose tools,
112
+ resources, prompts, and roots to LLM clients with comprehensive security features,
113
+ structured logging, and production-ready capabilities.
84
114
  email:
85
115
  - bayona.sergio@gmail.com
86
116
  executables:
@@ -89,6 +119,7 @@ executables:
89
119
  extensions: []
90
120
  extra_rdoc_files: []
91
121
  files:
122
+ - CHANGELOG.md
92
123
  - LICENSE.txt
93
124
  - README.md
94
125
  - bin/console
@@ -98,8 +129,29 @@ files:
98
129
  - lib/vector_mcp/errors.rb
99
130
  - lib/vector_mcp/handlers/core.rb
100
131
  - lib/vector_mcp/image_util.rb
132
+ - lib/vector_mcp/logging.rb
133
+ - lib/vector_mcp/logging/component.rb
134
+ - lib/vector_mcp/logging/configuration.rb
135
+ - lib/vector_mcp/logging/constants.rb
136
+ - lib/vector_mcp/logging/core.rb
137
+ - lib/vector_mcp/logging/filters/component.rb
138
+ - lib/vector_mcp/logging/filters/level.rb
139
+ - lib/vector_mcp/logging/formatters/base.rb
140
+ - lib/vector_mcp/logging/formatters/json.rb
141
+ - lib/vector_mcp/logging/formatters/text.rb
142
+ - lib/vector_mcp/logging/outputs/base.rb
143
+ - lib/vector_mcp/logging/outputs/console.rb
144
+ - lib/vector_mcp/logging/outputs/file.rb
101
145
  - lib/vector_mcp/sampling/request.rb
102
146
  - lib/vector_mcp/sampling/result.rb
147
+ - lib/vector_mcp/security.rb
148
+ - lib/vector_mcp/security/auth_manager.rb
149
+ - lib/vector_mcp/security/authorization.rb
150
+ - lib/vector_mcp/security/middleware.rb
151
+ - lib/vector_mcp/security/session_context.rb
152
+ - lib/vector_mcp/security/strategies/api_key.rb
153
+ - lib/vector_mcp/security/strategies/custom.rb
154
+ - lib/vector_mcp/security/strategies/jwt_token.rb
103
155
  - lib/vector_mcp/server.rb
104
156
  - lib/vector_mcp/server/capabilities.rb
105
157
  - lib/vector_mcp/server/message_handling.rb