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



250
251
252
# File 'lib/vector_mcp/definitions.rb', line 250

def name
  @name
end

#uriObject

Returns the value of attribute uri

Returns:

  • (Object)

    the current value of uri



250
251
252
# File 'lib/vector_mcp/definitions.rb', line 250

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.



312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/vector_mcp/definitions.rb', line 312

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.



253
254
255
256
257
258
# File 'lib/vector_mcp/definitions.rb', line 253

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.



330
331
332
333
334
335
# File 'lib/vector_mcp/definitions.rb', line 330

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.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/vector_mcp/definitions.rb', line 264

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