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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be351ba4378eab6d224a3b690a8feb205491fa9cd264c3e46038c48f8d4f7227
|
4
|
+
data.tar.gz: 0ff6dd0f16abda8d20d070143f777ed4685322d2f0f8c303d9e3990344da225b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b80dee8bbeb80f3eb7940bafe0e43aedfa266c2981087b6865de8de79b8a12578a6e0fd5316918a942b012318b5f8e466f8655bf43fbd75732c0436c379f7bb2
|
7
|
+
data.tar.gz: 83a841a0a278a2a53ccc5d141365a0013829a863e1dd0d54784648313175dd7a463071c34b969751c4508575260e28298c26ba0310ac12b83a13342afcd0282c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] - 2025-10-20
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Refactored account management to use user-defined account names instead of directly referencing account numbers. ([#7](https://github.com/jwplatta/schwab_mcp/pull/7))
|
7
|
+
- Updated dependency `schwab_rb` to version `0.4.0`. ([#6](https://github.com/jwplatta/schwab_mcp/pull/6))
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Refactored order tools to use the `schwab_rb` order factory for improved modularity. ([#5](https://github.com/jwplatta/schwab_mcp/pull/5))
|
11
|
+
|
12
|
+
### Infrastructure
|
13
|
+
- Added GitHub Actions workflows for Claude integration. ([#3](https://github.com/jwplatta/schwab_mcp/pull/3), [#4](https://github.com/jwplatta/schwab_mcp/pull/4))
|
14
|
+
|
3
15
|
## [0.1.0] - 2025-07-10
|
4
16
|
|
5
17
|
- Initial release
|
data/README.md
CHANGED
@@ -40,7 +40,7 @@ Create a `.env` file in your project root with the following required variables:
|
|
40
40
|
SCHWAB_API_KEY=your_schwab_api_key
|
41
41
|
SCHWAB_APP_SECRET=your_schwab_app_secret
|
42
42
|
SCHWAB_CALLBACK_URI=your_callback_uri
|
43
|
-
|
43
|
+
SCHWAB_TOKEN_PATH=path/to/your/token.json
|
44
44
|
```
|
45
45
|
|
46
46
|
### Running the MCP Server
|
@@ -51,12 +51,6 @@ Start the server using the provided executable:
|
|
51
51
|
bundle exec exe/schwab_mcp
|
52
52
|
```
|
53
53
|
|
54
|
-
Or use the convenience script:
|
55
|
-
|
56
|
-
```bash
|
57
|
-
./start_mcp_server.sh
|
58
|
-
```
|
59
|
-
|
60
54
|
### Token Management
|
61
55
|
|
62
56
|
The gem includes utility scripts for managing Schwab API authentication tokens:
|
data/debug_env.rb
CHANGED
@@ -6,10 +6,10 @@ puts "=== Environment Variables ==="
|
|
6
6
|
puts "SCHWAB_API_KEY: #{ENV['SCHWAB_API_KEY'] ? '[SET]' : '[NOT SET]'}"
|
7
7
|
puts "SCHWAB_APP_SECRET: #{ENV['SCHWAB_APP_SECRET'] ? '[SET]' : '[NOT SET]'}"
|
8
8
|
puts "APP_CALLBACK_URL: #{ENV['APP_CALLBACK_URL'] ? '[SET]' : '[NOT SET]'}"
|
9
|
-
puts "
|
9
|
+
puts "SCHWAB_TOKEN_PATH: #{ENV['SCHWAB_TOKEN_PATH'] ? '[SET]' : '[NOT SET]'}"
|
10
10
|
|
11
11
|
puts "\n=== Testing Token File ==="
|
12
|
-
token_path = ENV['
|
12
|
+
token_path = ENV['SCHWAB_TOKEN_PATH'] || './token.json'
|
13
13
|
puts "Checking token file at: #{token_path}"
|
14
14
|
puts "Token file exists: #{File.exist?(token_path)}"
|
15
15
|
|
data/exe/schwab_mcp
CHANGED
@@ -9,22 +9,22 @@ required_vars = [
|
|
9
9
|
'SCHWAB_API_KEY',
|
10
10
|
'SCHWAB_APP_SECRET',
|
11
11
|
'SCHWAB_CALLBACK_URI',
|
12
|
-
'
|
12
|
+
'SCHWAB_TOKEN_PATH'
|
13
13
|
]
|
14
14
|
missing_vars = required_vars.select { |var| ENV[var].nil? || ENV[var].empty? }
|
15
15
|
|
16
16
|
unless missing_vars.empty?
|
17
|
-
STDERR.puts "
|
17
|
+
STDERR.puts "Missing required environment variables: #{missing_vars.join(', ')}"
|
18
18
|
exit 1
|
19
19
|
end
|
20
20
|
|
21
|
-
STDERR.puts "
|
21
|
+
STDERR.puts "All required environment variables are set."
|
22
22
|
|
23
23
|
begin
|
24
|
-
STDERR.puts "
|
24
|
+
STDERR.puts "Starting Schwab MCP server..."
|
25
25
|
SchwabMCP::Server.new.start
|
26
26
|
rescue StandardError => e
|
27
|
-
STDERR.puts "
|
27
|
+
STDERR.puts "Error starting Schwab MCP server: #{e.message}"
|
28
28
|
STDERR.puts e.backtrace.join("\n")
|
29
29
|
exit 1
|
30
30
|
end
|
data/exe/schwab_token_refresh
CHANGED
@@ -13,7 +13,7 @@ required_vars = [
|
|
13
13
|
'SCHWAB_API_KEY',
|
14
14
|
'SCHWAB_APP_SECRET',
|
15
15
|
'SCHWAB_CALLBACK_URI',
|
16
|
-
'
|
16
|
+
'SCHWAB_TOKEN_PATH'
|
17
17
|
]
|
18
18
|
missing_vars = required_vars.select { |var| ENV[var].nil? || ENV[var].empty? }
|
19
19
|
|
@@ -22,7 +22,7 @@ unless missing_vars.empty?
|
|
22
22
|
exit 1
|
23
23
|
end
|
24
24
|
|
25
|
-
token_path = ENV['
|
25
|
+
token_path = ENV['SCHWAB_TOKEN_PATH']
|
26
26
|
puts "Token path: #{token_path}"
|
27
27
|
|
28
28
|
begin
|
data/exe/schwab_token_reset
CHANGED
@@ -12,38 +12,39 @@ required_vars = [
|
|
12
12
|
'SCHWAB_API_KEY',
|
13
13
|
'SCHWAB_APP_SECRET',
|
14
14
|
'SCHWAB_CALLBACK_URI',
|
15
|
-
'
|
15
|
+
'SCHWAB_TOKEN_PATH',
|
16
|
+
'SCHWAB_HOME',
|
16
17
|
]
|
17
18
|
missing_vars = required_vars.select { |var| ENV[var].nil? || ENV[var].empty? }
|
18
19
|
|
19
20
|
unless missing_vars.empty?
|
20
|
-
puts "
|
21
|
+
puts "Missing required environment variables: #{missing_vars.join(', ')}"
|
21
22
|
exit 1
|
22
23
|
end
|
23
24
|
|
24
|
-
token_path = ENV['
|
25
|
+
token_path = ENV['SCHWAB_TOKEN_PATH']
|
25
26
|
puts "Token path: #{token_path}"
|
26
27
|
|
27
28
|
if File.exist?(token_path)
|
28
|
-
puts "
|
29
|
+
puts "Deleting existing token file: #{token_path}"
|
29
30
|
File.delete(token_path)
|
30
|
-
puts "
|
31
|
+
puts "Token file deleted successfully"
|
31
32
|
else
|
32
|
-
puts "
|
33
|
+
puts "Token file doesn't exist at: #{token_path}"
|
33
34
|
end
|
34
35
|
|
35
36
|
refresh_script_path = File.join(File.dirname(__FILE__), 'schwab_token_refresh')
|
36
|
-
puts "
|
37
|
+
puts "Calling token refresh script: #{refresh_script_path}"
|
37
38
|
|
38
39
|
begin
|
39
40
|
result = system(refresh_script_path)
|
40
41
|
if result
|
41
|
-
puts "
|
42
|
+
puts "Token reset and refresh completed successfully"
|
42
43
|
else
|
43
|
-
puts "
|
44
|
+
puts "Token refresh script failed"
|
44
45
|
exit 1
|
45
46
|
end
|
46
47
|
rescue => e
|
47
|
-
puts "
|
48
|
+
puts "Failed to execute token refresh script: #{e.message}"
|
48
49
|
exit 1
|
49
50
|
end
|
@@ -56,50 +56,19 @@ module SchwabMCP
|
|
56
56
|
client = SchwabClientFactory.create_client
|
57
57
|
return SchwabClientFactory.client_error_response unless client
|
58
58
|
|
59
|
-
|
60
|
-
unless
|
61
|
-
|
62
|
-
log_error("Account name '#{account_name}' not found in environment variables")
|
59
|
+
available_accounts = client.available_account_names
|
60
|
+
unless available_accounts.include?(account_name)
|
61
|
+
log_error("Account name '#{account_name}' not found in configured accounts")
|
63
62
|
return MCP::Tool::Response.new([{
|
64
63
|
type: "text",
|
65
|
-
text: "**Error**: Account name '#{account_name}' not found in
|
64
|
+
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."
|
66
65
|
}])
|
67
66
|
end
|
68
67
|
|
69
|
-
log_debug("
|
70
|
-
log_debug("Fetching account numbers mapping")
|
71
|
-
|
72
|
-
|
73
|
-
account_numbers = client.get_account_numbers # returns SchwabRb::DataObjects::AccountNumbers
|
74
|
-
unless account_numbers && account_numbers.respond_to?(:accounts)
|
75
|
-
log_error("Failed to retrieve account numbers")
|
76
|
-
return MCP::Tool::Response.new([{
|
77
|
-
type: "text",
|
78
|
-
text: "**Error**: Failed to retrieve account numbers from Schwab API"
|
79
|
-
}])
|
80
|
-
end
|
81
|
-
|
82
|
-
account_hash = nil
|
83
|
-
account_numbers.accounts.each do |acct|
|
84
|
-
if acct.account_number.to_s == account_id.to_s
|
85
|
-
account_hash = acct.hash_value
|
86
|
-
break
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
unless account_hash
|
91
|
-
log_error("Account ID not found in available accounts")
|
92
|
-
return MCP::Tool::Response.new([{
|
93
|
-
type: "text",
|
94
|
-
text: "**Error**: Account ID not found in available accounts. #{account_numbers.accounts.length} accounts available."
|
95
|
-
}])
|
96
|
-
end
|
97
|
-
|
98
|
-
log_debug("Found account hash for account ID: #{account_name}")
|
68
|
+
log_debug("Using account name: #{account_name}")
|
99
69
|
log_debug("Verifying order exists before attempting cancellation")
|
100
70
|
|
101
|
-
|
102
|
-
order = client.get_order(order_id, account_hash) # returns SchwabRb::DataObjects::Order
|
71
|
+
order = client.get_order(order_id, account_name: account_name) # returns SchwabRb::DataObjects::Order
|
103
72
|
unless order
|
104
73
|
log_warn("Order not found or empty response for order ID: #{order_id}")
|
105
74
|
return MCP::Tool::Response.new([{
|
@@ -121,7 +90,7 @@ module SchwabMCP
|
|
121
90
|
end
|
122
91
|
|
123
92
|
log_info("Attempting to cancel order ID: #{order_id} (Status: #{order_status})")
|
124
|
-
cancel_response = client.cancel_order(order_id,
|
93
|
+
cancel_response = client.cancel_order(order_id, account_name: account_name)
|
125
94
|
|
126
95
|
if cancel_response.respond_to?(:status) && cancel_response.status == 200
|
127
96
|
log_info("Successfully cancelled order ID: #{order_id}")
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mcp"
|
4
|
+
require_relative "../loggable"
|
5
|
+
require "schwab_rb"
|
6
|
+
|
7
|
+
module SchwabMCP
|
8
|
+
module Tools
|
9
|
+
class GetAccountNamesTool < MCP::Tool
|
10
|
+
extend Loggable
|
11
|
+
description "Get a list of configured Schwab account names"
|
12
|
+
|
13
|
+
input_schema(
|
14
|
+
properties: {
|
15
|
+
topic: {
|
16
|
+
type: "string",
|
17
|
+
description: "Asking about a specific topic related to account names (optional)"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
required: []
|
21
|
+
)
|
22
|
+
|
23
|
+
annotations(
|
24
|
+
title: "Get Account Names",
|
25
|
+
read_only_hint: true,
|
26
|
+
destructive_hint: false,
|
27
|
+
idempotent_hint: true
|
28
|
+
)
|
29
|
+
|
30
|
+
def self.call(topic: nil, server_context:)
|
31
|
+
account_names = SchwabRb::AccountHashManager.new.available_account_names
|
32
|
+
acct_names_content = if account_names && !account_names.empty?
|
33
|
+
formatted_names = account_names.map { |name| "- #{name}" }.join("\n")
|
34
|
+
"Configured Schwab Account Names:\n\n#{formatted_names}"
|
35
|
+
else
|
36
|
+
<<~NO_ACCOUNTS
|
37
|
+
No Schwab Account Names Configured
|
38
|
+
|
39
|
+
You need to configure your Schwab account names in the account_names.json file.
|
40
|
+
|
41
|
+
This file should be located in your schwab_home directory (typically ~/.schwab_rb/).
|
42
|
+
|
43
|
+
For detailed setup instructions, please refer to:
|
44
|
+
https://github.com/jwplatta/schwab_rb/blob/main/doc/ACCOUNT_MANAGEMENT.md
|
45
|
+
|
46
|
+
The account_names.json file should map friendly names to your Schwab account hashes.
|
47
|
+
NO_ACCOUNTS
|
48
|
+
end
|
49
|
+
|
50
|
+
MCP::Tool::Response.new([{
|
51
|
+
type: "text",
|
52
|
+
text: acct_names_content
|
53
|
+
}])
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -57,49 +57,19 @@ module SchwabMCP
|
|
57
57
|
client = SchwabClientFactory.create_client
|
58
58
|
return SchwabClientFactory.client_error_response unless client
|
59
59
|
|
60
|
-
|
61
|
-
unless
|
62
|
-
|
63
|
-
log_error("Account name '#{account_name}' not found in environment variables")
|
60
|
+
available_accounts = client.available_account_names
|
61
|
+
unless available_accounts.include?(account_name)
|
62
|
+
log_error("Account name '#{account_name}' not found in configured accounts")
|
64
63
|
return MCP::Tool::Response.new([{
|
65
64
|
type: "text",
|
66
|
-
text: "**Error**: Account name '#{account_name}' not found in
|
65
|
+
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."
|
67
66
|
}])
|
68
67
|
end
|
69
68
|
|
70
|
-
log_debug("
|
71
|
-
log_debug("Fetching account numbers mapping")
|
72
|
-
|
73
|
-
|
74
|
-
account_numbers = client.get_account_numbers # returns SchwabRb::DataObjects::AccountNumbers
|
75
|
-
unless account_numbers && account_numbers.respond_to?(:accounts)
|
76
|
-
log_error("Failed to retrieve account numbers")
|
77
|
-
return MCP::Tool::Response.new([{
|
78
|
-
type: "text",
|
79
|
-
text: "**Error**: Failed to retrieve account numbers from Schwab API"
|
80
|
-
}])
|
81
|
-
end
|
82
|
-
|
83
|
-
account_hash = nil
|
84
|
-
account_numbers.accounts.each do |acct|
|
85
|
-
if acct.account_number.to_s == account_id.to_s
|
86
|
-
account_hash = acct.hash_value
|
87
|
-
break
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
unless account_hash
|
92
|
-
log_error("Account ID not found in available accounts")
|
93
|
-
return MCP::Tool::Response.new([{
|
94
|
-
type: "text",
|
95
|
-
text: "**Error**: Account ID not found in available accounts. #{account_numbers.accounts.length} accounts available."
|
96
|
-
}])
|
97
|
-
end
|
98
|
-
|
99
|
-
log_debug("Found account hash for account ID: #{account_name}")
|
69
|
+
log_debug("Using account name: #{account_name}")
|
100
70
|
log_debug("Fetching order details for order ID: #{order_id}")
|
101
71
|
|
102
|
-
order = client.get_order(order_id,
|
72
|
+
order = client.get_order(order_id, account_name: account_name) # returns SchwabRb::DataObjects::Order
|
103
73
|
if order
|
104
74
|
log_info("Successfully retrieved order details for order ID: #{order_id}")
|
105
75
|
formatted_response = format_order_object(order, order_id, account_name)
|
@@ -44,7 +44,7 @@ module SchwabMCP
|
|
44
44
|
|
45
45
|
def self.get_general_help
|
46
46
|
<<~HELP
|
47
|
-
#
|
47
|
+
# Schwab MCP Server
|
48
48
|
|
49
49
|
## Available Tools:
|
50
50
|
|
@@ -57,9 +57,9 @@ module SchwabMCP
|
|
57
57
|
- **list_movers_tool**: Get top ten movers for a given index
|
58
58
|
|
59
59
|
### Option Order Tools:
|
60
|
-
- **preview_order_tool**: Preview an options order before placing (
|
61
|
-
- **place_order_tool**: Place an options order for execution (
|
62
|
-
- **replace_order_tool**: Replace an existing order with a new one (
|
60
|
+
- **preview_order_tool**: Preview an options order before placing (SAFE PREVIEW)
|
61
|
+
- **place_order_tool**: Place an options order for execution (DESTRUCTIVE)
|
62
|
+
- **replace_order_tool**: Replace an existing order with a new one (DESTRUCTIVE)
|
63
63
|
|
64
64
|
### Account Management:
|
65
65
|
- **schwab_account_details_tool**: Get account information using account name mapping
|
@@ -67,7 +67,7 @@ module SchwabMCP
|
|
67
67
|
- **list_account_orders_tool**: List orders for a specific account using account name mapping
|
68
68
|
- **list_account_transactions_tool**: List transactions for a specific account
|
69
69
|
- **get_order_tool**: Get detailed information for a specific order by order ID
|
70
|
-
- **cancel_order_tool**: Cancel a specific order by order ID (
|
70
|
+
- **cancel_order_tool**: Cancel a specific order by order ID (DESTRUCTIVE)
|
71
71
|
|
72
72
|
### Documentation:
|
73
73
|
- **help_tool**: This help system
|
@@ -118,7 +118,7 @@ module SchwabMCP
|
|
118
118
|
|
119
119
|
def self.get_tools_help
|
120
120
|
<<~HELP
|
121
|
-
#
|
121
|
+
# Available Tools
|
122
122
|
|
123
123
|
## Market Data & Analysis Tools
|
124
124
|
|
@@ -197,7 +197,7 @@ module SchwabMCP
|
|
197
197
|
|
198
198
|
## Order Management Tools
|
199
199
|
|
200
|
-
### preview_order_tool
|
200
|
+
### preview_order_tool SAFE PREVIEW
|
201
201
|
Preview an options order before placing to validate structure and see estimated costs.
|
202
202
|
**Parameters**:
|
203
203
|
- `account_name` (required) - Account name ending with '_ACCOUNT'
|
@@ -213,7 +213,7 @@ module SchwabMCP
|
|
213
213
|
preview_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "callspread", short_symbol: "SPY250117C00600000", long_symbol: "SPY250117C00610000", price: 2.50)
|
214
214
|
```
|
215
215
|
|
216
|
-
### place_order_tool
|
216
|
+
### place_order_tool DESTRUCTIVE OPERATION
|
217
217
|
Place an options order for execution. **WARNING**: This places real orders with real money.
|
218
218
|
**Parameters**: Same as preview_order_tool
|
219
219
|
**Safety Features**:
|
@@ -226,7 +226,7 @@ module SchwabMCP
|
|
226
226
|
place_order_tool(account_name: "TRADING_BROKERAGE_ACCOUNT", strategy_type: "ironcondor", price: 1.50, quantity: 1)
|
227
227
|
```
|
228
228
|
|
229
|
-
### replace_order_tool
|
229
|
+
### replace_order_tool DESTRUCTIVE OPERATION
|
230
230
|
Replace an existing order with a new one. **WARNING**: Cancels existing order and places new one.
|
231
231
|
**Parameters**:
|
232
232
|
- `order_id` (required) - ID of existing order to replace
|
@@ -298,7 +298,7 @@ module SchwabMCP
|
|
298
298
|
get_order_tool(order_id: "987654321", account_name: "IRA_ACCOUNT")
|
299
299
|
```
|
300
300
|
|
301
|
-
### cancel_order_tool
|
301
|
+
### cancel_order_tool DESTRUCTIVE OPERATION
|
302
302
|
Cancel a specific order by order ID. **WARNING**: This action cannot be undone.
|
303
303
|
**Parameters**:
|
304
304
|
- `order_id` (required) - The numeric order ID to cancel
|
@@ -335,7 +335,7 @@ module SchwabMCP
|
|
335
335
|
|
336
336
|
def self.get_setup_help
|
337
337
|
<<~HELP
|
338
|
-
#
|
338
|
+
# Setup Guide
|
339
339
|
|
340
340
|
## 1. Environment Variables
|
341
341
|
Set these required environment variables:
|
@@ -87,42 +87,16 @@ module SchwabMCP
|
|
87
87
|
client = SchwabClientFactory.create_client
|
88
88
|
return SchwabClientFactory.client_error_response unless client
|
89
89
|
|
90
|
-
|
91
|
-
unless
|
92
|
-
|
93
|
-
log_error("Account name '#{account_name}' not found in environment variables")
|
90
|
+
available_accounts = client.available_account_names
|
91
|
+
unless available_accounts.include?(account_name)
|
92
|
+
log_error("Account name '#{account_name}' not found in configured accounts")
|
94
93
|
return MCP::Tool::Response.new([{
|
95
94
|
type: "text",
|
96
|
-
text: "**Error**: Account name '#{account_name}' not found in
|
95
|
+
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."
|
97
96
|
}])
|
98
97
|
end
|
99
98
|
|
100
|
-
log_debug("
|
101
|
-
log_debug("Fetching account numbers mapping")
|
102
|
-
|
103
|
-
account_numbers = client.get_account_numbers
|
104
|
-
|
105
|
-
unless account_numbers
|
106
|
-
log_error("Failed to retrieve account numbers")
|
107
|
-
return MCP::Tool::Response.new([{
|
108
|
-
type: "text",
|
109
|
-
text: "**Error**: Failed to retrieve account numbers from Schwab API"
|
110
|
-
}])
|
111
|
-
end
|
112
|
-
|
113
|
-
log_debug("Account numbers retrieved (#{account_numbers.size} accounts found)")
|
114
|
-
|
115
|
-
account_hash = account_numbers.find_hash_value(account_id)
|
116
|
-
|
117
|
-
unless account_hash
|
118
|
-
log_error("Account ID not found in available accounts")
|
119
|
-
return MCP::Tool::Response.new([{
|
120
|
-
type: "text",
|
121
|
-
text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
|
122
|
-
}])
|
123
|
-
end
|
124
|
-
|
125
|
-
log_debug("Found account hash for account ID: #{account_name}")
|
99
|
+
log_debug("Using account name: #{account_name}")
|
126
100
|
|
127
101
|
from_datetime = nil
|
128
102
|
to_datetime = nil
|
@@ -154,7 +128,7 @@ module SchwabMCP
|
|
154
128
|
log_debug("Fetching orders with params - max_results: #{max_results}, from_datetime: #{from_datetime}, to_datetime: #{to_datetime}, status: #{status}")
|
155
129
|
|
156
130
|
orders = client.get_account_orders(
|
157
|
-
|
131
|
+
account_name: account_name,
|
158
132
|
max_results: max_results,
|
159
133
|
from_entered_datetime: from_datetime,
|
160
134
|
to_entered_datetime: to_datetime,
|
@@ -83,42 +83,16 @@ module SchwabMCP
|
|
83
83
|
client = SchwabClientFactory.create_client
|
84
84
|
return SchwabClientFactory.client_error_response unless client
|
85
85
|
|
86
|
-
|
87
|
-
unless
|
88
|
-
|
89
|
-
log_error("Account name '#{account_name}' not found in environment variables")
|
86
|
+
available_accounts = client.available_account_names
|
87
|
+
unless available_accounts.include?(account_name)
|
88
|
+
log_error("Account name '#{account_name}' not found in configured accounts")
|
90
89
|
return MCP::Tool::Response.new([{
|
91
90
|
type: "text",
|
92
|
-
text: "**Error**: Account name '#{account_name}' not found in
|
91
|
+
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."
|
93
92
|
}])
|
94
93
|
end
|
95
94
|
|
96
|
-
log_debug("
|
97
|
-
log_debug("Fetching account numbers mapping")
|
98
|
-
|
99
|
-
account_numbers = client.get_account_numbers
|
100
|
-
|
101
|
-
unless account_numbers
|
102
|
-
log_error("Failed to retrieve account numbers")
|
103
|
-
return MCP::Tool::Response.new([{
|
104
|
-
type: "text",
|
105
|
-
text: "**Error**: Failed to retrieve account numbers from Schwab API"
|
106
|
-
}])
|
107
|
-
end
|
108
|
-
|
109
|
-
log_debug("Account mappings retrieved (#{account_numbers.size} accounts found)")
|
110
|
-
|
111
|
-
account_hash = account_numbers.find_hash_value(account_id)
|
112
|
-
|
113
|
-
unless account_hash
|
114
|
-
log_error("Account ID not found in available accounts")
|
115
|
-
return MCP::Tool::Response.new([{
|
116
|
-
type: "text",
|
117
|
-
text: "**Error**: Account ID not found in available accounts. #{account_numbers.size} accounts available."
|
118
|
-
}])
|
119
|
-
end
|
120
|
-
|
121
|
-
log_debug("Found account hash for account ID: #{account_name}")
|
95
|
+
log_debug("Using account name: #{account_name}")
|
122
96
|
|
123
97
|
start_date_obj = nil
|
124
98
|
end_date_obj = nil
|
@@ -150,7 +124,7 @@ module SchwabMCP
|
|
150
124
|
log_debug("Fetching transactions with params - start_date: #{start_date_obj}, end_date: #{end_date_obj}, transaction_types: #{transaction_types}, symbol: #{symbol}")
|
151
125
|
|
152
126
|
transactions = client.get_transactions(
|
153
|
-
|
127
|
+
account_name: account_name,
|
154
128
|
start_date: start_date_obj,
|
155
129
|
end_date: end_date_obj,
|
156
130
|
transaction_types: transaction_types,
|
@@ -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:
|
19
|
+
description: "Instrument symbol (e.g., 'AAPL', 'TSLA', '$SPX')",
|
20
|
+
pattern: '^[\$\^]?[A-Za-z0-9]{1,5}$'
|
21
21
|
},
|
22
22
|
contract_type: {
|
23
23
|
type: "string",
|
@@ -169,7 +169,7 @@ module SchwabMCP
|
|
169
169
|
params[:entitlement] = entitlement if entitlement
|
170
170
|
|
171
171
|
log_debug("Making API request for option chain with params: #{params}")
|
172
|
-
option_chain = client.get_option_chain(symbol.upcase,
|
172
|
+
option_chain = client.get_option_chain(symbol.upcase, **params)
|
173
173
|
|
174
174
|
if option_chain
|
175
175
|
log_info("Successfully retrieved option chain for #{symbol}")
|