vector_mcp 0.3.1 → 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 +68 -0
- data/lib/vector_mcp/errors.rb +6 -3
- data/lib/vector_mcp/handlers/core.rb +199 -45
- 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/strategies/jwt_token.rb +6 -3
- data/lib/vector_mcp/server.rb +61 -17
- data/lib/vector_mcp/session.rb +37 -4
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +10 -35
- metadata +17 -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
data/lib/vector_mcp/server.rb
CHANGED
@@ -19,6 +19,7 @@ require_relative "security/session_context"
|
|
19
19
|
require_relative "security/strategies/api_key"
|
20
20
|
require_relative "security/strategies/jwt_token"
|
21
21
|
require_relative "security/strategies/custom"
|
22
|
+
require_relative "middleware"
|
22
23
|
|
23
24
|
module VectorMCP
|
24
25
|
# The `Server` class is the central component for an MCP server implementation.
|
@@ -70,7 +71,7 @@ module VectorMCP
|
|
70
71
|
PROTOCOL_VERSION = "2024-11-05"
|
71
72
|
|
72
73
|
attr_reader :logger, :name, :version, :protocol_version, :tools, :resources, :prompts, :roots, :in_flight_requests,
|
73
|
-
:auth_manager, :authorization, :security_middleware
|
74
|
+
:auth_manager, :authorization, :security_middleware, :middleware_manager
|
74
75
|
attr_accessor :transport
|
75
76
|
|
76
77
|
# Initializes a new VectorMCP server.
|
@@ -98,8 +99,8 @@ module VectorMCP
|
|
98
99
|
@name = name_pos || name || "UnnamedServer"
|
99
100
|
@version = version
|
100
101
|
@protocol_version = options[:protocol_version] || PROTOCOL_VERSION
|
101
|
-
@logger = VectorMCP.
|
102
|
-
|
102
|
+
@logger = VectorMCP.logger_for("server")
|
103
|
+
# NOTE: log level should be configured via VectorMCP.configure_logging instead
|
103
104
|
|
104
105
|
@transport = nil
|
105
106
|
@tools = {}
|
@@ -121,6 +122,9 @@ module VectorMCP
|
|
121
122
|
@authorization = Security::Authorization.new
|
122
123
|
@security_middleware = Security::Middleware.new(@auth_manager, @authorization)
|
123
124
|
|
125
|
+
# Initialize middleware manager
|
126
|
+
@middleware_manager = Middleware::Manager.new
|
127
|
+
|
124
128
|
setup_default_handlers
|
125
129
|
|
126
130
|
@logger.info("Server instance '#{@name}' v#{@version} (MCP Protocol: #{@protocol_version}, Gem: v#{VectorMCP::VERSION}) initialized.")
|
@@ -133,7 +137,7 @@ module VectorMCP
|
|
133
137
|
# @param transport [:stdio, :sse, VectorMCP::Transport::Base] The transport to use.
|
134
138
|
# Can be a symbol (`:stdio`, `:sse`) or an initialized transport instance.
|
135
139
|
# If a symbol is provided, the method will instantiate the corresponding transport class.
|
136
|
-
# If `:sse` is chosen,
|
140
|
+
# If `:sse` is chosen, it uses Puma as the HTTP server.
|
137
141
|
# @param options [Hash] Transport-specific options (e.g., `:host`, `:port` for SSE).
|
138
142
|
# These are passed to the transport's constructor if a symbol is provided for `transport`.
|
139
143
|
# @return [void]
|
@@ -148,7 +152,7 @@ module VectorMCP
|
|
148
152
|
require_relative "transport/sse"
|
149
153
|
VectorMCP::Transport::SSE.new(self, **options)
|
150
154
|
rescue LoadError => e
|
151
|
-
logger.fatal("SSE transport requires additional dependencies.
|
155
|
+
logger.fatal("SSE transport requires additional dependencies.")
|
152
156
|
raise NotImplementedError, "SSE transport dependencies not available: #{e.message}"
|
153
157
|
end
|
154
158
|
when VectorMCP::Transport::Base # Allow passing an initialized transport instance
|
@@ -202,9 +206,9 @@ module VectorMCP
|
|
202
206
|
# Enable authorization with optional policy configuration block
|
203
207
|
# @param block [Proc] optional block for configuring authorization policies
|
204
208
|
# @return [void]
|
205
|
-
def enable_authorization!(&)
|
209
|
+
def enable_authorization!(&block)
|
206
210
|
@authorization.enable!
|
207
|
-
instance_eval(&) if block_given?
|
211
|
+
instance_eval(&block) if block_given?
|
208
212
|
@logger.info("Authorization enabled")
|
209
213
|
end
|
210
214
|
|
@@ -218,29 +222,29 @@ module VectorMCP
|
|
218
222
|
# Add authorization policy for tools
|
219
223
|
# @param block [Proc] policy block that receives (user, action, tool)
|
220
224
|
# @return [void]
|
221
|
-
def authorize_tools(&)
|
222
|
-
@authorization.add_policy(:tool, &)
|
225
|
+
def authorize_tools(&block)
|
226
|
+
@authorization.add_policy(:tool, &block)
|
223
227
|
end
|
224
228
|
|
225
229
|
# Add authorization policy for resources
|
226
230
|
# @param block [Proc] policy block that receives (user, action, resource)
|
227
231
|
# @return [void]
|
228
|
-
def authorize_resources(&)
|
229
|
-
@authorization.add_policy(:resource, &)
|
232
|
+
def authorize_resources(&block)
|
233
|
+
@authorization.add_policy(:resource, &block)
|
230
234
|
end
|
231
235
|
|
232
236
|
# Add authorization policy for prompts
|
233
237
|
# @param block [Proc] policy block that receives (user, action, prompt)
|
234
238
|
# @return [void]
|
235
|
-
def authorize_prompts(&)
|
236
|
-
@authorization.add_policy(:prompt, &)
|
239
|
+
def authorize_prompts(&block)
|
240
|
+
@authorization.add_policy(:prompt, &block)
|
237
241
|
end
|
238
242
|
|
239
243
|
# Add authorization policy for roots
|
240
244
|
# @param block [Proc] policy block that receives (user, action, root)
|
241
245
|
# @return [void]
|
242
|
-
def authorize_roots(&)
|
243
|
-
@authorization.add_policy(:root, &)
|
246
|
+
def authorize_roots(&block)
|
247
|
+
@authorization.add_policy(:root, &block)
|
244
248
|
end
|
245
249
|
|
246
250
|
# Check if security features are enabled
|
@@ -255,6 +259,46 @@ module VectorMCP
|
|
255
259
|
@security_middleware.security_status
|
256
260
|
end
|
257
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
|
+
|
258
302
|
private
|
259
303
|
|
260
304
|
# Add API key authentication strategy
|
@@ -276,8 +320,8 @@ module VectorMCP
|
|
276
320
|
# Add custom authentication strategy
|
277
321
|
# @param handler [Proc] custom authentication handler block
|
278
322
|
# @return [void]
|
279
|
-
def add_custom_auth(&)
|
280
|
-
strategy = Security::Strategies::Custom.new(&)
|
323
|
+
def add_custom_auth(&block)
|
324
|
+
strategy = Security::Strategies::Custom.new(&block)
|
281
325
|
@auth_manager.add_strategy(:custom, strategy)
|
282
326
|
end
|
283
327
|
|
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,15 +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
|
15
|
-
require_relative "vector_mcp/
|
14
|
+
# require_relative "vector_mcp/transport/sse" # Load on demand
|
15
|
+
require_relative "vector_mcp/logger"
|
16
|
+
require_relative "vector_mcp/middleware"
|
16
17
|
require_relative "vector_mcp/server"
|
17
18
|
|
18
19
|
# The VectorMCP module provides a full-featured, opinionated Ruby implementation
|
19
20
|
# of the **Model Context Protocol (MCP)**. It gives developers everything needed
|
20
21
|
# to spin up an MCP-compatible server—including:
|
21
22
|
#
|
22
|
-
# * **Transport adapters** (synchronous `stdio` or
|
23
|
+
# * **Transport adapters** (synchronous `stdio` or HTTP + SSE)
|
23
24
|
# * **High-level abstractions** for *tools*, *resources*, and *prompts*
|
24
25
|
# * **JSON-RPC 2.0** message handling with sensible defaults and detailed
|
25
26
|
# error reporting helpers
|
@@ -45,44 +46,18 @@ require_relative "vector_mcp/server"
|
|
45
46
|
# order to serve multiple concurrent clients over HTTP.
|
46
47
|
#
|
47
48
|
module VectorMCP
|
48
|
-
# @return [Logger] the shared logger instance for the library.
|
49
|
-
@logger = Logger.new($stderr, level: Logger::INFO, progname: "VectorMCP")
|
50
|
-
|
51
|
-
# @return [VectorMCP::Logging::Core] the new structured logging system
|
52
|
-
@logging_core = nil
|
53
|
-
|
54
49
|
class << self
|
55
|
-
# @!attribute [r] 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
50
|
# Get a component-specific logger
|
74
51
|
# @param component [String, Symbol] the component name
|
75
|
-
# @return [VectorMCP::
|
52
|
+
# @return [VectorMCP::Logger] component logger
|
76
53
|
def logger_for(component)
|
77
|
-
|
78
|
-
@logging_core.logger_for(component)
|
54
|
+
Logger.for(component)
|
79
55
|
end
|
80
56
|
|
81
|
-
#
|
82
|
-
# @
|
83
|
-
def
|
84
|
-
|
85
|
-
@logging_core.configure(&)
|
57
|
+
# Get the default logger
|
58
|
+
# @return [VectorMCP::Logger] default logger
|
59
|
+
def logger
|
60
|
+
@logger ||= Logger.for("vectormcp")
|
86
61
|
end
|
87
62
|
|
88
63
|
# Creates a new {VectorMCP::Server} instance. This is a **thin wrapper** around
|
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
|
@@ -66,47 +66,47 @@ dependencies:
|
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '3.0'
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
|
-
name:
|
69
|
+
name: jwt
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '2.7'
|
75
75
|
type: :runtime
|
76
76
|
prerelease: false
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
81
|
+
version: '2.7'
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name:
|
83
|
+
name: puma
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
88
|
+
version: '6.4'
|
89
89
|
type: :runtime
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
95
|
+
version: '6.4'
|
96
96
|
- !ruby/object:Gem::Dependency
|
97
|
-
name:
|
97
|
+
name: rack
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
102
|
+
version: '3.0'
|
103
103
|
type: :runtime
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
109
|
+
version: '3.0'
|
110
110
|
description: A Ruby gem implementing the Model Context Protocol (MCP) server-side
|
111
111
|
specification. Provides a framework for creating MCP servers that expose tools,
|
112
112
|
resources, prompts, and roots to LLM clients with comprehensive security features,
|
@@ -129,19 +129,12 @@ files:
|
|
129
129
|
- lib/vector_mcp/errors.rb
|
130
130
|
- lib/vector_mcp/handlers/core.rb
|
131
131
|
- lib/vector_mcp/image_util.rb
|
132
|
-
- lib/vector_mcp/
|
133
|
-
- lib/vector_mcp/
|
134
|
-
- lib/vector_mcp/
|
135
|
-
- lib/vector_mcp/
|
136
|
-
- lib/vector_mcp/
|
137
|
-
- lib/vector_mcp/
|
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
|
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
|
145
138
|
- lib/vector_mcp/sampling/request.rb
|
146
139
|
- lib/vector_mcp/sampling/result.rb
|
147
140
|
- lib/vector_mcp/security.rb
|
@@ -180,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
180
173
|
requirements:
|
181
174
|
- - ">="
|
182
175
|
- !ruby/object:Gem::Version
|
183
|
-
version: 3.
|
176
|
+
version: 3.0.6
|
184
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
178
|
requirements:
|
186
179
|
- - ">="
|
@@ -1,131 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module VectorMCP
|
4
|
-
module Logging
|
5
|
-
class Component
|
6
|
-
attr_reader :name, :core, :config
|
7
|
-
|
8
|
-
def initialize(name, core, config = {})
|
9
|
-
@name = name.to_s
|
10
|
-
@core = core
|
11
|
-
@config = config
|
12
|
-
@context = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
def with_context(context)
|
16
|
-
old_context = @context
|
17
|
-
@context = @context.merge(context)
|
18
|
-
yield
|
19
|
-
ensure
|
20
|
-
@context = old_context
|
21
|
-
end
|
22
|
-
|
23
|
-
def add_context(context)
|
24
|
-
@context = @context.merge(context)
|
25
|
-
end
|
26
|
-
|
27
|
-
def clear_context
|
28
|
-
@context = {}
|
29
|
-
end
|
30
|
-
|
31
|
-
def trace(message = nil, context: {}, &block)
|
32
|
-
log_with_block(Logging::LEVELS[:TRACE], message, context, &block)
|
33
|
-
end
|
34
|
-
|
35
|
-
def debug(message = nil, context: {}, &block)
|
36
|
-
log_with_block(Logging::LEVELS[:DEBUG], message, context, &block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def info(message = nil, context: {}, &block)
|
40
|
-
log_with_block(Logging::LEVELS[:INFO], message, context, &block)
|
41
|
-
end
|
42
|
-
|
43
|
-
def warn(message = nil, context: {}, &block)
|
44
|
-
log_with_block(Logging::LEVELS[:WARN], message, context, &block)
|
45
|
-
end
|
46
|
-
|
47
|
-
def error(message = nil, context: {}, &block)
|
48
|
-
log_with_block(Logging::LEVELS[:ERROR], message, context, &block)
|
49
|
-
end
|
50
|
-
|
51
|
-
def fatal(message = nil, context: {}, &block)
|
52
|
-
log_with_block(Logging::LEVELS[:FATAL], message, context, &block)
|
53
|
-
end
|
54
|
-
|
55
|
-
def security(message = nil, context: {}, &block)
|
56
|
-
log_with_block(Logging::LEVELS[:SECURITY], message, context, &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
def level
|
60
|
-
@core.configuration.level_for(@name)
|
61
|
-
end
|
62
|
-
|
63
|
-
def level_enabled?(level)
|
64
|
-
level >= self.level
|
65
|
-
end
|
66
|
-
|
67
|
-
def trace?
|
68
|
-
level_enabled?(Logging::LEVELS[:TRACE])
|
69
|
-
end
|
70
|
-
|
71
|
-
def debug?
|
72
|
-
level_enabled?(Logging::LEVELS[:DEBUG])
|
73
|
-
end
|
74
|
-
|
75
|
-
def info?
|
76
|
-
level_enabled?(Logging::LEVELS[:INFO])
|
77
|
-
end
|
78
|
-
|
79
|
-
def warn?
|
80
|
-
level_enabled?(Logging::LEVELS[:WARN])
|
81
|
-
end
|
82
|
-
|
83
|
-
def error?
|
84
|
-
level_enabled?(Logging::LEVELS[:ERROR])
|
85
|
-
end
|
86
|
-
|
87
|
-
def fatal?
|
88
|
-
level_enabled?(Logging::LEVELS[:FATAL])
|
89
|
-
end
|
90
|
-
|
91
|
-
def security?
|
92
|
-
level_enabled?(Logging::LEVELS[:SECURITY])
|
93
|
-
end
|
94
|
-
|
95
|
-
def measure(message, context: {}, level: :info, &block)
|
96
|
-
start_time = Time.now
|
97
|
-
result = nil
|
98
|
-
error = nil
|
99
|
-
|
100
|
-
begin
|
101
|
-
result = block.call
|
102
|
-
rescue StandardError => e
|
103
|
-
error = e
|
104
|
-
raise
|
105
|
-
ensure
|
106
|
-
duration = Time.now - start_time
|
107
|
-
measure_context = context.merge(
|
108
|
-
duration_ms: (duration * 1000).round(2),
|
109
|
-
success: error.nil?
|
110
|
-
)
|
111
|
-
measure_context[:error] = error.class.name if error
|
112
|
-
|
113
|
-
send(level, "#{message} completed", context: measure_context)
|
114
|
-
end
|
115
|
-
|
116
|
-
result
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
def log_with_block(level, message, context, &block)
|
122
|
-
return unless level_enabled?(level)
|
123
|
-
|
124
|
-
message = block.call if block_given?
|
125
|
-
|
126
|
-
full_context = @context.merge(context)
|
127
|
-
@core.log(level, @name, message, full_context)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
@@ -1,156 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "yaml"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
class Configuration
|
8
|
-
DEFAULT_CONFIG = {
|
9
|
-
level: "INFO",
|
10
|
-
format: "text",
|
11
|
-
output: "console",
|
12
|
-
components: {},
|
13
|
-
console: {
|
14
|
-
colorize: true,
|
15
|
-
include_timestamp: true,
|
16
|
-
include_thread: false
|
17
|
-
},
|
18
|
-
file: {
|
19
|
-
path: nil,
|
20
|
-
rotation: "daily",
|
21
|
-
max_size: "100MB",
|
22
|
-
max_files: 7
|
23
|
-
}
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
attr_reader :config
|
27
|
-
|
28
|
-
def initialize(config = {})
|
29
|
-
@config = deep_merge(DEFAULT_CONFIG, normalize_config(config))
|
30
|
-
validate_config!
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.from_file(path)
|
34
|
-
config = YAML.load_file(path)
|
35
|
-
new(config["logging"] || config)
|
36
|
-
rescue StandardError => e
|
37
|
-
raise ConfigurationError, "Failed to load configuration from #{path}: #{e.message}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.from_env
|
41
|
-
config = {}
|
42
|
-
|
43
|
-
config[:level] = ENV["VECTORMCP_LOG_LEVEL"] if ENV["VECTORMCP_LOG_LEVEL"]
|
44
|
-
config[:format] = ENV["VECTORMCP_LOG_FORMAT"] if ENV["VECTORMCP_LOG_FORMAT"]
|
45
|
-
config[:output] = ENV["VECTORMCP_LOG_OUTPUT"] if ENV["VECTORMCP_LOG_OUTPUT"]
|
46
|
-
|
47
|
-
config[:file] = { path: ENV["VECTORMCP_LOG_FILE_PATH"] } if ENV["VECTORMCP_LOG_FILE_PATH"]
|
48
|
-
|
49
|
-
new(config)
|
50
|
-
end
|
51
|
-
|
52
|
-
def level_for(component)
|
53
|
-
component_level = @config[:components][component.to_s]
|
54
|
-
level_value = component_level || @config[:level]
|
55
|
-
Logging.level_value(level_value)
|
56
|
-
end
|
57
|
-
|
58
|
-
def set_component_level(component, level)
|
59
|
-
@config[:components][component.to_s] = if level.is_a?(Integer)
|
60
|
-
Logging.level_name(level)
|
61
|
-
else
|
62
|
-
level.to_s.upcase
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def component_config(component_name)
|
67
|
-
@config[:components][component_name.to_s] || {}
|
68
|
-
end
|
69
|
-
|
70
|
-
def console_config
|
71
|
-
@config[:console]
|
72
|
-
end
|
73
|
-
|
74
|
-
def file_config
|
75
|
-
@config[:file]
|
76
|
-
end
|
77
|
-
|
78
|
-
def format
|
79
|
-
@config[:format]
|
80
|
-
end
|
81
|
-
|
82
|
-
def output
|
83
|
-
@config[:output]
|
84
|
-
end
|
85
|
-
|
86
|
-
def configure(&)
|
87
|
-
instance_eval(&) if block_given?
|
88
|
-
validate_config!
|
89
|
-
end
|
90
|
-
|
91
|
-
def level(new_level)
|
92
|
-
@config[:level] = new_level.to_s.upcase
|
93
|
-
end
|
94
|
-
|
95
|
-
def component(name, level:)
|
96
|
-
@config[:components][name.to_s] = level.to_s.upcase
|
97
|
-
end
|
98
|
-
|
99
|
-
def console(options = {})
|
100
|
-
@config[:console].merge!(options)
|
101
|
-
end
|
102
|
-
|
103
|
-
def file(options = {})
|
104
|
-
@config[:file].merge!(options)
|
105
|
-
end
|
106
|
-
|
107
|
-
def to_h
|
108
|
-
@config.dup
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
def normalize_config(config)
|
114
|
-
case config
|
115
|
-
when Hash
|
116
|
-
config.transform_keys(&:to_sym)
|
117
|
-
when String
|
118
|
-
{ level: config }
|
119
|
-
else
|
120
|
-
{}
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def deep_merge(hash1, hash2)
|
125
|
-
result = hash1.dup
|
126
|
-
hash2.each do |key, value|
|
127
|
-
result[key] = if result[key].is_a?(Hash) && value.is_a?(Hash)
|
128
|
-
deep_merge(result[key], value)
|
129
|
-
else
|
130
|
-
value
|
131
|
-
end
|
132
|
-
end
|
133
|
-
result
|
134
|
-
end
|
135
|
-
|
136
|
-
def validate_config!
|
137
|
-
validate_level!(@config[:level])
|
138
|
-
@config[:components].each_value do |level|
|
139
|
-
validate_level!(level)
|
140
|
-
end
|
141
|
-
|
142
|
-
raise ConfigurationError, "Invalid format: #{@config[:format]}" unless %w[text json].include?(@config[:format])
|
143
|
-
|
144
|
-
return if %w[console file both].include?(@config[:output])
|
145
|
-
|
146
|
-
raise ConfigurationError, "Invalid output: #{@config[:output]}"
|
147
|
-
end
|
148
|
-
|
149
|
-
def validate_level!(level)
|
150
|
-
return if Logging::LEVELS.key?(level.to_s.upcase.to_sym)
|
151
|
-
|
152
|
-
raise ConfigurationError, "Invalid log level: #{level}"
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|