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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -7
- data/debug_env.rb +2 -2
- data/exe/schwab_mcp +5 -5
- data/exe/schwab_token_refresh +2 -2
- data/exe/schwab_token_reset +11 -10
- data/lib/schwab_mcp/schwab_client_factory.rb +1 -1
- data/lib/schwab_mcp/tools/cancel_order_tool.rb +7 -38
- data/lib/schwab_mcp/tools/get_account_names_tool.rb +58 -0
- data/lib/schwab_mcp/tools/get_order_tool.rb +6 -36
- data/lib/schwab_mcp/tools/help_tool.rb +11 -11
- data/lib/schwab_mcp/tools/list_account_orders_tool.rb +6 -32
- data/lib/schwab_mcp/tools/list_account_transactions_tool.rb +6 -32
- data/lib/schwab_mcp/tools/option_chain_tool.rb +3 -3
- data/lib/schwab_mcp/tools/place_order_tool.rb +34 -52
- data/lib/schwab_mcp/tools/preview_order_tool.rb +20 -82
- data/lib/schwab_mcp/tools/quote_tool.rb +3 -5
- data/lib/schwab_mcp/tools/replace_order_tool.rb +34 -52
- data/lib/schwab_mcp/tools/schwab_account_details_tool.rb +8 -34
- data/lib/schwab_mcp/version.rb +1 -1
- data/lib/schwab_mcp.rb +10 -8
- metadata +3 -10
- data/lib/schwab_mcp/tools/list_schwab_accounts_tool.rb +0 -149
- data/orders_example.json +0 -7084
- data/spx_option_chain.json +0 -25073
- data/start_mcp_server.sh +0 -4
- data/test_mcp.rb +0 -16
- data/test_server.rb +0 -23
- data/trading_brokerage_account_details.json +0 -89
- data/transactions_example.json +0 -488
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schwab_mcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Platta
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-10-20 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: mcp
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- lib/schwab_mcp/resources/.keep
|
72
72
|
- lib/schwab_mcp/schwab_client_factory.rb
|
73
73
|
- lib/schwab_mcp/tools/cancel_order_tool.rb
|
74
|
+
- lib/schwab_mcp/tools/get_account_names_tool.rb
|
74
75
|
- lib/schwab_mcp/tools/get_market_hours_tool.rb
|
75
76
|
- lib/schwab_mcp/tools/get_order_tool.rb
|
76
77
|
- lib/schwab_mcp/tools/get_price_history_tool.rb
|
@@ -78,7 +79,6 @@ files:
|
|
78
79
|
- lib/schwab_mcp/tools/list_account_orders_tool.rb
|
79
80
|
- lib/schwab_mcp/tools/list_account_transactions_tool.rb
|
80
81
|
- lib/schwab_mcp/tools/list_movers_tool.rb
|
81
|
-
- lib/schwab_mcp/tools/list_schwab_accounts_tool.rb
|
82
82
|
- lib/schwab_mcp/tools/option_chain_tool.rb
|
83
83
|
- lib/schwab_mcp/tools/place_order_tool.rb
|
84
84
|
- lib/schwab_mcp/tools/preview_order_tool.rb
|
@@ -87,14 +87,7 @@ files:
|
|
87
87
|
- lib/schwab_mcp/tools/replace_order_tool.rb
|
88
88
|
- lib/schwab_mcp/tools/schwab_account_details_tool.rb
|
89
89
|
- lib/schwab_mcp/version.rb
|
90
|
-
- orders_example.json
|
91
90
|
- sig/schwab_mcp.rbs
|
92
|
-
- spx_option_chain.json
|
93
|
-
- start_mcp_server.sh
|
94
|
-
- test_mcp.rb
|
95
|
-
- test_server.rb
|
96
|
-
- trading_brokerage_account_details.json
|
97
|
-
- transactions_example.json
|
98
91
|
homepage: https://github.com/jwplatta/schwab_mcp
|
99
92
|
licenses:
|
100
93
|
- MIT
|
@@ -1,149 +0,0 @@
|
|
1
|
-
require "mcp"
|
2
|
-
require "schwab_rb"
|
3
|
-
require "json"
|
4
|
-
require_relative "../loggable"
|
5
|
-
require_relative "../redactor"
|
6
|
-
require_relative "../schwab_client_factory"
|
7
|
-
|
8
|
-
module SchwabMCP
|
9
|
-
module Tools
|
10
|
-
class ListSchwabAccountsTool < MCP::Tool
|
11
|
-
extend Loggable
|
12
|
-
description "List all configured Schwab accounts with their friendly names and basic info"
|
13
|
-
|
14
|
-
input_schema(
|
15
|
-
properties: {},
|
16
|
-
required: []
|
17
|
-
)
|
18
|
-
|
19
|
-
annotations(
|
20
|
-
title: "List Schwab Accounts",
|
21
|
-
read_only_hint: true,
|
22
|
-
destructive_hint: false,
|
23
|
-
idempotent_hint: true
|
24
|
-
)
|
25
|
-
|
26
|
-
def self.call(server_context:)
|
27
|
-
log_info("Listing all configured Schwab accounts")
|
28
|
-
|
29
|
-
begin
|
30
|
-
client = SchwabClientFactory.create_client
|
31
|
-
return SchwabClientFactory.client_error_response unless client
|
32
|
-
|
33
|
-
log_debug("Fetching account numbers from Schwab API")
|
34
|
-
account_numbers = client.get_account_numbers
|
35
|
-
|
36
|
-
unless account_numbers
|
37
|
-
log_error("Failed to retrieve account numbers")
|
38
|
-
return MCP::Tool::Response.new([{
|
39
|
-
type: "text",
|
40
|
-
text: "**Error**: Failed to retrieve account numbers from Schwab API"
|
41
|
-
}])
|
42
|
-
end
|
43
|
-
|
44
|
-
log_debug("Retrieved #{account_numbers.size} accounts from Schwab API")
|
45
|
-
|
46
|
-
configured_accounts = find_configured_accounts(account_numbers)
|
47
|
-
|
48
|
-
if configured_accounts.empty?
|
49
|
-
return MCP::Tool::Response.new([{
|
50
|
-
type: "text",
|
51
|
-
text: "**No Configured Accounts Found**\n\nNo environment variables found ending with '_ACCOUNT'.\n\nTo configure accounts, set environment variables like:\n- TRADING_BROKERAGE_ACCOUNT=123456789\n- RETIREMENT_IRA_ACCOUNT=987654321\n- INCOME_BROKERAGE_ACCOUNT=555666777"
|
52
|
-
}])
|
53
|
-
end
|
54
|
-
|
55
|
-
formatted_response = format_accounts_list(configured_accounts, account_numbers)
|
56
|
-
|
57
|
-
MCP::Tool::Response.new([{
|
58
|
-
type: "text",
|
59
|
-
text: formatted_response
|
60
|
-
}])
|
61
|
-
|
62
|
-
rescue JSON::ParserError => e
|
63
|
-
log_error("JSON parsing error: #{e.message}")
|
64
|
-
MCP::Tool::Response.new([{
|
65
|
-
type: "text",
|
66
|
-
text: "**Error**: Failed to parse API response: #{e.message}"
|
67
|
-
}])
|
68
|
-
rescue => e
|
69
|
-
log_error("Error listing accounts: #{e.message}")
|
70
|
-
log_debug("Backtrace: #{e.backtrace.first(3).join('\n')}")
|
71
|
-
MCP::Tool::Response.new([{
|
72
|
-
type: "text",
|
73
|
-
text: "**Error** listing accounts: #{e.message}\n\n#{e.backtrace.first(3).join('\n')}"
|
74
|
-
}])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def self.find_configured_accounts(account_numbers)
|
81
|
-
# Get all account IDs from Schwab API data object
|
82
|
-
schwab_account_ids = account_numbers.account_numbers
|
83
|
-
|
84
|
-
# Find environment variables ending with "_ACCOUNT"
|
85
|
-
configured = []
|
86
|
-
ENV.each do |key, value|
|
87
|
-
next unless key.end_with?('_ACCOUNT')
|
88
|
-
|
89
|
-
if schwab_account_ids.include?(value)
|
90
|
-
account = account_numbers.find_by_account_number(value)
|
91
|
-
configured << {
|
92
|
-
name: key,
|
93
|
-
friendly_name: friendly_name_from_env_key(key),
|
94
|
-
account_id: value,
|
95
|
-
account: account
|
96
|
-
}
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
configured.sort_by { |account| account[:name] }
|
101
|
-
end
|
102
|
-
|
103
|
-
def self.friendly_name_from_env_key(env_key)
|
104
|
-
# Convert "TRADING_BROKERAGE_ACCOUNT" to "Trading Brokerage"
|
105
|
-
env_key.gsub('_ACCOUNT', '').split('_').map(&:capitalize).join(' ')
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.format_accounts_list(configured_accounts, account_numbers)
|
109
|
-
response = "**Configured Schwab Accounts:**\n\n"
|
110
|
-
|
111
|
-
configured_accounts.each_with_index do |account, index|
|
112
|
-
response += "#{index + 1}. **#{account[:friendly_name]}** (`#{account[:name]}`)\n"
|
113
|
-
response += " - Status: ✅ Configured\n\n"
|
114
|
-
end
|
115
|
-
|
116
|
-
# Show unconfigured accounts (if any)
|
117
|
-
unconfigured_accounts = account_numbers.accounts.reject do |account_obj|
|
118
|
-
configured_accounts.any? { |config| config[:account_id] == account_obj.account_number }
|
119
|
-
end
|
120
|
-
|
121
|
-
if unconfigured_accounts.any?
|
122
|
-
response += "**Unconfigured Accounts Available:**\n\n"
|
123
|
-
unconfigured_accounts.each_with_index do |account_obj, index|
|
124
|
-
response += " - To configure: Set `YOUR_NAME_ACCOUNT=#{Redactor::REDACTED_ACCOUNT_PLACEHOLDER}` in your .env file\n\n"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
response += "**Usage:**\n"
|
129
|
-
response += "To get account information, use the `schwab_account` tool with one of these account names:\n"
|
130
|
-
configured_accounts.each do |account|
|
131
|
-
response += "- `#{account[:name]}`\n"
|
132
|
-
end
|
133
|
-
|
134
|
-
if configured_accounts.any?
|
135
|
-
response += "\n**Example:**\n"
|
136
|
-
first_account = configured_accounts.first
|
137
|
-
response += "```\n"
|
138
|
-
response += "Tool: schwab_account\n"
|
139
|
-
response += "Parameters: {\n"
|
140
|
-
response += " \"account_name\": \"#{first_account[:name]}\"\n"
|
141
|
-
response += "}\n"
|
142
|
-
response += "```"
|
143
|
-
end
|
144
|
-
|
145
|
-
response
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|