schwab_mcp 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.copilotignore +3 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +157 -0
  9. data/Rakefile +12 -0
  10. data/exe/schwab_mcp +19 -0
  11. data/exe/schwab_token_refresh +38 -0
  12. data/exe/schwab_token_reset +49 -0
  13. data/lib/schwab_mcp/loggable.rb +31 -0
  14. data/lib/schwab_mcp/logger.rb +62 -0
  15. data/lib/schwab_mcp/option_chain_filter.rb +213 -0
  16. data/lib/schwab_mcp/orders/iron_condor_order.rb +87 -0
  17. data/lib/schwab_mcp/orders/order_factory.rb +40 -0
  18. data/lib/schwab_mcp/orders/vertical_order.rb +62 -0
  19. data/lib/schwab_mcp/redactor.rb +210 -0
  20. data/lib/schwab_mcp/resources/.keep +0 -0
  21. data/lib/schwab_mcp/tools/cancel_order_tool.rb +226 -0
  22. data/lib/schwab_mcp/tools/get_market_hours_tool.rb +104 -0
  23. data/lib/schwab_mcp/tools/get_order_tool.rb +263 -0
  24. data/lib/schwab_mcp/tools/get_price_history_tool.rb +203 -0
  25. data/lib/schwab_mcp/tools/help_tool.rb +406 -0
  26. data/lib/schwab_mcp/tools/list_account_orders_tool.rb +295 -0
  27. data/lib/schwab_mcp/tools/list_account_transactions_tool.rb +311 -0
  28. data/lib/schwab_mcp/tools/list_movers_tool.rb +125 -0
  29. data/lib/schwab_mcp/tools/list_schwab_accounts_tool.rb +162 -0
  30. data/lib/schwab_mcp/tools/option_chain_tool.rb +274 -0
  31. data/lib/schwab_mcp/tools/option_strategy_finder_tool.rb +378 -0
  32. data/lib/schwab_mcp/tools/place_order_tool.rb +305 -0
  33. data/lib/schwab_mcp/tools/preview_order_tool.rb +259 -0
  34. data/lib/schwab_mcp/tools/quote_tool.rb +77 -0
  35. data/lib/schwab_mcp/tools/quotes_tool.rb +110 -0
  36. data/lib/schwab_mcp/tools/replace_order_tool.rb +312 -0
  37. data/lib/schwab_mcp/tools/schwab_account_details_tool.rb +208 -0
  38. data/lib/schwab_mcp/version.rb +5 -0
  39. data/lib/schwab_mcp.rb +107 -0
  40. data/sig/schwab_mcp.rbs +4 -0
  41. data/start_mcp_server.sh +4 -0
  42. metadata +115 -0
@@ -0,0 +1,203 @@
1
+ require "mcp"
2
+ require "schwab_rb"
3
+ require "json"
4
+ require "date"
5
+ require_relative "../loggable"
6
+
7
+ module SchwabMCP
8
+ module Tools
9
+ class GetPriceHistoryTool < MCP::Tool
10
+ extend Loggable
11
+ description "Get price history data for an instrument symbol using Schwab API"
12
+
13
+ input_schema(
14
+ properties: {
15
+ symbol: {
16
+ type: "string",
17
+ description: "Instrument symbol (e.g., 'AAPL', 'TSLA', '$SPX')",
18
+ pattern: "^[\\$A-Za-z]{1,6}$"
19
+ },
20
+ period_type: {
21
+ type: "string",
22
+ description: "Type of period for the price history",
23
+ enum: ["day", "month", "year", "ytd"]
24
+ },
25
+ period: {
26
+ type: "integer",
27
+ description: "Number of periods to retrieve. Valid values depend on period_type: day(1-10), month(1,2,3,6), year(1,2,3,5,10,15,20), ytd(1)"
28
+ },
29
+ frequency_type: {
30
+ type: "string",
31
+ description: "Type of frequency for the price history",
32
+ enum: ["minute", "daily", "weekly", "monthly"]
33
+ },
34
+ frequency: {
35
+ type: "integer",
36
+ description: "Frequency of data points. Valid values depend on frequency_type: minute(1,5,10,15,30), daily(1), weekly(1), monthly(1)"
37
+ },
38
+ start_datetime: {
39
+ type: "string",
40
+ description: "Start date/time in ISO format (e.g., '2024-01-01T00:00:00Z'). Cannot be used with period/period_type."
41
+ },
42
+ end_datetime: {
43
+ type: "string",
44
+ description: "End date/time in ISO format (e.g., '2024-01-31T23:59:59Z'). Cannot be used with period/period_type."
45
+ },
46
+ need_extended_hours_data: {
47
+ type: "boolean",
48
+ description: "Include extended hours data (pre-market and after-hours)"
49
+ },
50
+ need_previous_close: {
51
+ type: "boolean",
52
+ description: "Include previous day's closing price"
53
+ }
54
+ },
55
+ required: ["symbol"]
56
+ )
57
+
58
+ annotations(
59
+ title: "Get Price History",
60
+ read_only_hint: true,
61
+ destructive_hint: false,
62
+ idempotent_hint: true
63
+ )
64
+
65
+ def self.call(symbol:, period_type: nil, period: nil, frequency_type: nil, frequency: nil,
66
+ start_datetime: nil, end_datetime: nil, need_extended_hours_data: nil,
67
+ need_previous_close: nil, server_context:)
68
+ log_info("Getting price history for symbol: #{symbol}")
69
+
70
+ begin
71
+ client = SchwabRb::Auth.init_client_easy(
72
+ ENV['SCHWAB_API_KEY'],
73
+ ENV['SCHWAB_APP_SECRET'],
74
+ ENV['SCHWAB_CALLBACK_URI'],
75
+ ENV['TOKEN_PATH']
76
+ )
77
+
78
+ unless client
79
+ log_error("Failed to initialize Schwab client")
80
+ return MCP::Tool::Response.new([{
81
+ type: "text",
82
+ text: "**Error**: Failed to initialize Schwab client. Check your credentials."
83
+ }])
84
+ end
85
+
86
+ parsed_start = nil
87
+ parsed_end = nil
88
+
89
+ if start_datetime
90
+ begin
91
+ parsed_start = DateTime.parse(start_datetime)
92
+ rescue ArgumentError
93
+ return MCP::Tool::Response.new([{
94
+ type: "text",
95
+ text: "**Error**: Invalid start_datetime format. Use ISO format like '2024-01-01T00:00:00Z'"
96
+ }])
97
+ end
98
+ end
99
+
100
+ if end_datetime
101
+ begin
102
+ parsed_end = DateTime.parse(end_datetime)
103
+ rescue ArgumentError
104
+ return MCP::Tool::Response.new([{
105
+ type: "text",
106
+ text: "**Error**: Invalid end_datetime format. Use ISO format like '2024-01-31T23:59:59Z'"
107
+ }])
108
+ end
109
+ end
110
+
111
+ if (start_datetime || end_datetime) && (period_type || period)
112
+ return MCP::Tool::Response.new([{
113
+ type: "text",
114
+ text: "**Error**: Cannot use start_datetime/end_datetime with period_type/period. Choose one approach."
115
+ }])
116
+ end
117
+
118
+ period_type_enum = nil
119
+ frequency_type_enum = nil
120
+
121
+ if period_type
122
+ case period_type
123
+ when "day"
124
+ period_type_enum = SchwabRb::PriceHistory::PeriodTypes::DAY
125
+ when "month"
126
+ period_type_enum = SchwabRb::PriceHistory::PeriodTypes::MONTH
127
+ when "year"
128
+ period_type_enum = SchwabRb::PriceHistory::PeriodTypes::YEAR
129
+ when "ytd"
130
+ period_type_enum = SchwabRb::PriceHistory::PeriodTypes::YEAR_TO_DATE
131
+ end
132
+ end
133
+
134
+ if frequency_type
135
+ case frequency_type
136
+ when "minute"
137
+ frequency_type_enum = SchwabRb::PriceHistory::FrequencyTypes::MINUTE
138
+ when "daily"
139
+ frequency_type_enum = SchwabRb::PriceHistory::FrequencyTypes::DAILY
140
+ when "weekly"
141
+ frequency_type_enum = SchwabRb::PriceHistory::FrequencyTypes::WEEKLY
142
+ when "monthly"
143
+ frequency_type_enum = SchwabRb::PriceHistory::FrequencyTypes::MONTHLY
144
+ end
145
+ end
146
+
147
+ log_debug("Making price history API request for symbol: #{symbol}")
148
+
149
+ response = client.get_price_history(
150
+ symbol.upcase,
151
+ period_type: period_type_enum,
152
+ period: period,
153
+ frequency_type: frequency_type_enum,
154
+ frequency: frequency,
155
+ start_datetime: parsed_start,
156
+ end_datetime: parsed_end,
157
+ need_extended_hours_data: need_extended_hours_data,
158
+ need_previous_close: need_previous_close
159
+ )
160
+
161
+ if response&.body
162
+ log_info("Successfully retrieved price history for #{symbol}")
163
+
164
+ begin
165
+ data = JSON.parse(response.body)
166
+ candles = data.dig("candles") || []
167
+
168
+ summary = if candles.any?
169
+ "Retrieved #{candles.length} price candles"
170
+ else
171
+ "No price data available for the specified parameters"
172
+ end
173
+
174
+ MCP::Tool::Response.new([{
175
+ type: "text",
176
+ text: "**Price History for #{symbol.upcase}:**\n\n#{summary}\n\n```json\n#{response.body}\n```"
177
+ }])
178
+ rescue JSON::ParserError
179
+ MCP::Tool::Response.new([{
180
+ type: "text",
181
+ text: "**Price History for #{symbol.upcase}:**\n\n```json\n#{response.body}\n```"
182
+ }])
183
+ end
184
+ else
185
+ log_warn("Empty response from Schwab API for symbol: #{symbol}")
186
+ MCP::Tool::Response.new([{
187
+ type: "text",
188
+ text: "**No Data**: Empty response from Schwab API for symbol: #{symbol}"
189
+ }])
190
+ end
191
+
192
+ rescue => e
193
+ log_error("Error retrieving price history for #{symbol}: #{e.message}")
194
+ log_debug("Backtrace: #{e.backtrace.first(3).join('\n')}")
195
+ MCP::Tool::Response.new([{
196
+ type: "text",
197
+ text: "**Error** retrieving price history for #{symbol}: #{e.message}\n\n#{e.backtrace.first(3).join('\n')}"
198
+ }])
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,406 @@
1
+ require "mcp"
2
+ require_relative "../loggable"
3
+
4
+ module SchwabMCP
5
+ module Tools
6
+ class HelpTool < MCP::Tool
7
+ extend Loggable
8
+ description "Get comprehensive help and documentation for the Schwab MCP server tools and capabilities"
9
+
10
+ input_schema(
11
+ properties: {
12
+ topic: {
13
+ type: "string",
14
+ description: "Optional specific topic to get help for. Available: 'tools', 'setup'",
15
+ enum: ["tools", "setup"]
16
+ }
17
+ },
18
+ required: []
19
+ )
20
+
21
+ annotations(
22
+ title: "Get Help and Documentation",
23
+ read_only_hint: true,
24
+ destructive_hint: false,
25
+ idempotent_hint: true
26
+ )
27
+
28
+ def self.call(topic: nil, server_context:)
29
+ log_info("Help requested for topic: #{topic || 'general'}")
30
+
31
+ help_content = if topic
32
+ get_topic_help(topic)
33
+ else
34
+ get_general_help
35
+ end
36
+
37
+ MCP::Tool::Response.new([{
38
+ type: "text",
39
+ text: help_content
40
+ }])
41
+ end
42
+
43
+ private
44
+
45
+ def self.get_general_help
46
+ <<~HELP
47
+ # 📊 Schwab MCP Server
48
+
49
+ ## Available Tools:
50
+
51
+ ### Market Data & Analysis:
52
+ - **quote_tool**: Get real-time quote for a single symbol
53
+ - **quotes_tool**: Get real-time quotes for multiple symbols
54
+ - **option_chain_tool**: Get option chain data for a symbol
55
+ - **get_price_history_tool**: Get historical price data for an instrument
56
+ - **get_market_hours_tool**: Get market hours for specified markets
57
+ - **list_movers_tool**: Get top ten movers for a given index
58
+
59
+ ### Option Strategy Tools:
60
+ - **option_strategy_finder_tool**: Find option strategies (iron condor, call spread, put spread)
61
+ - **preview_order_tool**: Preview an options order before placing (⚠️ SAFE PREVIEW)
62
+ - **place_order_tool**: Place an options order for execution (⚠️ DESTRUCTIVE)
63
+ - **replace_order_tool**: Replace an existing order with a new one (⚠️ DESTRUCTIVE)
64
+
65
+ ### Account Management:
66
+ - **schwab_account_details_tool**: Get account information using account name mapping
67
+ - **list_schwab_accounts_tool**: List all available Schwab accounts
68
+ - **list_account_orders_tool**: List orders for a specific account using account name mapping
69
+ - **list_account_transactions_tool**: List transactions for a specific account
70
+ - **get_order_tool**: Get detailed information for a specific order by order ID
71
+ - **cancel_order_tool**: Cancel a specific order by order ID (⚠️ DESTRUCTIVE)
72
+
73
+ ### Documentation:
74
+ - **help_tool**: This help system
75
+
76
+ ## Usage Examples:
77
+ ```
78
+ # Market Data
79
+ quote_tool(symbol: "AAPL")
80
+ quotes_tool(symbols: ["AAPL", "TSLA", "MSFT"])
81
+ get_price_history_tool(symbol: "SPX", period_type: "day", period: 5, frequency_type: "minute", frequency: 5)
82
+ get_market_hours_tool(markets: ["equity", "option"])
83
+ list_movers_tool(index: "$SPX", sort_order: "PERCENT_CHANGE_UP")
84
+
85
+ # Options
86
+ option_chain_tool(symbol: "SPX", contract_type: "ALL")
87
+ option_strategy_finder_tool(strategy_type: "ironcondor", underlying_symbol: "SPX", expiration_date: "2025-01-17")
88
+ preview_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "ironcondor", price: 1.50, quantity: 1)
89
+
90
+ # Account Management
91
+ schwab_account_details_tool(account_name: "TRADING_BROKERAGE_ACCOUNT")
92
+ list_schwab_accounts_tool()
93
+ list_account_orders_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", status: "WORKING")
94
+ list_account_transactions_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", start_date: "2025-01-01")
95
+ get_order_tool(order_id: "123456789", account_name: "TRADING_BROKERAGE_ACCOUNT")
96
+ cancel_order_tool(order_id: "123456789", account_name: "TRADING_BROKERAGE_ACCOUNT")
97
+
98
+ # Help
99
+ help_tool(topic: "setup")
100
+ ```
101
+
102
+ ## Help Topics:
103
+ - `tools` - Detailed tool documentation
104
+ - `setup` - Configuration and authentication setup
105
+
106
+ Use `help_tool(topic: "setup")` for initial configuration.
107
+ HELP
108
+ end
109
+
110
+ def self.get_topic_help(topic)
111
+ case topic
112
+ when "tools"
113
+ get_tools_help
114
+ when "setup"
115
+ get_setup_help
116
+ else
117
+ "**Error**: Unknown topic '#{topic}'. Available topics: tools, setup"
118
+ end
119
+ end
120
+
121
+ def self.get_tools_help
122
+ <<~HELP
123
+ # 🔧 Available Tools
124
+
125
+ ## Market Data & Analysis Tools
126
+
127
+ ### quote_tool
128
+ Get real-time quote for a single symbol.
129
+ **Parameters**: `symbol` (required) - e.g., "AAPL", "TSLA"
130
+ **Example**: `quote_tool(symbol: "AAPL")`
131
+
132
+ ### quotes_tool
133
+ Get real-time quotes for multiple symbols.
134
+ **Parameters**:
135
+ - `symbols` (required) - Array of symbols, e.g., ["AAPL", "TSLA"]
136
+ - `fields` (optional) - Specific fields to return
137
+ - `indicative` (optional) - Boolean for indicative quotes
138
+
139
+ **Examples**:
140
+ ```
141
+ quotes_tool(symbols: ["AAPL", "TSLA", "MSFT"])
142
+ quotes_tool(symbols: ["/ES"], indicative: true)
143
+ ```
144
+
145
+ ### get_price_history_tool
146
+ Get historical price data for an instrument symbol.
147
+ **Parameters**:
148
+ - `symbol` (required) - Instrument symbol (e.g., "AAPL", "$SPX")
149
+ - `period_type` (optional) - "day", "month", "year", "ytd"
150
+ - `period` (optional) - Number of periods based on period_type
151
+ - `frequency_type` (optional) - "minute", "daily", "weekly", "monthly"
152
+ - `frequency` (optional) - Frequency of data points
153
+ - `start_datetime`, `end_datetime` (optional) - ISO format date range
154
+ - `need_extended_hours_data` (optional) - Include pre/post market data
155
+ - `need_previous_close` (optional) - Include previous close
156
+
157
+ **Examples**:
158
+ ```
159
+ get_price_history_tool(symbol: "SPX", period_type: "day", period: 5, frequency_type: "minute", frequency: 5)
160
+ get_price_history_tool(symbol: "AAPL", start_datetime: "2025-01-01T00:00:00Z", end_datetime: "2025-01-15T23:59:59Z")
161
+ ```
162
+
163
+ ### get_market_hours_tool
164
+ Get market hours for specified markets.
165
+ **Parameters**:
166
+ - `markets` (required) - Array of markets: ["equity", "option", "bond", "future", "forex"]
167
+ - `date` (optional) - Date in YYYY-MM-DD format (defaults to today)
168
+
169
+ **Examples**:
170
+ ```
171
+ get_market_hours_tool(markets: ["equity", "option"])
172
+ get_market_hours_tool(markets: ["future"], date: "2025-01-17")
173
+ ```
174
+
175
+ ### list_movers_tool
176
+ Get top ten movers for a given index.
177
+ **Parameters**:
178
+ - `index` (required) - "$DJI", "$COMPX", "$SPX", "NYSE", "NASDAQ", etc.
179
+ - `sort_order` (optional) - "VOLUME", "TRADES", "PERCENT_CHANGE_UP", "PERCENT_CHANGE_DOWN"
180
+ - `frequency` (optional) - Magnitude filter: 0, 1, 5, 10, 30, 60
181
+
182
+ **Examples**:
183
+ ```
184
+ list_movers_tool(index: "$SPX", sort_order: "PERCENT_CHANGE_UP")
185
+ list_movers_tool(index: "NASDAQ", frequency: 5)
186
+ ```
187
+
188
+ ## Option Tools
189
+
190
+ ### option_chain_tool
191
+ Get option chain data for an optionable symbol.
192
+ **Parameters**:
193
+ - `symbol` (required) - Underlying symbol, e.g., "SPX", "AAPL"
194
+ - `contract_type` (optional) - "CALL", "PUT", or "ALL"
195
+ - `from_date`, `to_date` (optional) - Date range for expirations
196
+ - Many other optional parameters for filtering
197
+
198
+ **Example**: `option_chain_tool(symbol: "SPX", contract_type: "ALL")`
199
+
200
+ ### option_strategy_finder_tool
201
+ Find option strategies using sophisticated algorithms.
202
+ **Parameters**:
203
+ - `strategy_type` (required) - "ironcondor", "callspread", or "putspread"
204
+ - `underlying_symbol` (required) - e.g., "SPX", "$SPX"
205
+ - `expiration_date` (required) - Target expiration "YYYY-MM-DD"
206
+ - `max_delta` (optional) - Maximum delta for short legs (default: 0.15)
207
+ - `max_spread` (optional) - Maximum spread width (default: 20.0)
208
+ - `min_credit` (optional) - Minimum credit in dollars (default: 100.0)
209
+ - `min_open_interest` (optional) - Minimum open interest (default: 0)
210
+ - `dist_from_strike` (optional) - Min distance from current price (default: 0.07)
211
+ - `expiration_type`, `settlement_type`, `option_root` (optional) - Filters
212
+
213
+ **Examples**:
214
+ ```
215
+ option_strategy_finder_tool(strategy_type: "ironcondor", underlying_symbol: "SPX", expiration_date: "2025-01-17")
216
+ option_strategy_finder_tool(strategy_type: "callspread", underlying_symbol: "SPY", expiration_date: "2025-01-10", max_delta: 0.20, min_credit: 50.0)
217
+ ```
218
+
219
+ ## Order Management Tools
220
+
221
+ ### preview_order_tool ⚠️ SAFE PREVIEW
222
+ Preview an options order before placing to validate structure and see estimated costs.
223
+ **Parameters**:
224
+ - `account_name` (required) - Account name ending with '_ACCOUNT'
225
+ - `strategy_type` (required) - "ironcondor", "callspread", "putspread"
226
+ - `price` (required) - Net price for the order
227
+ - `quantity` (optional) - Number of contracts (default: 1)
228
+ - `order_instruction` (optional) - "open" or "exit" (default: "open")
229
+ - Strategy-specific symbol parameters (e.g., put_short_symbol, call_long_symbol)
230
+
231
+ **Examples**:
232
+ ```
233
+ preview_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "ironcondor", price: 1.50, quantity: 1)
234
+ preview_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "callspread", short_symbol: "SPY250117C00600000", long_symbol: "SPY250117C00610000", price: 2.50)
235
+ ```
236
+
237
+ ### place_order_tool ⚠️ DESTRUCTIVE OPERATION
238
+ Place an options order for execution. **WARNING**: This places real orders with real money.
239
+ **Parameters**: Same as preview_order_tool
240
+ **Safety Features**:
241
+ - Validates order structure before placing
242
+ - Returns order ID for tracking
243
+ - Provides detailed confirmation
244
+
245
+ **Examples**:
246
+ ```
247
+ place_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "ironcondor", price: 1.50, quantity: 1)
248
+ ```
249
+
250
+ ### replace_order_tool ⚠️ DESTRUCTIVE OPERATION
251
+ Replace an existing order with a new one. **WARNING**: Cancels existing order and places new one.
252
+ **Parameters**:
253
+ - `order_id` (required) - ID of existing order to replace
254
+ - All other parameters same as place_order_tool
255
+
256
+ **Examples**:
257
+ ```
258
+ replace_order_tool(order_id: "123456789", account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "ironcondor", price: 1.75, quantity: 1)
259
+ ```
260
+
261
+ ## Account Management Tools
262
+
263
+ ### schwab_account_details_tool
264
+ Get detailed account information using account name mapping.
265
+ **Parameters**:
266
+ - `account_name` (required) - Account name ending with '_ACCOUNT' (e.g., "TRADING_BROKERAGE_ACCOUNT")
267
+ - `fields` (optional) - Array of specific fields to retrieve ["balances", "positions", "orders"]
268
+
269
+ **Examples**:
270
+ ```
271
+ schwab_account_details_tool(account_name: "TRADING_BROKERAGE_ACCOUNT")
272
+ schwab_account_details_tool(account_name: "IRA_ACCOUNT", fields: ["balances", "positions"])
273
+ ```
274
+
275
+ ### list_schwab_accounts_tool
276
+ List all available Schwab accounts with their details.
277
+ **Parameters**: None required
278
+ **Example**: `list_schwab_accounts_tool()`
279
+
280
+ ### list_account_orders_tool
281
+ List orders for a specific account using account name mapping.
282
+ **Parameters**:
283
+ - `account_name` (required) - Account name ending with '_ACCOUNT'
284
+ - `max_results` (optional) - Maximum number of orders to retrieve
285
+ - `from_date` (optional) - Start date in YYYY-MM-DD format (default: 60 days ago)
286
+ - `to_date` (optional) - End date in YYYY-MM-DD format (default: today)
287
+ - `status` (optional) - Filter by order status (WORKING, FILLED, CANCELED, etc.)
288
+
289
+ **Examples**:
290
+ ```
291
+ list_account_orders_tool(account_name: "TRADING_BROKERAGE_ACCOUNT")
292
+ list_account_orders_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", status: "WORKING")
293
+ list_account_orders_tool(account_name: "IRA_ACCOUNT", from_date: "2025-01-01", to_date: "2025-01-15", max_results: 50)
294
+ ```
295
+
296
+ ### list_account_transactions_tool
297
+ List transactions for a specific account using account name mapping.
298
+ **Parameters**:
299
+ - `account_name` (required) - Account name ending with '_ACCOUNT'
300
+ - `start_date` (optional) - Start date in YYYY-MM-DD format (default: 60 days ago)
301
+ - `end_date` (optional) - End date in YYYY-MM-DD format (default: today)
302
+ - `transaction_types` (optional) - Array of transaction types to filter by
303
+
304
+ **Examples**:
305
+ ```
306
+ list_account_transactions_tool(account_name: "TRADING_BROKERAGE_ACCOUNT")
307
+ list_account_transactions_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", start_date: "2025-01-01", transaction_types: ["TRADE", "DIVIDEND_OR_INTEREST"])
308
+ ```
309
+
310
+ ### get_order_tool
311
+ Get detailed information for a specific order by order ID.
312
+ **Parameters**:
313
+ - `order_id` (required) - The numeric order ID to retrieve details for
314
+ - `account_name` (required) - Account name ending with '_ACCOUNT'
315
+
316
+ **Examples**:
317
+ ```
318
+ get_order_tool(order_id: "123456789", account_name: "TRADING_BROKERAGE_ACCOUNT")
319
+ get_order_tool(order_id: "987654321", account_name: "IRA_ACCOUNT")
320
+ ```
321
+
322
+ ### cancel_order_tool ⚠️ DESTRUCTIVE OPERATION
323
+ Cancel a specific order by order ID. **WARNING**: This action cannot be undone.
324
+ **Parameters**:
325
+ - `order_id` (required) - The numeric order ID to cancel
326
+ - `account_name` (required) - Account name ending with '_ACCOUNT'
327
+
328
+ **Safety Features**:
329
+ - Verifies order exists before attempting cancellation
330
+ - Checks if order is cancelable (based on status)
331
+ - Provides detailed confirmation when successful
332
+ - Returns helpful error messages for invalid requests
333
+
334
+ **Examples**:
335
+ ```
336
+ cancel_order_tool(order_id: "123456789", account_name: "TRADING_BROKERAGE_ACCOUNT")
337
+ cancel_order_tool(order_id: "987654321", account_name: "IRA_ACCOUNT")
338
+ ```
339
+
340
+ **Important Notes**:
341
+ - Only working orders can typically be cancelled
342
+ - Filled, expired, or already cancelled orders cannot be cancelled
343
+ - Always verify cancellation using get_order_tool or list_account_orders_tool
344
+
345
+ ## Documentation
346
+
347
+ ### help_tool
348
+ Get help and documentation.
349
+ **Parameters**: `topic` (optional) - "tools" or "setup"
350
+ **Example**: `help_tool(topic: "setup")`
351
+
352
+ **Supported symbols**: Stocks (AAPL), Futures (/ES), ETFs, etc.
353
+ **Limits**: Max 500 symbols per quotes_tool request
354
+ HELP
355
+ end
356
+
357
+ def self.get_setup_help
358
+ <<~HELP
359
+ # 🚀 Setup Guide
360
+
361
+ ## 1. Environment Variables
362
+ Set these required environment variables:
363
+ ```bash
364
+ export SCHWAB_API_KEY="your_app_key"
365
+ export SCHWAB_APP_SECRET="your_app_secret"
366
+ export SCHWAB_CALLBACK_URI="https://localhost:8443/callback"
367
+ export TOKEN_PATH="./token.json"
368
+ ```
369
+
370
+ **Account Environment Variables** (for account-specific tools):
371
+ ```bash
372
+ export TRADING_BROKERAGE_ACCOUNT="your_account_number"
373
+ export IRA_ACCOUNT="your_ira_account_number"
374
+ # Add more accounts as needed, always ending with '_ACCOUNT'
375
+ ```
376
+
377
+ ## 2. Initial Authentication
378
+ Run the token refresh script to authenticate:
379
+ ```bash
380
+ ./exe/schwab_token_refresh
381
+ ```
382
+
383
+ This will:
384
+ - Open a browser for Schwab login
385
+ - Complete OAuth2 flow
386
+ - Save tokens to TOKEN_PATH
387
+
388
+ ## 3. Start Server
389
+ ```bash
390
+ ./exe/schwab_mcp
391
+ ```
392
+
393
+ ## Prerequisites:
394
+ - Schwab brokerage account
395
+ - Approved Schwab developer account (developer.schwab.com)
396
+ - Ruby 3.0+
397
+
398
+ ## Troubleshooting:
399
+ - Ensure callback URL matches exactly in your Schwab app settings
400
+ - Check token file permissions
401
+ - Verify environment variables are set correctly
402
+ HELP
403
+ end
404
+ end
405
+ end
406
+ end