Class: VectorMCP::Definitions::Root

Inherits:
Struct
  • Object
show all
Defined in:
lib/vector_mcp/definitions.rb

Overview

Represents an MCP root definition. Roots define filesystem boundaries where servers can operate.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#nameObject

Returns the value of attribute name

Returns:

  • (Object)

    the current value of name



220
221
222
# File 'lib/vector_mcp/definitions.rb', line 220

def name
  @name
end

#uriObject

Returns the value of attribute uri

Returns:

  • (Object)

    the current value of uri



220
221
222
# File 'lib/vector_mcp/definitions.rb', line 220

def uri
  @uri
end

Class Method Details

.from_path(path, name: nil) ⇒ Root

Class method to create a root from a local directory path.

Parameters:

  • path (String)

    Local filesystem path to the directory.

  • name (String) (defaults to: nil)

    Human-readable name for the root.

Returns:

  • (Root)

    A new Root instance.

Raises:

  • (ArgumentError)

    If the path is invalid or not accessible.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/vector_mcp/definitions.rb', line 282

def self.from_path(path, name: nil)
  # Expand path to get absolute path and resolve any relative components
  expanded_path = File.expand_path(path)

  # Create file:// URI
  uri = "file://#{expanded_path}"

  # Generate name if not provided
  default_name = name || File.basename(expanded_path)

  root = new(uri, default_name)
  root.validate! # Ensure the root is valid
  root
end

Instance Method Details

#as_mcp_definitionHash

Converts the root to its MCP definition hash.

Returns:

  • (Hash)

    A hash representing the root in MCP format.



223
224
225
226
227
228
# File 'lib/vector_mcp/definitions.rb', line 223

def as_mcp_definition
  {
    uri: uri.to_s,
    name: name
  }.compact
end

#pathString

Returns the filesystem path for file:// URIs.

Returns:

  • (String)

    The filesystem path.

Raises:

  • (ArgumentError)

    If the URI is not a file:// scheme.



300
301
302
303
304
305
# File 'lib/vector_mcp/definitions.rb', line 300

def path
  parsed_uri = URI(uri.to_s)
  raise ArgumentError, "Cannot get path for non-file URI: #{uri}" unless parsed_uri.scheme == "file"

  parsed_uri.path
end

#validate!Boolean

Validates that the root URI is properly formatted and secure. rubocop:disable Naming/PredicateMethod

Returns:

  • (Boolean)

    True if the root is valid.

Raises:

  • (ArgumentError)

    If the root is invalid.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/vector_mcp/definitions.rb', line 234

def validate!
  # Validate URI format
  parsed_uri = begin
    URI(uri.to_s)
  rescue URI::InvalidURIError
    raise ArgumentError, "Invalid URI format: #{uri}"
  end

  # Currently, only file:// scheme is supported per MCP spec
  raise ArgumentError, "Only file:// URIs are supported for roots, got: #{parsed_uri.scheme}://" unless parsed_uri.scheme == "file"

  # Validate and canonicalize path for security
  raw_path = parsed_uri.path

  # Canonicalize the path to resolve any relative components (., .., etc.)
  # This prevents path traversal attacks and normalizes the path
  begin
    canonical_path = File.expand_path(raw_path)
  rescue ArgumentError => e
    raise ArgumentError, "Invalid path format: #{raw_path} (#{e.message})"
  end

  # Security check: Verify the canonical path exists and is a directory
  raise ArgumentError, "Root directory does not exist: #{canonical_path}" unless File.exist?(canonical_path)
  raise ArgumentError, "Root path is not a directory: #{canonical_path}" unless File.directory?(canonical_path)
  raise ArgumentError, "Root directory is not readable: #{canonical_path}" unless File.readable?(canonical_path)

  # Additional security: Check if the canonical path differs significantly from raw path
  # This can indicate potential path traversal attempts
  if raw_path != canonical_path && raw_path.include?("..")
    # Log the canonicalization for security monitoring
    # Note: This is informational - the canonical path is what we'll actually use
    warn "[SECURITY] Path canonicalized from '#{raw_path}' to '#{canonical_path}'. " \
         "This may indicate a path traversal attempt."
  end

  # Update the URI to use the canonical path for consistency
  self.uri = "file://#{canonical_path}"

  true
end