Class: VectorMCP::Tool

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

Overview

Abstract base class for declarative tool definitions.

Subclass this to define tools using a class-level DSL instead of the block-based register_tool API. The two styles are fully interchangeable – both produce the same VectorMCP::Definitions::Tool struct that the rest of the system consumes.

Examples:

class ListProviders < VectorMCP::Tool
  tool_name   "list_providers"
  description "List providers filtered by category or status"

  param :category, type: :string, desc: "Filter by category slug"
  param :active,   type: :boolean, default: true

  def call(args, session)
    Provider.where(active: args.fetch("active", true))
  end
end

server.register(ListProviders)

Direct Known Subclasses

Rails::Tool

Constant Summary collapse

TYPE_MAP =

Maps Ruby symbol types to JSON Schema property fragments. Each value is merged into the generated property hash, so it may carry both type and format (or any other JSON Schema keyword).

{
  string: { "type" => "string" },
  integer: { "type" => "integer" },
  number: { "type" => "number" },
  boolean: { "type" => "boolean" },
  array: { "type" => "array" },
  object: { "type" => "object" },
  date: { "type" => "string", "format" => "date" },
  datetime: { "type" => "string", "format" => "date-time" }
}.freeze
COERCERS =

Maps Ruby symbol types to a coercer lambda. Types not listed here pass their values through unchanged. Coercers receive the raw value (or nil) and return the coerced value. They must be total over the values JSON Schema validation would accept.

{
  date: ->(v) { v.nil? || v.is_a?(Date) ? v : Date.parse(v.to_s) },
  datetime: ->(v) { v.nil? || v.is_a?(Time) ? v : Time.parse(v.to_s) }
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.coerce_args(args, params) ⇒ Object

Applies coercers to the raw argument hash. Returns a new hash; does not mutate the original. Keys without a coercible type pass through. Keys that are absent from args stay absent — coercion only fires for keys actually present.

A parse failure on a client-supplied value is translated into VectorMCP::InvalidParamsError (JSON-RPC -32602) so the client sees a “bad request” response instead of a generic internal error. This is needed because the json-schema gem does not enforce format: date upstream (it does enforce format: date-time), so malformed :date values would otherwise crash inside Date.parse.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/vector_mcp/tool.rb', line 168

def self.coerce_args(args, params)
  coerced = args.dup
  params.each do |param|
    name = param[:name]
    next unless coerced.key?(name)

    coercer = COERCERS[param[:type]]
    next unless coercer

    begin
      coerced[name] = coercer.call(coerced[name])
    rescue ArgumentError, TypeError => e
      # Date::Error < ArgumentError in Ruby 3.2+, so ArgumentError alone covers Date.parse failures.
      raise VectorMCP::InvalidParamsError.new(
        "Invalid #{param[:type]} value for param '#{name}': #{e.message}",
        details: { param: name, type: param[:type], message: e.message }
      )
    end
  end
  coerced
end

.description(text = nil) ⇒ String?

Sets or retrieves the tool description.

Parameters:

  • text (String, nil) (defaults to: nil)

    The description to set.

Returns:

  • (String, nil)

    The description.



80
81
82
83
84
85
86
# File 'lib/vector_mcp/tool.rb', line 80

def self.description(text = nil)
  if text
    @description = text
  else
    @description
  end
end

.inherited(subclass) ⇒ Object

Ensures each subclass gets its own @params array so sibling classes do not share mutable state.



56
57
58
59
# File 'lib/vector_mcp/tool.rb', line 56

def self.inherited(subclass)
  super
  subclass.instance_variable_set(:@params, [])
end

.param(name, type: :string, desc: nil, required: false, **options) ⇒ Object

Declares a parameter for the tool’s input schema.

Parameters:

  • name (Symbol, String)

    The parameter name.

  • type (Symbol) (defaults to: :string)

    The parameter type (:string, :integer, :number, :boolean, :array, :object).

  • desc (String, nil) (defaults to: nil)

    A human-readable description.

  • required (Boolean) (defaults to: false)

    Whether the parameter is required (default: false).

  • options (Hash)

    Additional JSON Schema keywords (enum:, default:, format:, items:, etc.).



95
96
97
98
99
100
101
102
103
# File 'lib/vector_mcp/tool.rb', line 95

def self.param(name, type: :string, desc: nil, required: false, **options)
  @params << {
    name: name.to_s,
    type: type,
    desc: desc,
    required: required,
    options: options
  }
end

.to_definitionVectorMCP::Definitions::Tool

Builds a VectorMCP::Definitions::Tool struct from the DSL metadata.

Returns:

Raises:

  • (ArgumentError)

    If the subclass is missing a description or #call method.



109
110
111
112
113
114
115
116
117
118
# File 'lib/vector_mcp/tool.rb', line 109

def self.to_definition
  validate_tool_class!

  VectorMCP::Definitions::Tool.new(
    tool_name,
    description,
    build_input_schema,
    build_handler
  )
end

.tool_name(name = nil) ⇒ String

Sets or retrieves the tool name.

When called with an argument, stores the name. When called without, returns the stored name or derives one from the class name.

Parameters:

  • name (String, Symbol, nil) (defaults to: nil)

    The tool name to set.

Returns:

  • (String)

    The tool name.



68
69
70
71
72
73
74
# File 'lib/vector_mcp/tool.rb', line 68

def self.tool_name(name = nil)
  if name
    @tool_name = name.to_s
  else
    @tool_name || derive_tool_name
  end
end

Instance Method Details

#call(_args, _session) ⇒ Object

The handler method that subclasses must implement.

Parameters:

  • _args (Hash)

    The tool arguments (string keys).

  • _session (VectorMCP::Session)

    The current session.

Returns:

  • (Object)

    The tool result.

Raises:

  • (NotImplementedError)


125
126
127
# File 'lib/vector_mcp/tool.rb', line 125

def call(_args, _session)
  raise NotImplementedError, "#{self.class.name} must implement #call(args, session)"
end