schwab_mcp 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.claude/settings.json +14 -0
- data/CLAUDE.md +124 -0
- data/debug_env.rb +46 -0
- data/doc/DATA_OBJECTS_MIGRATION_TODO.md +80 -0
- data/doc/SCHWAB_CLIENT_FACTORY_REFACTOR_PLAN.md +187 -0
- data/exe/schwab_mcp +14 -3
- data/exe/schwab_token_refresh +10 -9
- data/lib/schwab_mcp/redactor.rb +4 -0
- data/lib/schwab_mcp/schwab_client_factory.rb +44 -0
- data/lib/schwab_mcp/tools/cancel_order_tool.rb +29 -50
- data/lib/schwab_mcp/tools/get_market_hours_tool.rb +27 -28
- data/lib/schwab_mcp/tools/get_order_tool.rb +51 -108
- data/lib/schwab_mcp/tools/get_price_history_tool.rb +23 -35
- data/lib/schwab_mcp/tools/help_tool.rb +1 -22
- data/lib/schwab_mcp/tools/list_account_orders_tool.rb +35 -63
- data/lib/schwab_mcp/tools/list_account_transactions_tool.rb +43 -72
- data/lib/schwab_mcp/tools/list_movers_tool.rb +21 -34
- data/lib/schwab_mcp/tools/list_schwab_accounts_tool.rb +18 -31
- data/lib/schwab_mcp/tools/option_chain_tool.rb +130 -82
- data/lib/schwab_mcp/tools/place_order_tool.rb +105 -117
- data/lib/schwab_mcp/tools/preview_order_tool.rb +100 -48
- data/lib/schwab_mcp/tools/quote_tool.rb +33 -26
- data/lib/schwab_mcp/tools/quotes_tool.rb +97 -45
- data/lib/schwab_mcp/tools/replace_order_tool.rb +104 -116
- data/lib/schwab_mcp/tools/schwab_account_details_tool.rb +56 -72
- data/lib/schwab_mcp/version.rb +1 -1
- data/lib/schwab_mcp.rb +1 -2
- data/orders_example.json +7084 -0
- data/spx_option_chain.json +25073 -0
- data/test_mcp.rb +16 -0
- data/test_server.rb +23 -0
- data/trading_brokerage_account_details.json +89 -0
- data/transactions_example.json +488 -0
- metadata +17 -7
- data/lib/schwab_mcp/option_chain_filter.rb +0 -213
- data/lib/schwab_mcp/orders/iron_condor_order.rb +0 -87
- data/lib/schwab_mcp/orders/order_factory.rb +0 -40
- data/lib/schwab_mcp/orders/vertical_order.rb +0 -62
- data/lib/schwab_mcp/tools/option_strategy_finder_tool.rb +0 -378
@@ -3,6 +3,7 @@ require "schwab_rb"
|
|
3
3
|
require "json"
|
4
4
|
require_relative "../loggable"
|
5
5
|
require_relative "../redactor"
|
6
|
+
require_relative "../schwab_client_factory"
|
6
7
|
|
7
8
|
module SchwabMCP
|
8
9
|
module Tools
|
@@ -19,7 +20,7 @@ module SchwabMCP
|
|
19
20
|
},
|
20
21
|
fields: {
|
21
22
|
type: "array",
|
22
|
-
description: "Optional account fields to retrieve (
|
23
|
+
description: "Optional account fields to retrieve (positions)",
|
23
24
|
items: {
|
24
25
|
type: "string"
|
25
26
|
}
|
@@ -47,20 +48,8 @@ module SchwabMCP
|
|
47
48
|
end
|
48
49
|
|
49
50
|
begin
|
50
|
-
client =
|
51
|
-
|
52
|
-
ENV['SCHWAB_APP_SECRET'],
|
53
|
-
ENV['SCHWAB_CALLBACK_URI'],
|
54
|
-
ENV['TOKEN_PATH']
|
55
|
-
)
|
56
|
-
|
57
|
-
unless client
|
58
|
-
log_error("Failed to initialize Schwab client")
|
59
|
-
return MCP::Tool::Response.new([{
|
60
|
-
type: "text",
|
61
|
-
text: "**Error**: Failed to initialize Schwab client. Check your credentials."
|
62
|
-
}])
|
63
|
-
end
|
51
|
+
client = SchwabClientFactory.create_client
|
52
|
+
return SchwabClientFactory.client_error_response unless client
|
64
53
|
|
65
54
|
account_id = ENV[account_name]
|
66
55
|
unless account_id
|
@@ -75,9 +64,9 @@ module SchwabMCP
|
|
75
64
|
log_debug("Found account ID: [REDACTED] for account name: #{account_name}")
|
76
65
|
log_debug("Fetching account numbers mapping")
|
77
66
|
|
78
|
-
|
67
|
+
account_numbers = client.get_account_numbers
|
79
68
|
|
80
|
-
unless
|
69
|
+
unless account_numbers
|
81
70
|
log_error("Failed to retrieve account numbers")
|
82
71
|
return MCP::Tool::Response.new([{
|
83
72
|
type: "text",
|
@@ -85,36 +74,27 @@ module SchwabMCP
|
|
85
74
|
}])
|
86
75
|
end
|
87
76
|
|
88
|
-
|
89
|
-
log_debug("Account mappings retrieved (#{account_mappings.length} accounts found)")
|
77
|
+
log_debug("Account numbers retrieved (#{account_numbers.size} accounts found)")
|
90
78
|
|
91
|
-
account_hash =
|
92
|
-
account_mappings.each do |mapping|
|
93
|
-
if mapping[:accountNumber] == account_id
|
94
|
-
account_hash = mapping[:hashValue]
|
95
|
-
break
|
96
|
-
end
|
97
|
-
end
|
79
|
+
account_hash = account_numbers.find_hash_value(account_id)
|
98
80
|
|
99
81
|
unless account_hash
|
100
82
|
log_error("Account ID not found in available accounts")
|
101
|
-
available_accounts = account_mappings.map { |m| "[REDACTED]" }.join(", ")
|
102
83
|
return MCP::Tool::Response.new([{
|
103
84
|
type: "text",
|
104
|
-
text: "**Error**: Account ID not found in available accounts. #{
|
85
|
+
text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
|
105
86
|
}])
|
106
87
|
end
|
107
88
|
|
108
89
|
log_debug("Found account hash for account ID: #{account_name}")
|
109
|
-
|
110
90
|
log_debug("Fetching account information with fields: #{fields}")
|
111
|
-
account_response = client.get_account(account_hash, fields: fields)
|
112
91
|
|
113
|
-
|
92
|
+
account = client.get_account(account_hash, fields: fields)
|
93
|
+
|
94
|
+
if account
|
114
95
|
log_info("Successfully retrieved account information for #{account_name}")
|
115
|
-
account_data = JSON.parse(account_response.body)
|
116
96
|
|
117
|
-
formatted_response = format_account_data(
|
97
|
+
formatted_response = format_account_data(account, account_name, account_id)
|
118
98
|
|
119
99
|
MCP::Tool::Response.new([{
|
120
100
|
type: "text",
|
@@ -146,54 +126,58 @@ module SchwabMCP
|
|
146
126
|
|
147
127
|
private
|
148
128
|
|
149
|
-
def self.format_account_data(
|
150
|
-
account = account_data["securitiesAccount"]
|
129
|
+
def self.format_account_data(account, account_name, account_id)
|
151
130
|
friendly_name = account_name.gsub('_ACCOUNT', '').split('_').map(&:capitalize).join(' ')
|
152
131
|
|
153
132
|
formatted = "**Account Information for #{friendly_name} (#{account_name}):**\n\n"
|
154
133
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
formatted += "- Short Market Value: $#{format_currency(current_balances['shortMarketValue'])}\n"
|
167
|
-
end
|
134
|
+
formatted += "**Account Number:** #{Redactor::REDACTED_ACCOUNT_PLACEHOLDER}\n"
|
135
|
+
formatted += "**Account Type:** #{account.type}\n"
|
136
|
+
|
137
|
+
if current_balances = account.current_balances
|
138
|
+
formatted += "\n**Current Balances:**\n"
|
139
|
+
formatted += "- Cash Balance: $#{format_currency(current_balances.cash_balance)}\n"
|
140
|
+
formatted += "- Buying Power: $#{format_currency(current_balances.buying_power)}\n"
|
141
|
+
formatted += "- Liquidation Value: $#{format_currency(current_balances.liquidation_value)}\n"
|
142
|
+
formatted += "- Long Market Value: $#{format_currency(current_balances.long_market_value)}\n"
|
143
|
+
formatted += "- Short Market Value: $#{format_currency(current_balances.short_market_value)}\n"
|
144
|
+
end
|
168
145
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
146
|
+
# Positions summary
|
147
|
+
if positions = account.positions
|
148
|
+
formatted += "\n**Positions Summary:**\n"
|
149
|
+
formatted += "- Total Positions: #{positions.length}\n"
|
150
|
+
|
151
|
+
if positions.length > 0
|
152
|
+
formatted += "\n**Position Details:**\n"
|
153
|
+
positions.each do |position|
|
154
|
+
symbol = position.instrument.symbol
|
155
|
+
qty = position.long_quantity - position.short_quantity
|
156
|
+
market_value = position.market_value
|
157
|
+
formatted += "- #{symbol}: #{qty} shares, Market Value: $#{format_currency(market_value)}\n"
|
182
158
|
end
|
183
159
|
end
|
160
|
+
end
|
184
161
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
162
|
+
# Note: Orders would need to be fetched separately as they're not part of the Account data object
|
163
|
+
|
164
|
+
# Convert account back to hash for JSON display (redacted)
|
165
|
+
account_hash = {
|
166
|
+
securitiesAccount: {
|
167
|
+
type: account.type,
|
168
|
+
accountNumber: account.account_number,
|
169
|
+
positions: account.positions.map(&:to_h),
|
170
|
+
currentBalances: {
|
171
|
+
cashBalance: account.current_balances&.cash_balance,
|
172
|
+
buyingPower: account.current_balances&.buying_power,
|
173
|
+
liquidationValue: account.current_balances&.liquidation_value,
|
174
|
+
longMarketValue: account.current_balances&.long_market_value,
|
175
|
+
shortMarketValue: account.current_balances&.short_market_value
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
195
179
|
|
196
|
-
redacted_data = Redactor.redact(
|
180
|
+
redacted_data = Redactor.redact(account_hash)
|
197
181
|
formatted += "\n```json\n#{JSON.pretty_generate(redacted_data)}\n```"
|
198
182
|
formatted
|
199
183
|
end
|
data/lib/schwab_mcp/version.rb
CHANGED
data/lib/schwab_mcp.rb
CHANGED
@@ -9,7 +9,6 @@ require_relative "schwab_mcp/redactor"
|
|
9
9
|
require_relative "schwab_mcp/tools/quote_tool"
|
10
10
|
require_relative "schwab_mcp/tools/quotes_tool"
|
11
11
|
require_relative "schwab_mcp/tools/option_chain_tool"
|
12
|
-
require_relative "schwab_mcp/tools/option_strategy_finder_tool"
|
13
12
|
require_relative "schwab_mcp/tools/help_tool"
|
14
13
|
require_relative "schwab_mcp/tools/schwab_account_details_tool"
|
15
14
|
require_relative "schwab_mcp/tools/list_schwab_accounts_tool"
|
@@ -24,6 +23,7 @@ require_relative "schwab_mcp/tools/list_movers_tool"
|
|
24
23
|
require_relative "schwab_mcp/tools/get_market_hours_tool"
|
25
24
|
require_relative "schwab_mcp/tools/get_price_history_tool"
|
26
25
|
require_relative "schwab_mcp/loggable"
|
26
|
+
require_relative "schwab_mcp/schwab_client_factory"
|
27
27
|
|
28
28
|
|
29
29
|
module SchwabMCP
|
@@ -33,7 +33,6 @@ module SchwabMCP
|
|
33
33
|
Tools::QuoteTool,
|
34
34
|
Tools::QuotesTool,
|
35
35
|
Tools::OptionChainTool,
|
36
|
-
Tools::OptionStrategyFinderTool,
|
37
36
|
Tools::HelpTool,
|
38
37
|
Tools::SchwabAccountDetailsTool,
|
39
38
|
Tools::ListSchwabAccountsTool,
|