schwab_mcp 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ module SchwabMCP
19
19
  },
20
20
  strategy_type: {
21
21
  type: "string",
22
- enum: %w[single ironcondor vertical],
22
+ enum: %w[SINGLE VERTICAL IRON_CONDOR],
23
23
  description: "Type of options strategy to place"
24
24
  },
25
25
  price: {
@@ -101,11 +101,8 @@ module SchwabMCP
101
101
  account_result = resolve_account_details(client, params[:account_name])
102
102
  return account_result if account_result.is_a?(MCP::Tool::Response)
103
103
 
104
- account_id, account_hash = account_result
105
-
106
104
  order_builder = SchwabRb::Orders::OrderFactory.build(
107
105
  strategy_type: params[:strategy_type],
108
- account_number: account_id,
109
106
  price: params[:price],
110
107
  quantity: params[:quantity] || 1,
111
108
  order_instruction: (params[:order_instruction] || "open").to_sym,
@@ -123,7 +120,7 @@ module SchwabMCP
123
120
  )
124
121
 
125
122
  log_debug("Making place order API request")
126
- response = client.place_order(account_hash, order_builder)
123
+ response = client.place_order(account_name: params[:account_name], order: order_builder)
127
124
 
128
125
  if response && (200..299).include?(response.status)
129
126
  log_info("Successfully placed #{params[:strategy_type]} order (HTTP #{response.status})")
@@ -157,77 +154,62 @@ module SchwabMCP
157
154
  end
158
155
 
159
156
  def self.resolve_account_details(client, account_name)
160
- account_id = ENV[account_name]
161
- unless account_id
162
- available_accounts = ENV.keys.select { |key| key.end_with?("_ACCOUNT") }
163
- log_error("Account name '#{account_name}' not found in environment variables")
164
- return MCP::Tool::Response.new([{
165
- type: "text",
166
- text: "**Error**: Account name '#{account_name}' not found in environment variables.\n\nAvailable accounts: #{available_accounts.join(", ")}\n\nTo configure: Set ENV['#{account_name}'] to your account ID."
167
- }])
168
- end
169
-
170
- log_debug("Found account ID: [REDACTED] for account name: #{account_name}")
171
- log_debug("Fetching account numbers mapping")
172
-
173
- account_numbers = client.get_account_numbers
174
-
175
- unless account_numbers
176
- log_error("Failed to retrieve account numbers")
177
- return MCP::Tool::Response.new([{
178
- type: "text",
179
- text: "**Error**: Failed to retrieve account numbers from Schwab API"
180
- }])
181
- end
182
-
183
- log_debug("Account mappings retrieved (#{account_numbers.size} accounts found)")
184
-
185
- account_hash = account_numbers.find_hash_value(account_id)
186
-
187
- unless account_hash
188
- log_error("Account ID not found in available accounts")
157
+ available_accounts = client.available_account_names
158
+ unless available_accounts.include?(account_name)
159
+ log_error("Account name '#{account_name}' not found in configured accounts")
189
160
  return MCP::Tool::Response.new([{
190
161
  type: "text",
191
- text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
162
+ text: "**Error**: Account name '#{account_name}' not found in configured accounts.\n\nAvailable accounts: #{available_accounts.join(", ")}\n\nTo configure: Add the account to your schwab_rb configuration file."
192
163
  }])
193
164
  end
194
165
 
195
- log_debug("Found account hash for account name: #{account_name}")
196
- [account_id, account_hash]
166
+ log_debug("Using account name: #{account_name}")
167
+ account_name
197
168
  end
198
169
 
199
170
  def self.validate_strategy_params(params)
200
- case params[:strategy_type]
201
- when "ironcondor"
171
+ strategy = params[:strategy_type].to_s.upcase
172
+ case strategy
173
+ when 'IRON_CONDOR'
202
174
  required_fields = %i[put_short_symbol put_long_symbol call_short_symbol call_long_symbol]
203
175
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
204
176
  unless missing_fields.empty?
205
177
  raise ArgumentError, "Iron condor strategy requires: #{missing_fields.join(", ")}"
206
178
  end
207
- when "callspread", "putspread"
179
+ when 'VERTICAL'
208
180
  required_fields = %i[short_leg_symbol long_leg_symbol]
209
181
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
210
182
  unless missing_fields.empty?
211
183
  raise ArgumentError, "#{params[:strategy_type]} strategy requires: #{missing_fields.join(", ")}"
212
184
  end
185
+ when 'SINGLE'
186
+ required_fields = %i[symbol]
187
+ missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
188
+ unless missing_fields.empty?
189
+ raise ArgumentError, "#{params[:strategy_type]} strategy requires: #{missing_fields.join(", ")}"
190
+ end
213
191
  else
214
192
  raise ArgumentError, "Unsupported strategy type: #{params[:strategy_type]}"
215
193
  end
216
194
  end
217
195
 
218
196
  def self.format_place_order_response(response, params)
219
- strategy_summary = case params[:strategy_type]
220
- when "ironcondor"
221
- "**Iron Condor Order Placed**\n" \
222
- "- Put Short: #{params[:put_short_symbol]}\n" \
223
- "- Put Long: #{params[:put_long_symbol]}\n" \
224
- "- Call Short: #{params[:call_short_symbol]}\n" \
225
- "- Call Long: #{params[:call_long_symbol]}\n"
226
- when "callspread", "putspread"
227
- "**#{params[:strategy_type].capitalize} Order Placed**\n" \
228
- "- Short Leg: #{params[:short_leg_symbol]}\n" \
229
- "- Long Leg: #{params[:long_leg_symbol]}\n"
230
- end
197
+ strategy = params[:strategy_type].to_s.upcase
198
+ strategy_summary = case strategy
199
+ when 'IRON_CONDOR'
200
+ "**Iron Condor Order Placed**\n" \
201
+ "- Put Short: #{params[:put_short_symbol]}\n" \
202
+ "- Put Long: #{params[:put_long_symbol]}\n" \
203
+ "- Call Short: #{params[:call_short_symbol]}\n" \
204
+ "- Call Long: #{params[:call_long_symbol]}\n"
205
+ when 'VERTICAL'
206
+ "**Vertical Spread Order Placed**\n" \
207
+ "- Short Leg: #{params[:short_leg_symbol]}\n" \
208
+ "- Long Leg: #{params[:long_leg_symbol]}\n"
209
+ when 'SINGLE'
210
+ "**Single Option Order Placed**\n" \
211
+ "- Symbol: #{params[:symbol]}\n"
212
+ end
231
213
 
232
214
  friendly_name = params[:account_name].gsub("_ACCOUNT", "").split("_").map(&:capitalize).join(" ")
233
215
 
@@ -20,7 +20,7 @@ module SchwabMCP
20
20
  },
21
21
  strategy_type: {
22
22
  type: "string",
23
- enum: ["ironcondor", "vertical", "single"],
23
+ enum: %w[SINGLE VERTICAL IRON_CONDOR],
24
24
  description: "Type of options strategy to preview"
25
25
  },
26
26
  price: {
@@ -106,11 +106,8 @@ module SchwabMCP
106
106
  account_result = resolve_account_details(client, params[:account_name])
107
107
  return account_result if account_result.is_a?(MCP::Tool::Response)
108
108
 
109
- account_id, account_hash = account_result
110
-
111
109
  order_builder = SchwabRb::Orders::OrderFactory.build(
112
110
  strategy_type: params[:strategy_type],
113
- account_number: account_id,
114
111
  price: params[:price],
115
112
  quantity: params[:quantity] || 1,
116
113
  order_instruction: (params[:order_instruction] || "open").to_sym,
@@ -124,11 +121,11 @@ module SchwabMCP
124
121
  short_leg_symbol: params[:short_leg_symbol],
125
122
  long_leg_symbol: params[:long_leg_symbol],
126
123
  # Single option params
127
- symbol: params[:symbol],
124
+ symbol: params[:symbol]
128
125
  )
129
126
 
130
127
  log_debug("Making preview order API request")
131
- response = client.preview_order(account_hash, order_builder, return_data_objects: true)
128
+ response = client.preview_order(account_name: params[:account_name], order: order_builder, return_data_objects: true)
132
129
 
133
130
  if response
134
131
  log_info("Successfully previewed #{params[:strategy_type]} order")
@@ -159,59 +156,36 @@ module SchwabMCP
159
156
  private
160
157
 
161
158
  def self.resolve_account_details(client, account_name)
162
- account_id = ENV[account_name]
163
- unless account_id
164
- available_accounts = ENV.keys.select { |key| key.end_with?('_ACCOUNT') }
165
- log_error("Account name '#{account_name}' not found in environment variables")
166
- error_msg = "**Error**: Account name '#{account_name}' not found in environment variables.\n\nAvailable accounts: #{available_accounts.join(', ')}\n\nTo configure: Set ENV['#{account_name}'] to your account ID."
167
- return MCP::Tool::Response.new([{
168
- type: "text",
169
- text: Redactor.redact_formatted_text(error_msg)
170
- }])
171
- end
172
-
173
- log_debug("Found account ID: [REDACTED] for account name: #{account_name}")
174
- log_debug("Fetching account numbers mapping")
175
-
176
- account_numbers = client.get_account_numbers(return_data_objects: true)
177
- unless account_numbers && !account_numbers.empty?
178
- log_error("Failed to retrieve account numbers or no accounts returned")
179
- error_msg = "**Error**: Failed to retrieve account numbers from Schwab API or no accounts returned"
180
- return MCP::Tool::Response.new([{
181
- type: "text",
182
- text: Redactor.redact_formatted_text(error_msg)
183
- }])
184
- end
185
-
186
- mapping = account_numbers.accounts.find { |acct| acct.account_number == account_id }
187
- unless mapping
188
- log_error("Account ID not found in available accounts")
189
- error_msg = "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
159
+ available_accounts = client.available_account_names
160
+ unless available_accounts.include?(account_name)
161
+ log_error("Account name '#{account_name}' not found in configured accounts")
162
+ error_msg = "**Error**: Account name '#{account_name}' not found in configured accounts.\n\nAvailable accounts: #{available_accounts.join(', ')}\n\nTo configure: Add the account to your schwab_rb configuration file."
190
163
  return MCP::Tool::Response.new([{
191
164
  type: "text",
192
165
  text: Redactor.redact_formatted_text(error_msg)
193
166
  }])
194
167
  end
195
168
 
196
- log_debug("Found account hash for account name: #{account_name}")
197
- [account_id, mapping.hash_value]
169
+ log_debug("Using account name: #{account_name}")
170
+ account_name
198
171
  end
199
172
 
200
173
  def self.validate_strategy_params(params)
201
- case params[:strategy_type]
202
- when 'ironcondor'
174
+ strategy = params[:strategy_type].to_s.upcase
175
+ case strategy
176
+ when 'IRON_CONDOR'
203
177
  required_fields = [:put_short_symbol, :put_long_symbol, :call_short_symbol, :call_long_symbol]
204
178
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
205
179
  unless missing_fields.empty?
206
180
  raise ArgumentError, "Iron condor strategy requires: #{missing_fields.join(', ')}"
207
181
  end
208
- when 'vertical'
182
+ when 'VERTICAL'
209
183
  required_fields = [:short_leg_symbol, :long_leg_symbol]
210
184
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
211
185
  unless missing_fields.empty?
212
186
  raise ArgumentError, "#{params[:strategy_type]} strategy requires: #{missing_fields.join(', ')}"
213
187
  end
214
- when 'single'
188
+ when 'SINGLE'
215
189
  required_fields = [:symbol]
216
190
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
217
191
  unless missing_fields.empty?
@@ -222,58 +196,22 @@ module SchwabMCP
222
196
  end
223
197
  end
224
198
 
225
- def self.format_preview_response(response_body, params)
226
- parsed = JSON.parse(response_body)
227
- redacted_data = Redactor.redact(parsed)
228
-
229
- begin
230
- strategy_summary = case params[:strategy_type]
231
- when 'ironcondor'
232
- "**Iron Condor Preview**\n" \
233
- "- Put Short: #{params[:put_short_symbol]}\n" \
234
- "- Put Long: #{params[:put_long_symbol]}\n" \
235
- "- Call Short: #{params[:call_short_symbol]}\n" \
236
- "- Call Long: #{params[:call_long_symbol]}\n"
237
- when 'vertical'
238
- "**Vertical Preview**\n" \
239
- "- Short Leg: #{params[:short_leg_symbol]}\n" \
240
- "- Long Leg: #{params[:long_leg_symbol]}\n"
241
- when 'single'
242
- "**Single Option Preview**\n" \
243
- "- Symbol: #{params[:symbol]}\n"
244
- end
245
-
246
- friendly_name = params[:account_name].gsub('_ACCOUNT', '').split('_').map(&:capitalize).join(' ')
247
-
248
- order_details = "**Order Details:**\n" \
249
- "- Strategy: #{params[:strategy_type]}\n" \
250
- "- Action: #{params[:order_instruction] || 'open'}\n" \
251
- "- Quantity: #{params[:quantity] || 1}\n" \
252
- "- Price: $#{params[:price]}\n" \
253
- "- Account: #{friendly_name} (#{params[:account_name]})\n\n"
254
-
255
- full_response = "**Schwab API Preview Response:**\n\n```json\n#{JSON.pretty_generate(redacted_data)}\n```"
256
-
257
- "#{strategy_summary}\n#{order_details}#{full_response}"
258
- rescue JSON::ParserError
259
- "**Order Preview Response:**\n\n```\n#{JSON.pretty_generate(redacted_data)}\n```"
260
- end
261
- end
262
199
  def self.format_preview_response(order_preview, params)
263
200
  # order_preview is a SchwabRb::DataObjects::OrderPreview
264
201
  begin
265
- strategy_summary = case params[:strategy_type]
266
- when 'ironcondor'
202
+ strategy = params[:strategy_type].to_s.upcase
203
+ strategy_summary = case strategy
204
+ when 'IRON_CONDOR'
267
205
  "**Iron Condor Preview**\n" \
268
206
  "- Put Short: #{params[:put_short_symbol]}\n" \
269
207
  "- Put Long: #{params[:put_long_symbol]}\n" \
270
208
  "- Call Short: #{params[:call_short_symbol]}\n" \
271
209
  "- Call Long: #{params[:call_long_symbol]}\n"
272
- when 'vertical'
273
- "**Vertical Preview**\n" \
210
+ when 'VERTICAL'
211
+ "**Vertical Spread Preview**\n" \
274
212
  "- Short Leg: #{params[:short_leg_symbol]}\n" \
275
213
  "- Long Leg: #{params[:long_leg_symbol]}\n"
276
- when 'single'
214
+ when 'SINGLE'
277
215
  "**Single Option Preview**\n" \
278
216
  "- Symbol: #{params[:symbol]}\n"
279
217
  end
@@ -16,8 +16,8 @@ module SchwabMCP
16
16
  properties: {
17
17
  symbol: {
18
18
  type: "string",
19
- description: "Instrument symbol (e.g., 'AAPL', 'TSLA')",
20
- pattern: "^[A-Za-z]{1,5}$"
19
+ description: "Instrument symbol (e.g., 'AAPL', 'TSLA', '$SPX')",
20
+ pattern: '^[\$\^]?[A-Za-z0-9]{1,5}$'
21
21
  }
22
22
  },
23
23
  required: ["symbol"]
@@ -38,7 +38,7 @@ module SchwabMCP
38
38
  return SchwabClientFactory.client_error_response unless client
39
39
 
40
40
  log_debug("Making API request for symbol: #{symbol}")
41
- quote_obj = client.get_quote(symbol.upcase, return_data_objects: true)
41
+ quote_obj = client.get_quote(symbol.upcase)
42
42
 
43
43
  unless quote_obj
44
44
  log_warn("No quote data object returned for symbol: #{symbol}")
@@ -48,7 +48,6 @@ module SchwabMCP
48
48
  }])
49
49
  end
50
50
 
51
- # Format output based on quote type
52
51
  formatted = format_quote_object(quote_obj)
53
52
  log_info("Successfully retrieved quote for #{symbol}")
54
53
  MCP::Tool::Response.new([{
@@ -66,7 +65,6 @@ module SchwabMCP
66
65
  end
67
66
  end
68
67
 
69
- # Format the quote object for display
70
68
  def self.format_quote_object(obj)
71
69
  case obj
72
70
  when SchwabRb::DataObjects::OptionQuote
@@ -23,7 +23,7 @@ module SchwabMCP
23
23
  },
24
24
  strategy_type: {
25
25
  type: "string",
26
- enum: %w[ironcondor callspread putspread],
26
+ enum: %w[SINGLE VERTICAL IRON_CONDOR],
27
27
  description: "Type of options strategy for the replacement order"
28
28
  },
29
29
  price: {
@@ -105,11 +105,8 @@ module SchwabMCP
105
105
  account_result = resolve_account_details(client, params[:account_name])
106
106
  return account_result if account_result.is_a?(MCP::Tool::Response)
107
107
 
108
- account_id, account_hash = account_result
109
-
110
108
  order_builder = SchwabRb::Orders::OrderFactory.build(
111
109
  strategy_type: params[:strategy_type],
112
- account_number: account_id,
113
110
  price: params[:price],
114
111
  quantity: params[:quantity] || 1,
115
112
  order_instruction: (params[:order_instruction] || "open").to_sym,
@@ -127,7 +124,7 @@ module SchwabMCP
127
124
  )
128
125
 
129
126
  log_debug("Making replace order API request for order ID: #{params[:order_id]}")
130
- response = client.replace_order(account_hash, params[:order_id], order_builder)
127
+ response = client.replace_order(account_name: params[:account_name], order_id: params[:order_id], order: order_builder)
131
128
 
132
129
  if response && (200..299).include?(response.status)
133
130
  log_info("Successfully replaced order #{params[:order_id]} with #{params[:strategy_type]} order (HTTP #{response.status})")
@@ -161,77 +158,62 @@ module SchwabMCP
161
158
  end
162
159
 
163
160
  def self.resolve_account_details(client, account_name)
164
- account_id = ENV[account_name]
165
- unless account_id
166
- available_accounts = ENV.keys.select { |key| key.end_with?("_ACCOUNT") }
167
- log_error("Account name '#{account_name}' not found in environment variables")
168
- return MCP::Tool::Response.new([{
169
- type: "text",
170
- text: "**Error**: Account name '#{account_name}' not found in environment variables.\n\nAvailable accounts: #{available_accounts.join(", ")}\n\nTo configure: Set ENV['#{account_name}'] to your account ID."
171
- }])
172
- end
173
-
174
- log_debug("Found account ID: [REDACTED] for account name: #{account_name}")
175
- log_debug("Fetching account numbers mapping")
176
-
177
- account_numbers = client.get_account_numbers
178
-
179
- unless account_numbers
180
- log_error("Failed to retrieve account numbers")
181
- return MCP::Tool::Response.new([{
182
- type: "text",
183
- text: "**Error**: Failed to retrieve account numbers from Schwab API"
184
- }])
185
- end
186
-
187
- log_debug("Account mappings retrieved (#{account_numbers.size} accounts found)")
188
-
189
- account_hash = account_numbers.find_hash_value(account_id)
190
-
191
- unless account_hash
192
- log_error("Account ID not found in available accounts")
161
+ available_accounts = client.available_account_names
162
+ unless available_accounts.include?(account_name)
163
+ log_error("Account name '#{account_name}' not found in configured accounts")
193
164
  return MCP::Tool::Response.new([{
194
165
  type: "text",
195
- text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
166
+ text: "**Error**: Account name '#{account_name}' not found in configured accounts.\n\nAvailable accounts: #{available_accounts.join(", ")}\n\nTo configure: Add the account to your schwab_rb configuration file."
196
167
  }])
197
168
  end
198
169
 
199
- log_debug("Found account hash for account name: #{account_name}")
200
- [account_id, account_hash]
170
+ log_debug("Using account name: #{account_name}")
171
+ account_name
201
172
  end
202
173
 
203
174
  def self.validate_strategy_params(params)
204
- case params[:strategy_type]
205
- when "ironcondor"
175
+ strategy = params[:strategy_type].to_s.upcase
176
+ case strategy
177
+ when 'IRON_CONDOR'
206
178
  required_fields = %i[put_short_symbol put_long_symbol call_short_symbol call_long_symbol]
207
179
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
208
180
  unless missing_fields.empty?
209
181
  raise ArgumentError, "Iron condor strategy requires: #{missing_fields.join(", ")}"
210
182
  end
211
- when "callspread", "putspread"
183
+ when 'VERTICAL'
212
184
  required_fields = %i[short_leg_symbol long_leg_symbol]
213
185
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
214
186
  unless missing_fields.empty?
215
187
  raise ArgumentError, "#{params[:strategy_type]} strategy requires: #{missing_fields.join(", ")}"
216
188
  end
189
+ when 'SINGLE'
190
+ required_fields = %i[symbol]
191
+ missing_fields = required_fields.select { |field| params[field].nil? || params[field].empty? }
192
+ unless missing_fields.empty?
193
+ raise ArgumentError, "#{params[:strategy_type]} strategy requires: #{missing_fields.join(", ")}"
194
+ end
217
195
  else
218
196
  raise ArgumentError, "Unsupported strategy type: #{params[:strategy_type]}"
219
197
  end
220
198
  end
221
199
 
222
200
  def self.format_replace_order_response(response, params)
223
- strategy_summary = case params[:strategy_type]
224
- when "ironcondor"
225
- "**Iron Condor Order Replaced**\n" \
226
- "- Put Short: #{params[:put_short_symbol]}\n" \
227
- "- Put Long: #{params[:put_long_symbol]}\n" \
228
- "- Call Short: #{params[:call_short_symbol]}\n" \
229
- "- Call Long: #{params[:call_long_symbol]}\n"
230
- when "callspread", "putspread"
231
- "**#{params[:strategy_type].capitalize} Order Replaced**\n" \
232
- "- Short Leg: #{params[:short_leg_symbol]}\n" \
233
- "- Long Leg: #{params[:long_leg_symbol]}\n"
234
- end
201
+ strategy = params[:strategy_type].to_s.upcase
202
+ strategy_summary = case strategy
203
+ when 'IRON_CONDOR'
204
+ "**Iron Condor Order Replaced**\n" \
205
+ "- Put Short: #{params[:put_short_symbol]}\n" \
206
+ "- Put Long: #{params[:put_long_symbol]}\n" \
207
+ "- Call Short: #{params[:call_short_symbol]}\n" \
208
+ "- Call Long: #{params[:call_long_symbol]}\n"
209
+ when 'VERTICAL'
210
+ "**Vertical Spread Order Replaced**\n" \
211
+ "- Short Leg: #{params[:short_leg_symbol]}\n" \
212
+ "- Long Leg: #{params[:long_leg_symbol]}\n"
213
+ when 'SINGLE'
214
+ "**Single Option Order Replaced**\n" \
215
+ "- Symbol: #{params[:symbol]}\n"
216
+ end
235
217
 
236
218
  friendly_name = params[:account_name].gsub("_ACCOUNT", "").split("_").map(&:capitalize).join(" ")
237
219
 
@@ -51,50 +51,24 @@ module SchwabMCP
51
51
  client = SchwabClientFactory.create_client
52
52
  return SchwabClientFactory.client_error_response unless client
53
53
 
54
- account_id = ENV[account_name]
55
- unless account_id
56
- available_accounts = ENV.keys.select { |key| key.end_with?('_ACCOUNT') }
57
- log_error("Account name '#{account_name}' not found in environment variables")
54
+ available_accounts = client.available_account_names
55
+ unless available_accounts.include?(account_name)
56
+ log_error("Account name '#{account_name}' not found in configured accounts")
58
57
  return MCP::Tool::Response.new([{
59
58
  type: "text",
60
- text: "**Error**: Account name '#{account_name}' not found in environment variables.\n\nAvailable accounts: #{available_accounts.join(', ')}\n\nTo configure: Set ENV['#{account_name}'] to your account ID."
59
+ text: "**Error**: Account name '#{account_name}' not found in configured accounts.\n\nAvailable accounts: #{available_accounts.join(', ')}\n\nTo configure: Add the account to your schwab_rb configuration file."
61
60
  }])
62
61
  end
63
62
 
64
- log_debug("Found account ID: [REDACTED] for account name: #{account_name}")
65
- log_debug("Fetching account numbers mapping")
66
-
67
- account_numbers = client.get_account_numbers
68
-
69
- unless account_numbers
70
- log_error("Failed to retrieve account numbers")
71
- return MCP::Tool::Response.new([{
72
- type: "text",
73
- text: "**Error**: Failed to retrieve account numbers from Schwab API"
74
- }])
75
- end
76
-
77
- log_debug("Account numbers retrieved (#{account_numbers.size} accounts found)")
78
-
79
- account_hash = account_numbers.find_hash_value(account_id)
80
-
81
- unless account_hash
82
- log_error("Account ID not found in available accounts")
83
- return MCP::Tool::Response.new([{
84
- type: "text",
85
- text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
86
- }])
87
- end
88
-
89
- log_debug("Found account hash for account ID: #{account_name}")
63
+ log_debug("Using account name: #{account_name}")
90
64
  log_debug("Fetching account information with fields: #{fields}")
91
65
 
92
- account = client.get_account(account_hash, fields: fields)
66
+ account = client.get_account(account_name: account_name, fields: fields)
93
67
 
94
68
  if account
95
69
  log_info("Successfully retrieved account information for #{account_name}")
96
70
 
97
- formatted_response = format_account_data(account, account_name, account_id)
71
+ formatted_response = format_account_data(account, account_name)
98
72
 
99
73
  MCP::Tool::Response.new([{
100
74
  type: "text",
@@ -126,7 +100,7 @@ module SchwabMCP
126
100
 
127
101
  private
128
102
 
129
- def self.format_account_data(account, account_name, account_id)
103
+ def self.format_account_data(account, account_name)
130
104
  friendly_name = account_name.gsub('_ACCOUNT', '').split('_').map(&:capitalize).join(' ')
131
105
 
132
106
  formatted = "**Account Information for #{friendly_name} (#{account_name}):**\n\n"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwabMCP
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/schwab_mcp.rb CHANGED
@@ -11,7 +11,6 @@ require_relative "schwab_mcp/tools/quotes_tool"
11
11
  require_relative "schwab_mcp/tools/option_chain_tool"
12
12
  require_relative "schwab_mcp/tools/help_tool"
13
13
  require_relative "schwab_mcp/tools/schwab_account_details_tool"
14
- require_relative "schwab_mcp/tools/list_schwab_accounts_tool"
15
14
  require_relative "schwab_mcp/tools/list_account_orders_tool"
16
15
  require_relative "schwab_mcp/tools/list_account_transactions_tool"
17
16
  require_relative "schwab_mcp/tools/get_order_tool"
@@ -22,6 +21,7 @@ require_relative "schwab_mcp/tools/replace_order_tool"
22
21
  require_relative "schwab_mcp/tools/list_movers_tool"
23
22
  require_relative "schwab_mcp/tools/get_market_hours_tool"
24
23
  require_relative "schwab_mcp/tools/get_price_history_tool"
24
+ require_relative "schwab_mcp/tools/get_account_names_tool"
25
25
  require_relative "schwab_mcp/loggable"
26
26
  require_relative "schwab_mcp/schwab_client_factory"
27
27
 
@@ -35,7 +35,6 @@ module SchwabMCP
35
35
  Tools::OptionChainTool,
36
36
  Tools::HelpTool,
37
37
  Tools::SchwabAccountDetailsTool,
38
- Tools::ListSchwabAccountsTool,
39
38
  Tools::ListAccountOrdersTool,
40
39
  Tools::ListAccountTransactionsTool,
41
40
  Tools::GetOrderTool,
@@ -45,35 +44,38 @@ module SchwabMCP
45
44
  Tools::ReplaceOrderTool,
46
45
  Tools::ListMoversTool,
47
46
  Tools::GetMarketHoursTool,
48
- Tools::GetPriceHistoryTool
47
+ Tools::GetPriceHistoryTool,
48
+ Tools::GetAccountNamesTool
49
49
  ].freeze
50
50
 
51
51
  class Server
52
52
  include Loggable
53
53
 
54
54
  def initialize
55
- configure_schwab_rb_logging
55
+ configure_schwab_rb
56
56
 
57
57
  @server = MCP::Server.new(
58
58
  name: "schwab_mcp_server",
59
59
  version: SchwabMCP::VERSION,
60
60
  tools: TOOLS,
61
+ server_context: { user: "foobar" }
61
62
  )
62
63
  end
63
64
 
64
65
  def start
65
66
  configure_mcp
66
- log_info("🚀 Starting Schwab MCP Server #{SchwabMCP::VERSION}")
67
- log_info("📊 Available tools: #{TOOLS.map { |tool| tool.name.split('::').last }.join(', ')}")
68
- log_info("📝 Logs will be written to: #{log_file_path}")
67
+ log_info("Starting Schwab MCP Server #{SchwabMCP::VERSION}")
68
+ log_info("Available tools: #{TOOLS.map { |tool| tool.name.split('::').last }.join(', ')}")
69
+ log_info("Logs will be written to: #{log_file_path}")
69
70
  transport = MCP::Transports::StdioTransport.new(@server)
70
71
  transport.open
71
72
  end
72
73
 
73
74
  private
74
75
 
75
- def configure_schwab_rb_logging
76
+ def configure_schwab_rb
76
77
  SchwabRb.configure do |config|
78
+ config.schwab_home = ENV['SCHWAB_HOME']
77
79
  config.logger = SchwabMCP::Logger.instance
78
80
  config.log_level = ENV.fetch('LOG_LEVEL', 'INFO').upcase
79
81
  end