vector_mcp 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +349 -0
- data/README.md +292 -501
- data/lib/vector_mcp/errors.rb +30 -3
- data/lib/vector_mcp/handlers/core.rb +270 -33
- data/lib/vector_mcp/logger.rb +148 -0
- data/lib/vector_mcp/middleware/base.rb +177 -0
- data/lib/vector_mcp/middleware/context.rb +76 -0
- data/lib/vector_mcp/middleware/hook.rb +169 -0
- data/lib/vector_mcp/middleware/manager.rb +191 -0
- data/lib/vector_mcp/middleware.rb +43 -0
- data/lib/vector_mcp/security/auth_manager.rb +79 -0
- data/lib/vector_mcp/security/authorization.rb +96 -0
- data/lib/vector_mcp/security/middleware.rb +172 -0
- data/lib/vector_mcp/security/session_context.rb +147 -0
- data/lib/vector_mcp/security/strategies/api_key.rb +167 -0
- data/lib/vector_mcp/security/strategies/custom.rb +71 -0
- data/lib/vector_mcp/security/strategies/jwt_token.rb +121 -0
- data/lib/vector_mcp/security.rb +46 -0
- data/lib/vector_mcp/server.rb +189 -5
- data/lib/vector_mcp/session.rb +37 -4
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +16 -8
- metadata +49 -4
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VectorMCP
|
4
|
+
module Security
|
5
|
+
module Strategies
|
6
|
+
# Custom authentication strategy
|
7
|
+
# Allows developers to implement their own authentication logic
|
8
|
+
class Custom
|
9
|
+
attr_reader :handler
|
10
|
+
|
11
|
+
# Initialize with a custom authentication handler
|
12
|
+
# @param handler [Proc] a block that takes a request and returns user info or false
|
13
|
+
def initialize(&handler)
|
14
|
+
raise ArgumentError, "Custom authentication strategy requires a block" unless handler
|
15
|
+
|
16
|
+
@handler = handler
|
17
|
+
end
|
18
|
+
|
19
|
+
# Authenticate a request using the custom handler
|
20
|
+
# @param request [Hash] the request object
|
21
|
+
# @return [Object, false] result from custom handler or false if authentication failed
|
22
|
+
def authenticate(request)
|
23
|
+
result = @handler.call(request)
|
24
|
+
|
25
|
+
# Ensure result includes strategy info if it's successful
|
26
|
+
if result && result != false
|
27
|
+
format_successful_result(result)
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
rescue NoMemoryError, StandardError
|
32
|
+
# Log error but return false for security
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check if handler is configured
|
37
|
+
# @return [Boolean] true if handler is present
|
38
|
+
def configured?
|
39
|
+
!@handler.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Format successful authentication result with strategy metadata
|
45
|
+
# @param result [Object] the result from the custom handler
|
46
|
+
# @return [Object] formatted result with strategy metadata
|
47
|
+
def format_successful_result(result)
|
48
|
+
case result
|
49
|
+
when Hash
|
50
|
+
# If result has a :user key, extract it and use as main user data
|
51
|
+
if result.key?(:user)
|
52
|
+
user_data = result[:user]
|
53
|
+
# For nil user, return a marker that will become nil in session context
|
54
|
+
return :authenticated_nil_user if user_data.nil?
|
55
|
+
|
56
|
+
user_data
|
57
|
+
else
|
58
|
+
result.merge(strategy: "custom", authenticated_at: Time.now)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
{
|
62
|
+
user: result,
|
63
|
+
strategy: "custom",
|
64
|
+
authenticated_at: Time.now
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,121 @@
|
|
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, JWT::InvalidAudienceError,
|
55
|
+
JWT::VerificationError, 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
|
+
token = auth_header[7..] # Remove 'Bearer ' prefix
|
100
|
+
return nil if token.nil? || token.strip.empty?
|
101
|
+
|
102
|
+
token.strip
|
103
|
+
end
|
104
|
+
|
105
|
+
# Extract token from custom JWT header
|
106
|
+
# @param headers [Hash] request headers
|
107
|
+
# @return [String, nil] the token if found
|
108
|
+
def extract_from_jwt_header(headers)
|
109
|
+
headers["X-JWT-Token"] || headers["x-jwt-token"]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Extract token from query parameters
|
113
|
+
# @param params [Hash] request parameters
|
114
|
+
# @return [String, nil] the token if found
|
115
|
+
def extract_from_params(params)
|
116
|
+
params["jwt_token"] || params["token"]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
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
|
data/lib/vector_mcp/server.rb
CHANGED
@@ -12,6 +12,14 @@ 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"
|
22
|
+
require_relative "middleware"
|
15
23
|
|
16
24
|
module VectorMCP
|
17
25
|
# The `Server` class is the central component for an MCP server implementation.
|
@@ -62,7 +70,8 @@ module VectorMCP
|
|
62
70
|
# The specific version of the Model Context Protocol this server implements.
|
63
71
|
PROTOCOL_VERSION = "2024-11-05"
|
64
72
|
|
65
|
-
attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests
|
73
|
+
attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
|
74
|
+
:auth_manager, :authorization, :security_middleware, :middleware_manager
|
66
75
|
attr_accessor :transport
|
67
76
|
|
68
77
|
# Initializes a new VectorMCP server.
|
@@ -90,8 +99,8 @@ module VectorMCP
|
|
90
99
|
@name = name_pos || name || "UnnamedServer"
|
91
100
|
@version = version
|
92
101
|
@protocol_version = options[:protocol_version] || PROTOCOL_VERSION
|
93
|
-
@logger = VectorMCP.
|
94
|
-
|
102
|
+
@logger = VectorMCP.logger_for("server")
|
103
|
+
# NOTE: log level should be configured via VectorMCP.configure_logging instead
|
95
104
|
|
96
105
|
@transport = nil
|
97
106
|
@tools = {}
|
@@ -108,6 +117,14 @@ module VectorMCP
|
|
108
117
|
# Configure sampling capabilities
|
109
118
|
@sampling_config = configure_sampling_capabilities(options[:sampling_config] || {})
|
110
119
|
|
120
|
+
# Initialize security components
|
121
|
+
@auth_manager = Security::AuthManager.new
|
122
|
+
@authorization = Security::Authorization.new
|
123
|
+
@security_middleware = Security::Middleware.new(@auth_manager, @authorization)
|
124
|
+
|
125
|
+
# Initialize middleware manager
|
126
|
+
@middleware_manager = Middleware::Manager.new
|
127
|
+
|
111
128
|
setup_default_handlers
|
112
129
|
|
113
130
|
@logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
|
@@ -120,7 +137,7 @@ module VectorMCP
|
|
120
137
|
# @param transport [:stdio, :sse, VectorMCP::Transport::Base] The transport to use.
|
121
138
|
# Can be a symbol (`:stdio`, `:sse`) or an initialized transport instance.
|
122
139
|
# If a symbol is provided, the method will instantiate the corresponding transport class.
|
123
|
-
# If `:sse` is chosen,
|
140
|
+
# If `:sse` is chosen, it uses Puma as the HTTP server.
|
124
141
|
# @param options [Hash] Transport-specific options (e.g., `:host`, `:port` for SSE).
|
125
142
|
# These are passed to the transport's constructor if a symbol is provided for `transport`.
|
126
143
|
# @return [void]
|
@@ -135,7 +152,7 @@ module VectorMCP
|
|
135
152
|
require_relative "transport/sse"
|
136
153
|
VectorMCP::Transport::SSE.new(self, **options)
|
137
154
|
rescue LoadError => e
|
138
|
-
logger.fatal("SSE transport requires additional dependencies.
|
155
|
+
logger.fatal("SSE transport requires additional dependencies.")
|
139
156
|
raise NotImplementedError, "SSE transport dependencies not available: #{e.message}"
|
140
157
|
end
|
141
158
|
when VectorMCP::Transport::Base # Allow passing an initialized transport instance
|
@@ -148,6 +165,173 @@ module VectorMCP
|
|
148
165
|
self.transport = active_transport
|
149
166
|
active_transport.run
|
150
167
|
end
|
168
|
+
|
169
|
+
# --- Security Configuration ---
|
170
|
+
|
171
|
+
# Enable authentication with specified strategy and configuration
|
172
|
+
# @param strategy [Symbol] the authentication strategy (:api_key, :jwt, :custom)
|
173
|
+
# @param options [Hash] strategy-specific configuration options
|
174
|
+
# @return [void]
|
175
|
+
def enable_authentication!(strategy: :api_key, **options, &block)
|
176
|
+
# Clear existing strategies when switching to a new configuration
|
177
|
+
clear_auth_strategies unless @auth_manager.strategies.empty?
|
178
|
+
|
179
|
+
@auth_manager.enable!(default_strategy: strategy)
|
180
|
+
|
181
|
+
case strategy
|
182
|
+
when :api_key
|
183
|
+
add_api_key_auth(options[:keys] || [])
|
184
|
+
when :jwt
|
185
|
+
add_jwt_auth(options)
|
186
|
+
when :custom
|
187
|
+
handler = block || options[:handler]
|
188
|
+
raise ArgumentError, "Custom authentication strategy requires a handler block" unless handler
|
189
|
+
|
190
|
+
add_custom_auth(&handler)
|
191
|
+
|
192
|
+
else
|
193
|
+
raise ArgumentError, "Unknown authentication strategy: #{strategy}"
|
194
|
+
end
|
195
|
+
|
196
|
+
@logger.info("Authentication enabled with strategy: #{strategy}")
|
197
|
+
end
|
198
|
+
|
199
|
+
# Disable authentication (return to pass-through mode)
|
200
|
+
# @return [void]
|
201
|
+
def disable_authentication!
|
202
|
+
@auth_manager.disable!
|
203
|
+
@logger.info("Authentication disabled")
|
204
|
+
end
|
205
|
+
|
206
|
+
# Enable authorization with optional policy configuration block
|
207
|
+
# @param block [Proc] optional block for configuring authorization policies
|
208
|
+
# @return [void]
|
209
|
+
def enable_authorization!(&block)
|
210
|
+
@authorization.enable!
|
211
|
+
instance_eval(&block) if block_given?
|
212
|
+
@logger.info("Authorization enabled")
|
213
|
+
end
|
214
|
+
|
215
|
+
# Disable authorization (return to pass-through mode)
|
216
|
+
# @return [void]
|
217
|
+
def disable_authorization!
|
218
|
+
@authorization.disable!
|
219
|
+
@logger.info("Authorization disabled")
|
220
|
+
end
|
221
|
+
|
222
|
+
# Add authorization policy for tools
|
223
|
+
# @param block [Proc] policy block that receives (user, action, tool)
|
224
|
+
# @return [void]
|
225
|
+
def authorize_tools(&block)
|
226
|
+
@authorization.add_policy(:tool, &block)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Add authorization policy for resources
|
230
|
+
# @param block [Proc] policy block that receives (user, action, resource)
|
231
|
+
# @return [void]
|
232
|
+
def authorize_resources(&block)
|
233
|
+
@authorization.add_policy(:resource, &block)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Add authorization policy for prompts
|
237
|
+
# @param block [Proc] policy block that receives (user, action, prompt)
|
238
|
+
# @return [void]
|
239
|
+
def authorize_prompts(&block)
|
240
|
+
@authorization.add_policy(:prompt, &block)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Add authorization policy for roots
|
244
|
+
# @param block [Proc] policy block that receives (user, action, root)
|
245
|
+
# @return [void]
|
246
|
+
def authorize_roots(&block)
|
247
|
+
@authorization.add_policy(:root, &block)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Check if security features are enabled
|
251
|
+
# @return [Boolean] true if authentication or authorization is enabled
|
252
|
+
def security_enabled?
|
253
|
+
@security_middleware.security_enabled?
|
254
|
+
end
|
255
|
+
|
256
|
+
# Get current security status for debugging/monitoring
|
257
|
+
# @return [Hash] security configuration status
|
258
|
+
def security_status
|
259
|
+
@security_middleware.security_status
|
260
|
+
end
|
261
|
+
|
262
|
+
# --- Middleware Management ---
|
263
|
+
|
264
|
+
# Register middleware for specific hook types
|
265
|
+
# @param middleware_class [Class] Middleware class inheriting from VectorMCP::Middleware::Base
|
266
|
+
# @param hooks [Symbol, Array<Symbol>] Hook types to register for (e.g., :before_tool_call, [:before_tool_call, :after_tool_call])
|
267
|
+
# @param priority [Integer] Execution priority (lower numbers execute first, default: 100)
|
268
|
+
# @param conditions [Hash] Conditions for when middleware should run
|
269
|
+
# @option conditions [Array<String>] :only_operations Only run for these operations
|
270
|
+
# @option conditions [Array<String>] :except_operations Don't run for these operations
|
271
|
+
# @option conditions [Array<String>] :only_users Only run for these user IDs
|
272
|
+
# @option conditions [Array<String>] :except_users Don't run for these user IDs
|
273
|
+
# @option conditions [Boolean] :critical If true, errors in this middleware stop execution
|
274
|
+
# @example
|
275
|
+
# server.use_middleware(MyMiddleware, :before_tool_call)
|
276
|
+
# server.use_middleware(AuthMiddleware, [:before_request, :after_response], priority: 10)
|
277
|
+
# server.use_middleware(LoggingMiddleware, :after_tool_call, conditions: { only_operations: ['important_tool'] })
|
278
|
+
def use_middleware(middleware_class, hooks, priority: Middleware::Hook::DEFAULT_PRIORITY, conditions: {})
|
279
|
+
@middleware_manager.register(middleware_class, hooks, priority: priority, conditions: conditions)
|
280
|
+
@logger.info("Registered middleware: #{middleware_class.name}")
|
281
|
+
end
|
282
|
+
|
283
|
+
# Remove all middleware hooks for a specific class
|
284
|
+
# @param middleware_class [Class] Middleware class to remove
|
285
|
+
def remove_middleware(middleware_class)
|
286
|
+
@middleware_manager.unregister(middleware_class)
|
287
|
+
@logger.info("Removed middleware: #{middleware_class.name}")
|
288
|
+
end
|
289
|
+
|
290
|
+
# Get middleware statistics
|
291
|
+
# @return [Hash] Statistics about registered middleware
|
292
|
+
def middleware_stats
|
293
|
+
@middleware_manager.stats
|
294
|
+
end
|
295
|
+
|
296
|
+
# Clear all middleware (useful for testing)
|
297
|
+
def clear_middleware!
|
298
|
+
@middleware_manager.clear!
|
299
|
+
@logger.info("Cleared all middleware")
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
|
304
|
+
# Add API key authentication strategy
|
305
|
+
# @param keys [Array<String>] array of valid API keys
|
306
|
+
# @return [void]
|
307
|
+
def add_api_key_auth(keys)
|
308
|
+
strategy = Security::Strategies::ApiKey.new(keys: keys)
|
309
|
+
@auth_manager.add_strategy(:api_key, strategy)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Add JWT authentication strategy
|
313
|
+
# @param options [Hash] JWT configuration options
|
314
|
+
# @return [void]
|
315
|
+
def add_jwt_auth(options)
|
316
|
+
strategy = Security::Strategies::JwtToken.new(**options)
|
317
|
+
@auth_manager.add_strategy(:jwt, strategy)
|
318
|
+
end
|
319
|
+
|
320
|
+
# Add custom authentication strategy
|
321
|
+
# @param handler [Proc] custom authentication handler block
|
322
|
+
# @return [void]
|
323
|
+
def add_custom_auth(&block)
|
324
|
+
strategy = Security::Strategies::Custom.new(&block)
|
325
|
+
@auth_manager.add_strategy(:custom, strategy)
|
326
|
+
end
|
327
|
+
|
328
|
+
# Clear all authentication strategies
|
329
|
+
# @return [void]
|
330
|
+
def clear_auth_strategies
|
331
|
+
@auth_manager.strategies.each_key do |strategy_name|
|
332
|
+
@auth_manager.remove_strategy(strategy_name)
|
333
|
+
end
|
334
|
+
end
|
151
335
|
end
|
152
336
|
|
153
337
|
module Transport
|
data/lib/vector_mcp/session.rb
CHANGED
@@ -95,10 +95,43 @@ module VectorMCP
|
|
95
95
|
def sample(request_params, timeout: nil)
|
96
96
|
validate_sampling_preconditions
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
98
|
+
# Create middleware context for sampling
|
99
|
+
context = VectorMCP::Middleware::Context.new(
|
100
|
+
operation_type: :sampling,
|
101
|
+
operation_name: "createMessage",
|
102
|
+
params: request_params,
|
103
|
+
session: self,
|
104
|
+
server: @server,
|
105
|
+
metadata: { start_time: Time.now, timeout: timeout }
|
106
|
+
)
|
107
|
+
|
108
|
+
# Execute before_sampling_request hooks
|
109
|
+
context = @server.middleware_manager.execute_hooks(:before_sampling_request, context)
|
110
|
+
raise context.error if context.error?
|
111
|
+
|
112
|
+
begin
|
113
|
+
sampling_req_obj = VectorMCP::Sampling::Request.new(request_params)
|
114
|
+
@logger.info("[Session #{@id}] Sending sampling/createMessage request to client.")
|
115
|
+
|
116
|
+
result = send_sampling_request(sampling_req_obj, timeout)
|
117
|
+
|
118
|
+
# Set result in context
|
119
|
+
context.result = result
|
120
|
+
|
121
|
+
# Execute after_sampling_response hooks
|
122
|
+
context = @server.middleware_manager.execute_hooks(:after_sampling_response, context)
|
123
|
+
|
124
|
+
context.result
|
125
|
+
rescue StandardError => e
|
126
|
+
# Set error in context and execute error hooks
|
127
|
+
context.error = e
|
128
|
+
context = @server.middleware_manager.execute_hooks(:on_sampling_error, context)
|
129
|
+
|
130
|
+
# Re-raise unless middleware handled the error
|
131
|
+
raise e unless context.result
|
132
|
+
|
133
|
+
context.result
|
134
|
+
end
|
102
135
|
end
|
103
136
|
|
104
137
|
private
|
data/lib/vector_mcp/version.rb
CHANGED
data/lib/vector_mcp.rb
CHANGED
@@ -11,14 +11,16 @@ require_relative "vector_mcp/util"
|
|
11
11
|
require_relative "vector_mcp/image_util"
|
12
12
|
require_relative "vector_mcp/handlers/core"
|
13
13
|
require_relative "vector_mcp/transport/stdio"
|
14
|
-
# require_relative "vector_mcp/transport/sse" # Load on demand
|
14
|
+
# require_relative "vector_mcp/transport/sse" # Load on demand
|
15
|
+
require_relative "vector_mcp/logger"
|
16
|
+
require_relative "vector_mcp/middleware"
|
15
17
|
require_relative "vector_mcp/server"
|
16
18
|
|
17
19
|
# The VectorMCP module provides a full-featured, opinionated Ruby implementation
|
18
20
|
# of the **Model Context Protocol (MCP)**. It gives developers everything needed
|
19
21
|
# to spin up an MCP-compatible server—including:
|
20
22
|
#
|
21
|
-
# * **Transport adapters** (synchronous `stdio` or
|
23
|
+
# * **Transport adapters** (synchronous `stdio` or HTTP + SSE)
|
22
24
|
# * **High-level abstractions** for *tools*, *resources*, and *prompts*
|
23
25
|
# * **JSON-RPC 2.0** message handling with sensible defaults and detailed
|
24
26
|
# error reporting helpers
|
@@ -44,13 +46,19 @@ require_relative "vector_mcp/server"
|
|
44
46
|
# order to serve multiple concurrent clients over HTTP.
|
45
47
|
#
|
46
48
|
module VectorMCP
|
47
|
-
# @return [Logger] the shared logger instance for the library.
|
48
|
-
@logger = Logger.new($stderr, level: Logger::INFO, progname: "VectorMCP")
|
49
|
-
|
50
49
|
class << self
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
50
|
+
# Get a component-specific logger
|
51
|
+
# @param component [String, Symbol] the component name
|
52
|
+
# @return [VectorMCP::Logger] component logger
|
53
|
+
def logger_for(component)
|
54
|
+
Logger.for(component)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the default logger
|
58
|
+
# @return [VectorMCP::Logger] default logger
|
59
|
+
def logger
|
60
|
+
@logger ||= Logger.for("vectormcp")
|
61
|
+
end
|
54
62
|
|
55
63
|
# Creates a new {VectorMCP::Server} instance. This is a **thin wrapper** around
|
56
64
|
# `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.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergio Bayona
|
@@ -65,6 +65,20 @@ dependencies:
|
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '3.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: jwt
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.7'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.7'
|
68
82
|
- !ruby/object:Gem::Dependency
|
69
83
|
name: puma
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,8 +93,24 @@ dependencies:
|
|
79
93
|
- - "~>"
|
80
94
|
- !ruby/object:Gem::Version
|
81
95
|
version: '6.4'
|
82
|
-
|
83
|
-
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rack
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.0'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.0'
|
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,22 @@ 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/logger.rb
|
133
|
+
- lib/vector_mcp/middleware.rb
|
134
|
+
- lib/vector_mcp/middleware/base.rb
|
135
|
+
- lib/vector_mcp/middleware/context.rb
|
136
|
+
- lib/vector_mcp/middleware/hook.rb
|
137
|
+
- lib/vector_mcp/middleware/manager.rb
|
101
138
|
- lib/vector_mcp/sampling/request.rb
|
102
139
|
- lib/vector_mcp/sampling/result.rb
|
140
|
+
- lib/vector_mcp/security.rb
|
141
|
+
- lib/vector_mcp/security/auth_manager.rb
|
142
|
+
- lib/vector_mcp/security/authorization.rb
|
143
|
+
- lib/vector_mcp/security/middleware.rb
|
144
|
+
- lib/vector_mcp/security/session_context.rb
|
145
|
+
- lib/vector_mcp/security/strategies/api_key.rb
|
146
|
+
- lib/vector_mcp/security/strategies/custom.rb
|
147
|
+
- lib/vector_mcp/security/strategies/jwt_token.rb
|
103
148
|
- lib/vector_mcp/server.rb
|
104
149
|
- lib/vector_mcp/server/capabilities.rb
|
105
150
|
- lib/vector_mcp/server/message_handling.rb
|
@@ -128,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
173
|
requirements:
|
129
174
|
- - ">="
|
130
175
|
- !ruby/object:Gem::Version
|
131
|
-
version: 3.
|
176
|
+
version: 3.0.6
|
132
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
178
|
requirements:
|
134
179
|
- - ">="
|