Class: SchwabMCP::Tools::QuotesTool

Inherits:
MCP::Tool
  • Object
show all
Extended by:
Loggable
Defined in:
lib/schwab_mcp/tools/quotes_tool.rb

Class Method Summary collapse

Methods included from Loggable

log_debug, log_error, log_fatal, log_info, log_warn, logger

Class Method Details

.call(symbols:, server_context:, fields: ["quote"], indicative: false) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 49

def self.call(symbols:, server_context:, fields: ["quote"], indicative: false)
  symbols = [symbols] if symbols.is_a?(String)

  log_info("Getting quotes for #{symbols.length} symbols: #{symbols.join(", ")}")

  begin
    client = SchwabClientFactory.create_client
    return SchwabClientFactory.client_error_response unless client

    log_debug("Making API request for symbols: #{symbols.join(", ")}")
    log_debug("Fields: #{fields || "all"}")
    log_debug("Indicative: #{indicative || "not specified"}")

    normalized_symbols = symbols.map(&:upcase)

    quotes_data = client.get_quotes(
      normalized_symbols,
      fields: fields,
      indicative: indicative,
      return_data_objects: true
    )

    unless quotes_data
      log_warn("No quote data objects returned for symbols: #{symbols.join(", ")}")
      return MCP::Tool::Response.new([{
                                       type: "text",
                                       text: "**No Data**: No quote data returned for symbols: " \
                                             "#{symbols.join(", ")}"
                                     }])
    end

    # Format quotes output
    formatted_quotes = format_quotes_data(quotes_data, normalized_symbols)
    log_info("Successfully retrieved quotes for #{symbols.length} symbols")

    symbol_list = normalized_symbols.join(", ")
    field_info = fields ? " (fields: #{fields.join(", ")})" : " (all fields)"
    indicative_info = indicative.nil? ? "" : " (indicative: #{indicative})"

    MCP::Tool::Response.new([{
                              type: "text",
                              text: "**Quotes for #{symbol_list}:**#{field_info}#{indicative_info}\n\n" \
                                    "#{formatted_quotes}"
                            }])
  rescue StandardError => e
    log_error("Error retrieving quotes for #{symbols.join(", ")}: #{e.message}")
    log_debug("Backtrace: #{e.backtrace.first(3).join("\n")}")
    MCP::Tool::Response.new([{
                              type: "text",
                              text: "**Error** retrieving quotes for #{symbols.join(", ")}: #{e.message}\n\n" \
                                    "#{e.backtrace.first(3).join("\n")}"
                            }])
  end
end

.format_equity_quote(obj) ⇒ Object

Format equity quote



150
151
152
153
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 150

def self.format_equity_quote(obj)
  "Equity: #{obj.symbol}\nLast: #{obj.last_price}  Bid: #{obj.bid_price}  Ask: #{obj.ask_price}  " \
  "Mark: #{obj.mark}  Net Chg: #{obj.net_change}  %Chg: #{obj.net_percent_change}  Vol: #{obj.total_volume}"
end

.format_hash_quotes(quotes_data, symbols) ⇒ Object

Format hash of quotes (symbol => quote_object)



119
120
121
122
123
124
125
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 119

def self.format_hash_quotes(quotes_data, symbols)
  formatted_lines = symbols.map do |symbol|
    quote_obj = quotes_data[symbol] || quotes_data[symbol.to_sym]
    quote_obj ? format_single_quote(quote_obj) : "#{symbol}: No data available"
  end
  formatted_lines.join("\n\n")
end

.format_index_quote(obj) ⇒ Object

Format index quote



156
157
158
159
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 156

def self.format_index_quote(obj)
  "Index: #{obj.symbol}\nLast: #{obj.last_price}  Bid: N/A  Ask: N/A  Mark: #{obj.mark}  " \
  "Net Chg: #{obj.net_change}  %Chg: #{obj.net_percent_change}  Vol: #{obj.total_volume}"
end

.format_option_quote(obj) ⇒ Object

Format option quote



142
143
144
145
146
147
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 142

def self.format_option_quote(obj)
  "Option: #{obj.symbol}\nLast: #{obj.last_price}  Bid: #{obj.bid_price}  Ask: #{obj.ask_price}  " \
  "Mark: #{obj.mark}  Delta: #{obj.delta}  Gamma: #{obj.gamma}  Vol: #{obj.volatility}  " \
  "OI: #{obj.open_interest}  Exp: #{obj.expiration_month}/#{obj.expiration_day}/#{obj.expiration_year}  " \
  "Strike: #{obj.strike_price}"
end

.format_quotes_data(quotes_data, symbols) ⇒ Object

Format the quotes data for display



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 105

def self.format_quotes_data(quotes_data, symbols)
  return "No quotes available" unless quotes_data

  case quotes_data
  when Hash
    format_hash_quotes(quotes_data, symbols)
  when Array
    quotes_data.map { |quote_obj| format_single_quote(quote_obj) }.join("\n\n")
  else
    format_single_quote(quotes_data)
  end
end

.format_single_quote(obj) ⇒ Object

Format a single quote object for display (reused from quote_tool.rb)



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/schwab_mcp/tools/quotes_tool.rb', line 128

def self.format_single_quote(obj)
  case obj
  when SchwabRb::DataObjects::OptionQuote
    format_option_quote(obj)
  when SchwabRb::DataObjects::EquityQuote
    format_equity_quote(obj)
  when SchwabRb::DataObjects::IndexQuote
    format_index_quote(obj)
  else
    obj.respond_to?(:to_h) ? obj.to_h.inspect : obj.inspect
  end
end