Module: VectorMCP::Util

Defined in:
lib/vector_mcp/util.rb

Overview

Provides utility functions for VectorMCP operations, such as data conversion and parsing.

Class Method Summary collapse

Class Method Details

.all_content_items?(arr) ⇒ Boolean

Checks if all array items are pre-formatted MCP content items.

Parameters:

  • arr (Array)

    The array to check.

Returns:

  • (Boolean)

    True if all items have type fields.



105
106
107
# File 'lib/vector_mcp/util.rb', line 105

def all_content_items?(arr)
  arr.all? { |item| item.is_a?(Hash) && (item[:type] || item["type"]) }
end

.array_content(arr, mime_type) ⇒ Array<Hash>

Converts an Array into MCP content items. If all array elements are pre-formatted MCP content items, they are used directly. Otherwise, each item in the array is recursively converted using #convert_to_mcp_content.

Parameters:

  • arr (Array)

    The array to convert.

  • mime_type (String)

    The default MIME type for child items if they need conversion.

Returns:

  • (Array<Hash>)

    MCP content array.



94
95
96
97
98
99
100
# File 'lib/vector_mcp/util.rb', line 94

def array_content(arr, mime_type)
  if all_content_items?(arr)
    arr.map { |item| process_content_item(item) }
  else
    arr.flat_map { |item| convert_to_mcp_content(item, mime_type: mime_type) }
  end
end

.binary_image_data?(str) ⇒ Boolean

Checks if a string contains binary image data.

Parameters:

  • str (String)

    The string to check.

Returns:

  • (Boolean)

    True if it appears to be binary image data.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/vector_mcp/util.rb', line 170

def binary_image_data?(str)
  return false if str.nil? || str.empty?

  # Check encoding first
  encoding = str.encoding
  is_binary = encoding == Encoding::ASCII_8BIT || !str.valid_encoding?

  return false unless is_binary

  # Use ImageUtil to detect if it's actually image data
  require_relative "image_util"
  !VectorMCP::ImageUtil.detect_image_format(str).nil?
rescue StandardError
  false
end

.binary_image_to_content(binary_data) ⇒ Array<Hash>

Converts binary image data to MCP image content.

Parameters:

  • binary_data (String)

    Binary image data.

Returns:

  • (Array<Hash>)

    MCP content array with image content.



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/vector_mcp/util.rb', line 204

def binary_image_to_content(binary_data)
  require_relative "image_util"

  begin
    image_content = VectorMCP::ImageUtil.to_mcp_image_content(binary_data)
    [image_content]
  rescue ArgumentError
    # If image processing fails, fall back to text content
    [{ type: "text", text: binary_data.to_s, mimeType: "application/octet-stream" }]
  end
end

.convert_to_mcp_content(input, mime_type: "text/plain") ⇒ Array<Hash>

Converts a given Ruby object into an array of MCP content items. This is the primary public helper for transforming arbitrary Ruby values into the wire-format expected by the MCP spec.

Keys present in each returned hash:

  • :type"text" or "image"; automatic detection for binary data.
  • :text – UTF-8 encoded payload (for text content).
  • :data – Base64 encoded payload (for image content).
  • :mimeType – IANA media-type describing the content.
  • :uriOptional. Added downstream (e.g., by Handlers::Core.read_resource).

The method never returns nil and always returns at least one element.

Examples:

Simple string

VectorMCP::Util.convert_to_mcp_content("Hello")
# => [{type: "text", text: "Hello", mimeType: "text/plain"}]

Complex object

VectorMCP::Util.convert_to_mcp_content({foo: 1})
# => [{type: "text", text: "{\"foo\":1}", mimeType: "application/json"}]

Image file path

VectorMCP::Util.convert_to_mcp_content("image.jpg")
# => [{type: "image", data: "base64...", mimeType: "image/jpeg"}]

Parameters:

  • input (Object)

    The Ruby value to convert. Supported types are String, Hash, Array, or any object that responds to #to_s.

  • mime_type (String) (defaults to: "text/plain")

    The fallback MIME type for plain-text conversions (defaults to "text/plain").

Returns:

  • (Array<Hash>)

    A non-empty array whose hashes conform to the MCP Content schema.



43
44
45
46
47
48
49
# File 'lib/vector_mcp/util.rb', line 43

def convert_to_mcp_content(input, mime_type: "text/plain")
  return string_content(input, mime_type) if input.is_a?(String)
  return hash_content(input)             if input.is_a?(Hash)
  return array_content(input, mime_type) if input.is_a?(Array)

  fallback_content(input, mime_type)
end

.extract_id_from_invalid_json(json_string) ⇒ String?

Extracts an ID from a potentially malformed JSON string using regex. This is a best-effort attempt, primarily for error reporting when full JSON parsing fails. It looks for patterns like "id": 123 or "id": "abc".

Parameters:

  • json_string (String)

    The (potentially invalid) JSON string.

Returns:

  • (String, nil)

    The extracted ID as a string if found (numeric or string), otherwise nil.



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/vector_mcp/util.rb', line 137

def extract_id_from_invalid_json(json_string)
  # Try to find id field with numeric value
  numeric_match = json_string.match(/"id"\s*:\s*(\d+)/)
  return numeric_match[1] if numeric_match

  # Try to find id field with string value, preserving escaped characters
  string_match = json_string.match(/"id"\s*:\s*"((?:\\.|[^"])*)"/)
  return string_match[1] if string_match

  nil
end

.fallback_content(obj, mime_type) ⇒ Array<Hash>

Fallback conversion for any other object type to an MCP text content item. Converts the object to its string representation.

Parameters:

  • obj (Object)

    The object to convert.

  • mime_type (String)

    The MIME type for the content.

Returns:

  • (Array<Hash>)

    MCP content array with one text item.



124
125
126
# File 'lib/vector_mcp/util.rb', line 124

def fallback_content(obj, mime_type)
  [{ type: "text", text: obj.to_s, mimeType: mime_type }]
end

.file_path_to_image_content(file_path) ⇒ Array<Hash>

Converts a file path string to image content.

Parameters:

  • file_path (String)

    Path to the image file.

Returns:

  • (Array<Hash>)

    MCP content array with image content.



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/vector_mcp/util.rb', line 189

def file_path_to_image_content(file_path)
  require_relative "image_util"

  begin
    image_content = VectorMCP::ImageUtil.file_to_mcp_image_content(file_path)
    [image_content]
  rescue ArgumentError => e
    # If image processing fails, fall back to text content with error message
    [{ type: "text", text: "Error loading image '#{file_path}': #{e.message}", mimeType: "text/plain" }]
  end
end

.hash_content(hash) ⇒ Array<Hash>

Converts a Hash into an MCP content item. If the hash appears to be a pre-formatted MCP content item, it's used directly. Otherwise, it's converted to a JSON string with application/json MIME type.

Parameters:

  • hash (Hash)

    The hash to convert.

Returns:

  • (Array<Hash>)

    MCP content array.



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vector_mcp/util.rb', line 75

def hash_content(hash)
  if hash[:type] || hash["type"] # Already in content format
    normalized = hash.transform_keys(&:to_sym)

    # Validate and enhance image content if needed
    return [validate_and_enhance_image_content(normalized)] if normalized[:type] == "image"

    [normalized]
  else
    [{ type: "text", text: hash.to_json, mimeType: "application/json" }]
  end
end

.looks_like_image_file_path?(str) ⇒ Boolean

Checks if a string looks like a file path to an image.

Parameters:

  • str (String)

    The string to check.

Returns:

  • (Boolean)

    True if it looks like an image file path.



154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/vector_mcp/util.rb', line 154

def looks_like_image_file_path?(str)
  return false if str.nil? || str.empty? || str.length > 500

  # Check for common image extensions
  image_extensions = %w[.jpg .jpeg .png .gif .webp .bmp .tiff .tif .svg]
  has_image_extension = image_extensions.any? { |ext| str.downcase.end_with?(ext) }

  # Check if it looks like a file path (contains / or \ or ends with image extension)
  looks_like_path = str.include?("/") || str.include?("\\") || has_image_extension

  has_image_extension && looks_like_path
end

.process_content_item(item) ⇒ Hash

Processes a single content item, normalizing and validating as needed.

Parameters:

  • item (Hash)

    The content item to process.

Returns:

  • (Hash)

    The processed content item.



112
113
114
115
116
117
# File 'lib/vector_mcp/util.rb', line 112

def process_content_item(item)
  normalized = item.transform_keys(&:to_sym)
  return validate_and_enhance_image_content(normalized) if normalized[:type] == "image"

  normalized
end

.string_content(str, mime_type) ⇒ Array<Hash>

Converts a String into an MCP content item. Intelligently detects if the string is binary image data, a file path to an image, or regular text content.

Parameters:

  • str (String)

    The string to convert.

  • mime_type (String)

    The MIME type for the content.

Returns:

  • (Array<Hash>)

    MCP content array with one item.



59
60
61
62
63
64
65
66
67
68
# File 'lib/vector_mcp/util.rb', line 59

def string_content(str, mime_type)
  # Check if this might be a file path to an image
  return file_path_to_image_content(str) if looks_like_image_file_path?(str)

  # Check if this is binary image data
  return binary_image_to_content(str) if binary_image_data?(str)

  # Default to text content
  [{ type: "text", text: str, mimeType: mime_type }]
end

.validate_and_enhance_image_content(content) ⇒ Hash

Validates and enhances existing image content hash.

Parameters:

  • content (Hash)

    Existing image content hash.

Returns:

  • (Hash)

    Validated and enhanced image content.



219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/vector_mcp/util.rb', line 219

def validate_and_enhance_image_content(content)
  # Ensure required fields are present
  raise ArgumentError, "Image content must have both :data and :mimeType fields" unless content[:data] && content[:mimeType]

  # Validate the base64 data if possible
  begin
    require_relative "image_util"
    VectorMCP::ImageUtil.decode_base64(content[:data])
  rescue ArgumentError => e
    raise ArgumentError, "Invalid base64 image data: #{e.message}"
  end

  content
end