vector_mcp 0.3.1 → 0.3.3
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 +122 -0
- data/lib/vector_mcp/definitions.rb +25 -9
- data/lib/vector_mcp/errors.rb +2 -3
- data/lib/vector_mcp/handlers/core.rb +206 -50
- data/lib/vector_mcp/logger.rb +148 -0
- data/lib/vector_mcp/middleware/base.rb +171 -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 +179 -0
- data/lib/vector_mcp/middleware.rb +43 -0
- data/lib/vector_mcp/request_context.rb +182 -0
- data/lib/vector_mcp/sampling/result.rb +11 -1
- data/lib/vector_mcp/security/middleware.rb +2 -28
- data/lib/vector_mcp/security/strategies/api_key.rb +2 -24
- data/lib/vector_mcp/security/strategies/jwt_token.rb +6 -3
- data/lib/vector_mcp/server/capabilities.rb +5 -7
- data/lib/vector_mcp/server/message_handling.rb +11 -5
- data/lib/vector_mcp/server.rb +74 -20
- data/lib/vector_mcp/session.rb +131 -8
- data/lib/vector_mcp/transport/base_session_manager.rb +320 -0
- data/lib/vector_mcp/transport/http_stream/event_store.rb +151 -0
- data/lib/vector_mcp/transport/http_stream/session_manager.rb +189 -0
- data/lib/vector_mcp/transport/http_stream/stream_handler.rb +269 -0
- data/lib/vector_mcp/transport/http_stream.rb +779 -0
- data/lib/vector_mcp/transport/sse.rb +74 -19
- data/lib/vector_mcp/transport/sse_session_manager.rb +188 -0
- data/lib/vector_mcp/transport/stdio.rb +70 -13
- data/lib/vector_mcp/transport/stdio_session_manager.rb +181 -0
- data/lib/vector_mcp/util.rb +39 -1
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +10 -35
- metadata +25 -24
- data/lib/vector_mcp/logging/component.rb +0 -131
- data/lib/vector_mcp/logging/configuration.rb +0 -156
- data/lib/vector_mcp/logging/constants.rb +0 -21
- data/lib/vector_mcp/logging/core.rb +0 -175
- data/lib/vector_mcp/logging/filters/component.rb +0 -69
- data/lib/vector_mcp/logging/filters/level.rb +0 -23
- data/lib/vector_mcp/logging/formatters/base.rb +0 -52
- data/lib/vector_mcp/logging/formatters/json.rb +0 -83
- data/lib/vector_mcp/logging/formatters/text.rb +0 -72
- data/lib/vector_mcp/logging/outputs/base.rb +0 -64
- data/lib/vector_mcp/logging/outputs/console.rb +0 -35
- data/lib/vector_mcp/logging/outputs/file.rb +0 -157
- data/lib/vector_mcp/logging.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64b4dac9a0a1e9c782d3b5d693e7582803621ac81555f992031f9edbdd3f5b6b
|
4
|
+
data.tar.gz: 571e540ac540859b1fefaadc5e2b3cda39a1415c3496af38cee46cb839316c85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc7c13562e31070ce7aef3d0d20801e4a7368a44b22ea58d01ec872f7f9191e7b70d96e72c3cdcd42b9ecc40dc9b7ea30e63ce12b5b54987734a933402269b71
|
7
|
+
data.tar.gz: 6f989302cbd97c00aa5f2dfe28950cffb5c2c53c204c3ab10eb886a9dc680e0db6a704fef54229488910c837ebe3857c7e493ae5aa8729d8d6677fe048c223e1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,125 @@
|
|
1
|
+
## [0.3.3] – 2025-07-29
|
2
|
+
|
3
|
+
### Fixed
|
4
|
+
|
5
|
+
* **Critical Security Fix - SSE Transport Session Isolation**: Fixed default behavior where SSE transport shared session state across all clients
|
6
|
+
- **BREAKING CHANGE**: SSE transport now defaults to secure session isolation mode
|
7
|
+
- Session manager is now enabled by default to prevent race conditions and data leakage
|
8
|
+
- Legacy shared session mode available via `disable_session_manager: true` option (deprecated with warning)
|
9
|
+
- Enhanced security tests to prevent regression of this critical vulnerability
|
10
|
+
|
11
|
+
* **Critical Security Fix - Path Traversal Vulnerability**: Replaced naive path traversal validation with robust canonicalization
|
12
|
+
- **Path Validation Enhancement**: Now uses `File.expand_path` for proper path canonicalization
|
13
|
+
- **Attack Prevention**: Eliminates false positives from simple string-based `.` checks
|
14
|
+
- **Security Monitoring**: Added warnings for potential path traversal attempts
|
15
|
+
- **Bypass Protection**: Prevents sophisticated path traversal attacks through encoding or complex patterns
|
16
|
+
|
17
|
+
* **Session Compatibility**: Fixed session object type detection across different transports
|
18
|
+
- **Transport Compatibility**: Added automatic detection between `BaseSessionManager::Session` and `VectorMCP::Session` types
|
19
|
+
- **Method Resolution**: Fixed `undefined method 'initialized?'` and `'initialize!'` errors
|
20
|
+
- **Transport Layer**: Enhanced stdio, SSE, and HTTP stream transports for consistent session handling
|
21
|
+
|
22
|
+
* **Race Condition Fix**: Resolved concurrent session creation test failures in SSE transport
|
23
|
+
- **Thread Safety**: Implemented `Concurrent::Array` for thread-safe session tracking
|
24
|
+
- **Test Stability**: Enhanced test reliability for concurrent operations
|
25
|
+
|
26
|
+
* **Code Quality**: Fixed RuboCop style and linting violations
|
27
|
+
- **Naming Conventions**: Updated method names to follow Ruby conventions (removed `get_` prefixes)
|
28
|
+
- **Style Compliance**: Fixed line length violations and predicate method naming
|
29
|
+
- **Consistency**: Applied consistent coding standards across the codebase
|
30
|
+
|
31
|
+
### Security
|
32
|
+
|
33
|
+
* **Defense in Depth**: Major security improvements addressing critical vulnerabilities
|
34
|
+
- **Multi-Client Security**: Eliminated shared state vulnerabilities in SSE transport
|
35
|
+
- **Path Security**: Comprehensive path traversal protection using canonical path resolution
|
36
|
+
- **Session Isolation**: Proper session boundary enforcement across all transport types
|
37
|
+
|
38
|
+
* **Backward Compatibility**: Security fixes maintain API compatibility while improving defaults
|
39
|
+
- **Opt-in Legacy Mode**: Deprecated insecure modes available for gradual migration
|
40
|
+
- **Migration Path**: Clear deprecation warnings guide users to secure configurations
|
41
|
+
|
42
|
+
### Testing
|
43
|
+
|
44
|
+
* **Enhanced Security Test Coverage**: Comprehensive test suites for critical security fixes
|
45
|
+
- **Path Traversal Tests**: 20+ test cases covering legitimate paths, attack vectors, and edge cases
|
46
|
+
- **SSE Security Tests**: Verification of default secure behavior and session isolation
|
47
|
+
- **Integration Tests**: Cross-transport compatibility and session handling validation
|
48
|
+
|
49
|
+
### Technical Details
|
50
|
+
|
51
|
+
* **Session Architecture**: Improved session management layer for better transport compatibility
|
52
|
+
* **Security Monitoring**: Enhanced logging and warning systems for security events
|
53
|
+
* **Error Handling**: Better error messages and debugging information for session-related issues
|
54
|
+
|
55
|
+
## [0.3.2] – 2025-07-02
|
56
|
+
|
57
|
+
### Added
|
58
|
+
|
59
|
+
* **Comprehensive Middleware System**: Pluggable hook system for custom behavior around all MCP operations
|
60
|
+
* **Hook Points**: Support for all major operations including tools, resources, prompts, sampling, transport, and authentication
|
61
|
+
* **Priority-Based Execution**: Control middleware execution order with configurable priorities
|
62
|
+
* **Conditional Execution**: Run middleware only for specific operations, users, or conditions
|
63
|
+
* **Context Management**: Rich execution context with operation metadata and session information
|
64
|
+
* **Error Handling**: Graceful error recovery with middleware-specific error hooks
|
65
|
+
* **Built-in Middleware**: PII redaction, request retry, rate limiting, and enhanced logging examples
|
66
|
+
|
67
|
+
* **Enhanced Examples Organization**: Comprehensive example reorganization for better developer experience
|
68
|
+
* **Getting Started Examples**: `examples/getting_started/` with basic server implementations
|
69
|
+
* **Core Features Examples**: `examples/core_features/` demonstrating key capabilities
|
70
|
+
* **Use Cases Examples**: `examples/use_cases/` with real-world application scenarios
|
71
|
+
* **Logging Examples**: `examples/logging/` showcasing structured logging capabilities
|
72
|
+
* **Middleware Examples**: `examples/middleware_examples.rb` and `examples/simple_middleware_demo.rb`
|
73
|
+
|
74
|
+
* **Refactored Logging System**: Enhanced logging architecture with better performance and flexibility
|
75
|
+
* **Simplified API**: Streamlined `VectorMCP.logger_for(component)` interface
|
76
|
+
* **Performance Improvements**: Optimized log formatting and output handling
|
77
|
+
* **Better Component Organization**: Hierarchical logger management with cleaner separation
|
78
|
+
|
79
|
+
### Changed
|
80
|
+
|
81
|
+
* **Middleware Integration**: Core server architecture enhanced to support middleware hooks
|
82
|
+
* **Server Methods**: New `use_middleware`, `middleware_stats`, `remove_middleware`, and `clear_middleware` methods
|
83
|
+
* **Handler Integration**: All core handlers now support middleware execution around operations
|
84
|
+
* **Session Context**: Enhanced session context with middleware metadata and execution tracking
|
85
|
+
|
86
|
+
* **Example Structure**: Major reorganization of examples for better discoverability
|
87
|
+
* **Categorized Examples**: Logical grouping by functionality and use case
|
88
|
+
* **Enhanced Documentation**: Each example category includes detailed README files
|
89
|
+
* **Use Case Focus**: Real-world scenarios like data analysis, file operations, and web scraping
|
90
|
+
|
91
|
+
* **Backward Compatibility**: All middleware features are opt-in with zero impact on existing servers
|
92
|
+
* **Default Behavior**: Servers without middleware continue working exactly as before
|
93
|
+
* **Optional Integration**: Middleware can be added incrementally to existing applications
|
94
|
+
|
95
|
+
### Fixed
|
96
|
+
|
97
|
+
* **Ruby Version Compatibility**: Enhanced support for older Ruby versions
|
98
|
+
* **Code Quality**: Multiple bug fixes and improvements identified through expanded test coverage
|
99
|
+
* **Performance**: Optimized middleware execution path for minimal overhead when no middleware is registered
|
100
|
+
|
101
|
+
### Security
|
102
|
+
|
103
|
+
* **Middleware Security**: Security-aware middleware execution
|
104
|
+
* **Session Context Integration**: Middleware has access to authentication and authorization context
|
105
|
+
* **Secure Error Handling**: Middleware errors handled securely without information leakage
|
106
|
+
* **Permission-Aware Hooks**: Middleware can respect user permissions and security policies
|
107
|
+
|
108
|
+
### Testing
|
109
|
+
|
110
|
+
* **Comprehensive Middleware Tests**: 50+ tests covering all middleware functionality
|
111
|
+
* **Hook Execution Tests**: Verification of all hook types and execution order
|
112
|
+
* **Priority and Condition Tests**: Complex scenario testing for middleware orchestration
|
113
|
+
* **Integration Tests**: End-to-end testing with real server operations
|
114
|
+
* **Performance Tests**: Overhead measurement and resource usage validation
|
115
|
+
|
116
|
+
### Technical Details
|
117
|
+
|
118
|
+
* **API Compatibility**: All middleware features maintain full backward compatibility
|
119
|
+
* **Performance**: Minimal overhead when middleware is not used, efficient execution when enabled
|
120
|
+
* **Memory Management**: Proper cleanup and resource management for long-running servers
|
121
|
+
* **Thread Safety**: Concurrent middleware execution with proper synchronization
|
122
|
+
|
1
123
|
## [0.3.1] – 2025-06-25
|
2
124
|
|
3
125
|
### Added
|
@@ -242,17 +242,33 @@ module VectorMCP
|
|
242
242
|
# Currently, only file:// scheme is supported per MCP spec
|
243
243
|
raise ArgumentError, "Only file:// URIs are supported for roots, got: #{parsed_uri.scheme}://" unless parsed_uri.scheme == "file"
|
244
244
|
|
245
|
-
# Validate
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
245
|
+
# Validate and canonicalize path for security
|
246
|
+
raw_path = parsed_uri.path
|
247
|
+
|
248
|
+
# Canonicalize the path to resolve any relative components (., .., etc.)
|
249
|
+
# This prevents path traversal attacks and normalizes the path
|
250
|
+
begin
|
251
|
+
canonical_path = File.expand_path(raw_path)
|
252
|
+
rescue ArgumentError => e
|
253
|
+
raise ArgumentError, "Invalid path format: #{raw_path} (#{e.message})"
|
254
|
+
end
|
250
255
|
|
251
|
-
# Security check:
|
256
|
+
# Security check: Verify the canonical path exists and is a directory
|
257
|
+
raise ArgumentError, "Root directory does not exist: #{canonical_path}" unless File.exist?(canonical_path)
|
258
|
+
raise ArgumentError, "Root path is not a directory: #{canonical_path}" unless File.directory?(canonical_path)
|
259
|
+
raise ArgumentError, "Root directory is not readable: #{canonical_path}" unless File.readable?(canonical_path)
|
260
|
+
|
261
|
+
# Additional security: Check if the canonical path differs significantly from raw path
|
262
|
+
# This can indicate potential path traversal attempts
|
263
|
+
if raw_path != canonical_path && raw_path.include?("..")
|
264
|
+
# Log the canonicalization for security monitoring
|
265
|
+
# Note: This is informational - the canonical path is what we'll actually use
|
266
|
+
warn "[SECURITY] Path canonicalized from '#{raw_path}' to '#{canonical_path}'. " \
|
267
|
+
"This may indicate a path traversal attempt."
|
268
|
+
end
|
252
269
|
|
253
|
-
|
254
|
-
|
255
|
-
raise ArgumentError, "Root path contains unsafe traversal patterns: #{path}" if path.include?("..") || path.include?("./")
|
270
|
+
# Update the URI to use the canonical path for consistency
|
271
|
+
self.uri = "file://#{canonical_path}"
|
256
272
|
|
257
273
|
true
|
258
274
|
end
|
data/lib/vector_mcp/errors.rb
CHANGED
@@ -28,7 +28,6 @@ module VectorMCP
|
|
28
28
|
# @param details [Hash, nil] Additional details for the error (optional).
|
29
29
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
30
30
|
def initialize(message, code: -32_600, details: nil, request_id: nil)
|
31
|
-
VectorMCP.logger.debug("Initializing ProtocolError with code: #{code}")
|
32
31
|
@code = code
|
33
32
|
@message = message
|
34
33
|
@details = details # NOTE: `data` in JSON-RPC is often used for this purpose.
|
@@ -104,7 +103,7 @@ module VectorMCP
|
|
104
103
|
# @param details [Hash, nil] Additional details for the error (optional).
|
105
104
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
106
105
|
def initialize(message = "Server error", code: -32_000, details: nil, request_id: nil)
|
107
|
-
|
106
|
+
# ServerError initialization
|
108
107
|
unless (-32_099..-32_000).cover?(code)
|
109
108
|
warn "Server error code #{code} is outside of the reserved range (-32099 to -32000). Using -32000 instead."
|
110
109
|
code = -32_000
|
@@ -131,7 +130,7 @@ module VectorMCP
|
|
131
130
|
# @param details [Hash, nil] Additional details for the error (optional).
|
132
131
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
133
132
|
def initialize(message = "Not Found", details: nil, request_id: nil)
|
134
|
-
|
133
|
+
# NotFoundError initialization
|
135
134
|
super(message, code: -32_001, details: details, request_id: request_id)
|
136
135
|
end
|
137
136
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "json"
|
4
4
|
require "uri"
|
5
5
|
require "json-schema"
|
6
|
+
require_relative "../middleware"
|
6
7
|
|
7
8
|
module VectorMCP
|
8
9
|
module Handlers
|
@@ -21,7 +22,6 @@ module VectorMCP
|
|
21
22
|
# @param _server [VectorMCP::Server] The server instance (ignored).
|
22
23
|
# @return [Hash] An empty hash, as per MCP spec for ping.
|
23
24
|
def self.ping(_params, _session, _server)
|
24
|
-
VectorMCP.logger.debug("Handling ping request")
|
25
25
|
{}
|
26
26
|
end
|
27
27
|
|
@@ -54,27 +54,23 @@ module VectorMCP
|
|
54
54
|
tool_name = params["name"]
|
55
55
|
arguments = params["arguments"] || {}
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
context = create_tool_context(tool_name, params, session, server)
|
58
|
+
context = server.middleware_manager.execute_hooks(:before_tool_call, context)
|
59
|
+
return handle_middleware_error(context) if context.error?
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
begin
|
62
|
+
tool = find_tool!(tool_name, server)
|
63
|
+
security_result = validate_tool_security!(session, tool, server)
|
64
|
+
validate_input_arguments!(tool_name, tool, arguments)
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
+
result = execute_tool_handler(tool, arguments, security_result, session)
|
67
|
+
context.result = build_tool_result(result)
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
tool.handler.call(arguments, security_result[:session_context])
|
73
|
-
end
|
74
|
-
{
|
75
|
-
isError: false,
|
76
|
-
content: VectorMCP::Util.convert_to_mcp_content(result)
|
77
|
-
}
|
69
|
+
context = server.middleware_manager.execute_hooks(:after_tool_call, context)
|
70
|
+
context.result
|
71
|
+
rescue StandardError => e
|
72
|
+
handle_tool_error(e, context, server)
|
73
|
+
end
|
78
74
|
end
|
79
75
|
|
80
76
|
# Handles the `resources/list` request.
|
@@ -103,27 +99,24 @@ module VectorMCP
|
|
103
99
|
# @raise [VectorMCP::ForbiddenError] if authorization fails.
|
104
100
|
def self.read_resource(params, session, server)
|
105
101
|
uri_s = params["uri"]
|
106
|
-
raise VectorMCP::NotFoundError.new("Not Found", details: "Resource not found: #{uri_s}") unless server.resources[uri_s]
|
107
102
|
|
108
|
-
|
103
|
+
context = create_resource_context(uri_s, params, session, server)
|
104
|
+
context = server.middleware_manager.execute_hooks(:before_resource_read, context)
|
105
|
+
return handle_middleware_error(context) if context.error?
|
109
106
|
|
110
|
-
|
111
|
-
|
112
|
-
|
107
|
+
begin
|
108
|
+
resource = find_resource!(uri_s, server)
|
109
|
+
security_result = validate_resource_security!(session, resource, server)
|
113
110
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
contents.each do |item|
|
123
|
-
# Add URI to each content item if not already present
|
124
|
-
item[:uri] ||= uri_s
|
111
|
+
content_raw = execute_resource_handler(resource, params, security_result)
|
112
|
+
contents = process_resource_content(content_raw, resource, uri_s)
|
113
|
+
|
114
|
+
context.result = { contents: contents }
|
115
|
+
context = server.middleware_manager.execute_hooks(:after_resource_read, context)
|
116
|
+
context.result
|
117
|
+
rescue StandardError => e
|
118
|
+
handle_resource_error(e, context, server)
|
125
119
|
end
|
126
|
-
{ contents: contents }
|
127
120
|
end
|
128
121
|
|
129
122
|
# Handles the `prompts/list` request.
|
@@ -184,20 +177,51 @@ module VectorMCP
|
|
184
177
|
# @raise [VectorMCP::NotFoundError] if the prompt name is not found.
|
185
178
|
# @raise [VectorMCP::InvalidParamsError] if arguments are invalid.
|
186
179
|
# @raise [VectorMCP::InternalError] if the prompt handler returns an invalid data structure.
|
187
|
-
def self.get_prompt(params,
|
180
|
+
def self.get_prompt(params, session, server)
|
188
181
|
prompt_name = params["name"]
|
189
|
-
prompt = fetch_prompt(prompt_name, server)
|
190
182
|
|
191
|
-
|
192
|
-
|
183
|
+
# Create middleware context
|
184
|
+
context = VectorMCP::Middleware::Context.new(
|
185
|
+
operation_type: :prompt_get,
|
186
|
+
operation_name: prompt_name,
|
187
|
+
params: params,
|
188
|
+
session: session,
|
189
|
+
server: server,
|
190
|
+
metadata: { start_time: Time.now }
|
191
|
+
)
|
192
|
+
|
193
|
+
# Execute before_prompt_get hooks
|
194
|
+
context = server.middleware_manager.execute_hooks(:before_prompt_get, context)
|
195
|
+
return handle_middleware_error(context) if context.error?
|
196
|
+
|
197
|
+
begin
|
198
|
+
prompt = fetch_prompt(prompt_name, server)
|
199
|
+
|
200
|
+
arguments = params["arguments"] || {}
|
201
|
+
validate_arguments!(prompt_name, prompt, arguments)
|
193
202
|
|
194
|
-
|
195
|
-
|
203
|
+
# Call the registered handler after arguments were validated
|
204
|
+
result_data = prompt.handler.call(arguments)
|
196
205
|
|
197
|
-
|
206
|
+
validate_prompt_response!(prompt_name, result_data, server)
|
198
207
|
|
199
|
-
|
200
|
-
|
208
|
+
# Set result in context
|
209
|
+
context.result = result_data
|
210
|
+
|
211
|
+
# Execute after_prompt_get hooks
|
212
|
+
context = server.middleware_manager.execute_hooks(:after_prompt_get, context)
|
213
|
+
|
214
|
+
context.result
|
215
|
+
rescue StandardError => e
|
216
|
+
# Set error in context and execute error hooks
|
217
|
+
context.error = e
|
218
|
+
context = server.middleware_manager.execute_hooks(:on_prompt_error, context)
|
219
|
+
|
220
|
+
# Re-raise unless middleware handled the error
|
221
|
+
raise e unless context.result
|
222
|
+
|
223
|
+
context.result
|
224
|
+
end
|
201
225
|
end
|
202
226
|
|
203
227
|
# --- Notification Handlers ---
|
@@ -400,12 +424,16 @@ module VectorMCP
|
|
400
424
|
# @param session [VectorMCP::Session] The current session
|
401
425
|
# @return [Hash] Request context for security middleware
|
402
426
|
def self.extract_request_from_session(session)
|
403
|
-
#
|
404
|
-
|
427
|
+
# All sessions should have a request_context - this is enforced by Session initialization
|
428
|
+
unless session.respond_to?(:request_context) && session.request_context
|
429
|
+
raise VectorMCP::InternalError,
|
430
|
+
"Session missing request_context - transport layer integration error. Session ID: #{session.id}"
|
431
|
+
end
|
432
|
+
|
405
433
|
{
|
406
|
-
headers: session.
|
407
|
-
params: session.
|
408
|
-
session_id: session.
|
434
|
+
headers: session.request_context.headers,
|
435
|
+
params: session.request_context.params,
|
436
|
+
session_id: session.id
|
409
437
|
}
|
410
438
|
end
|
411
439
|
private_class_method :extract_request_from_session
|
@@ -427,6 +455,134 @@ module VectorMCP
|
|
427
455
|
end
|
428
456
|
end
|
429
457
|
private_class_method :handle_security_failure
|
458
|
+
|
459
|
+
# Handle middleware error by returning appropriate response or raising error
|
460
|
+
# @api private
|
461
|
+
# @param context [VectorMCP::Middleware::Context] The middleware context with error
|
462
|
+
# @return [Hash, nil] Response hash if middleware provided one
|
463
|
+
# @raise [StandardError] Re-raises the original error if not handled
|
464
|
+
def self.handle_middleware_error(context)
|
465
|
+
# If middleware provided a result, return it
|
466
|
+
return context.result if context.result
|
467
|
+
|
468
|
+
# Otherwise, re-raise the middleware error
|
469
|
+
raise context.error
|
470
|
+
end
|
471
|
+
|
472
|
+
# Tool helper methods
|
473
|
+
|
474
|
+
# Create middleware context for tool operations
|
475
|
+
def self.create_tool_context(tool_name, params, session, server)
|
476
|
+
VectorMCP::Middleware::Context.new(
|
477
|
+
operation_type: :tool_call,
|
478
|
+
operation_name: tool_name,
|
479
|
+
params: params,
|
480
|
+
session: session,
|
481
|
+
server: server,
|
482
|
+
metadata: { start_time: Time.now }
|
483
|
+
)
|
484
|
+
end
|
485
|
+
|
486
|
+
# Find and validate tool exists
|
487
|
+
def self.find_tool!(tool_name, server)
|
488
|
+
tool = server.tools[tool_name]
|
489
|
+
raise VectorMCP::NotFoundError.new("Not Found", details: "Tool not found: #{tool_name}") unless tool
|
490
|
+
|
491
|
+
tool
|
492
|
+
end
|
493
|
+
|
494
|
+
# Validate tool security
|
495
|
+
def self.validate_tool_security!(session, tool, server)
|
496
|
+
security_result = check_tool_security(session, tool, server)
|
497
|
+
handle_security_failure(security_result) unless security_result[:success]
|
498
|
+
security_result
|
499
|
+
end
|
500
|
+
|
501
|
+
# Execute tool handler with proper arity handling
|
502
|
+
def self.execute_tool_handler(tool, arguments, _security_result, session)
|
503
|
+
if [1, -1].include?(tool.handler.arity)
|
504
|
+
tool.handler.call(arguments)
|
505
|
+
else
|
506
|
+
tool.handler.call(arguments, session)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# Build tool result response
|
511
|
+
def self.build_tool_result(result)
|
512
|
+
{
|
513
|
+
isError: false,
|
514
|
+
content: VectorMCP::Util.convert_to_mcp_content(result)
|
515
|
+
}
|
516
|
+
end
|
517
|
+
|
518
|
+
# Handle tool execution errors
|
519
|
+
def self.handle_tool_error(error, context, server)
|
520
|
+
context.error = error
|
521
|
+
context = server.middleware_manager.execute_hooks(:on_tool_error, context)
|
522
|
+
raise error unless context.result
|
523
|
+
|
524
|
+
context.result
|
525
|
+
end
|
526
|
+
|
527
|
+
# Resource helper methods
|
528
|
+
|
529
|
+
# Create middleware context for resource operations
|
530
|
+
def self.create_resource_context(uri_s, params, session, server)
|
531
|
+
VectorMCP::Middleware::Context.new(
|
532
|
+
operation_type: :resource_read,
|
533
|
+
operation_name: uri_s,
|
534
|
+
params: params,
|
535
|
+
session: session,
|
536
|
+
server: server,
|
537
|
+
metadata: { start_time: Time.now }
|
538
|
+
)
|
539
|
+
end
|
540
|
+
|
541
|
+
# Find and validate resource exists
|
542
|
+
def self.find_resource!(uri_s, server)
|
543
|
+
raise VectorMCP::NotFoundError.new("Not Found", details: "Resource not found: #{uri_s}") unless server.resources[uri_s]
|
544
|
+
|
545
|
+
server.resources[uri_s]
|
546
|
+
end
|
547
|
+
|
548
|
+
# Validate resource security
|
549
|
+
def self.validate_resource_security!(session, resource, server)
|
550
|
+
security_result = check_resource_security(session, resource, server)
|
551
|
+
handle_security_failure(security_result) unless security_result[:success]
|
552
|
+
security_result
|
553
|
+
end
|
554
|
+
|
555
|
+
# Execute resource handler with proper arity handling
|
556
|
+
def self.execute_resource_handler(resource, params, security_result)
|
557
|
+
if [1, -1].include?(resource.handler.arity)
|
558
|
+
resource.handler.call(params)
|
559
|
+
else
|
560
|
+
resource.handler.call(params, security_result[:session_context])
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
# Process resource content and add URI
|
565
|
+
def self.process_resource_content(content_raw, resource, uri_s)
|
566
|
+
contents = VectorMCP::Util.convert_to_mcp_content(content_raw, mime_type: resource.mime_type)
|
567
|
+
contents.each do |item|
|
568
|
+
item[:uri] ||= uri_s
|
569
|
+
end
|
570
|
+
contents
|
571
|
+
end
|
572
|
+
|
573
|
+
# Handle resource execution errors
|
574
|
+
def self.handle_resource_error(error, context, server)
|
575
|
+
context.error = error
|
576
|
+
context = server.middleware_manager.execute_hooks(:on_resource_error, context)
|
577
|
+
raise error unless context.result
|
578
|
+
|
579
|
+
context.result
|
580
|
+
end
|
581
|
+
|
582
|
+
private_class_method :handle_middleware_error, :create_tool_context, :find_tool!, :validate_tool_security!,
|
583
|
+
:execute_tool_handler, :build_tool_result, :handle_tool_error, :create_resource_context,
|
584
|
+
:find_resource!, :validate_resource_security!, :execute_resource_handler,
|
585
|
+
:process_resource_content, :handle_resource_error
|
430
586
|
end
|
431
587
|
end
|
432
588
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module VectorMCP
|
7
|
+
# Simple, environment-driven logger for VectorMCP
|
8
|
+
# Supports JSON and text formats with component-based identification
|
9
|
+
class Logger
|
10
|
+
LEVELS = {
|
11
|
+
"TRACE" => ::Logger::DEBUG,
|
12
|
+
"DEBUG" => ::Logger::DEBUG,
|
13
|
+
"INFO" => ::Logger::INFO,
|
14
|
+
"WARN" => ::Logger::WARN,
|
15
|
+
"ERROR" => ::Logger::ERROR,
|
16
|
+
"FATAL" => ::Logger::FATAL
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
attr_reader :component, :ruby_logger
|
20
|
+
|
21
|
+
def initialize(component = "vectormcp")
|
22
|
+
@component = component.to_s
|
23
|
+
@ruby_logger = create_ruby_logger
|
24
|
+
@format = ENV.fetch("VECTORMCP_LOG_FORMAT", "text").downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.for(component)
|
28
|
+
new(component)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Log methods with context support and block evaluation
|
32
|
+
def debug(message = nil, **context, &block)
|
33
|
+
log(:debug, message || block&.call, context)
|
34
|
+
end
|
35
|
+
|
36
|
+
def info(message = nil, **context, &block)
|
37
|
+
log(:info, message || block&.call, context)
|
38
|
+
end
|
39
|
+
|
40
|
+
def warn(message = nil, **context, &block)
|
41
|
+
log(:warn, message || block&.call, context)
|
42
|
+
end
|
43
|
+
|
44
|
+
def error(message = nil, **context, &block)
|
45
|
+
log(:error, message || block&.call, context)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fatal(message = nil, **context, &block)
|
49
|
+
log(:fatal, message || block&.call, context)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Security-specific logging
|
53
|
+
def security(message, **context)
|
54
|
+
log(:error, "[SECURITY] #{message}", context.merge(security_event: true))
|
55
|
+
end
|
56
|
+
|
57
|
+
# Performance measurement
|
58
|
+
def measure(description, **context)
|
59
|
+
start_time = Time.now
|
60
|
+
result = yield
|
61
|
+
duration = Time.now - start_time
|
62
|
+
|
63
|
+
info("#{description} completed", **context, duration_ms: (duration * 1000).round(2),
|
64
|
+
success: true)
|
65
|
+
|
66
|
+
result
|
67
|
+
rescue StandardError => e
|
68
|
+
duration = Time.now - start_time
|
69
|
+
error("#{description} failed", **context, duration_ms: (duration * 1000).round(2),
|
70
|
+
success: false,
|
71
|
+
error: e.class.name,
|
72
|
+
error_message: e.message)
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def log(level, message, context)
|
79
|
+
return unless @ruby_logger.send("#{level}?")
|
80
|
+
|
81
|
+
if @format == "json"
|
82
|
+
log_json(level, message, context)
|
83
|
+
else
|
84
|
+
log_text(level, message, context)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def log_json(level, message, context)
|
89
|
+
entry = {
|
90
|
+
timestamp: Time.now.iso8601(3),
|
91
|
+
level: level.to_s.upcase,
|
92
|
+
component: @component,
|
93
|
+
message: message,
|
94
|
+
thread_id: Thread.current.object_id
|
95
|
+
}
|
96
|
+
entry.merge!(context) unless context.empty?
|
97
|
+
|
98
|
+
@ruby_logger.send(level, entry.to_json)
|
99
|
+
end
|
100
|
+
|
101
|
+
def log_text(level, message, context)
|
102
|
+
formatted_message = if context.empty?
|
103
|
+
"[#{@component}] #{message}"
|
104
|
+
else
|
105
|
+
context_str = context.map { |k, v| "#{k}=#{v}" }.join(" ")
|
106
|
+
"[#{@component}] #{message} (#{context_str})"
|
107
|
+
end
|
108
|
+
|
109
|
+
@ruby_logger.send(level, formatted_message)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_ruby_logger
|
113
|
+
output = determine_output
|
114
|
+
logger = ::Logger.new(output)
|
115
|
+
logger.level = determine_level
|
116
|
+
logger.formatter = method(:format_log_entry)
|
117
|
+
logger
|
118
|
+
end
|
119
|
+
|
120
|
+
def determine_output
|
121
|
+
case ENV.fetch("VECTORMCP_LOG_OUTPUT", "stderr").downcase
|
122
|
+
when "stdout"
|
123
|
+
$stdout
|
124
|
+
when "file"
|
125
|
+
file_path = ENV.fetch("VECTORMCP_LOG_FILE", "./vectormcp.log")
|
126
|
+
File.open(file_path, "a")
|
127
|
+
else
|
128
|
+
$stderr
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def determine_level
|
133
|
+
level_name = ENV.fetch("VECTORMCP_LOG_LEVEL", "INFO").upcase
|
134
|
+
LEVELS.fetch(level_name, ::Logger::INFO)
|
135
|
+
end
|
136
|
+
|
137
|
+
def format_log_entry(severity, datetime, _progname, msg)
|
138
|
+
if @format == "json"
|
139
|
+
# JSON messages are already formatted
|
140
|
+
"#{msg}\n"
|
141
|
+
else
|
142
|
+
# Text format with timestamp
|
143
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
144
|
+
"#{timestamp} [#{severity}] #{msg}\n"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|