Skip to main content
Glama
acidsolution

Schwab MCP Server

by acidsolution
README.md
# Schwab MCP Server

A read-only Model Context Protocol (MCP) server for Charles Schwab API. Access your Schwab account data and market information through AI assistants like Claude, ChatGPT, and more.

## Features

- **Portfolio Analysis** - View positions with cost basis, quantity, and market value
- **Real-time Quotes** - Get current prices for stocks and ETFs
- **Options Data** - Access options chains with Greeks
- **Price History** - Historical OHLCV data for technical analysis
- **Account Info** - View account balances and details

**Security Note:** This server is strictly READ-ONLY. No trading or account modification functionality is implemented.

## Prerequisites

- Python 3.10 or higher
- A Charles Schwab Developer account with:
  - App Key (Client ID)
  - App Secret (Client Secret)
  - A valid Refresh Token
  - Callback URL configured

## Installation

1. Clone the repository:
```bash
git clone https://github.com/yourusername/schwab-mcp-server.git
cd schwab-mcp-server
```

2. Create and activate a virtual environment:
```bash
python -m venv venv

# On macOS/Linux:
source venv/bin/activate

# On Windows:
venv\Scripts\activate
```

3. Install the package:
```bash
pip install -e .
```

## Configuration

### Environment Variables

Create a `.env` file in the project root:

```bash
SCHWAB_CALLBACK_URL=https://127.0.0.1:8182/callback
SCHWAB_TOKEN_PATH=~/.schwab-mcp/token.json
LOG_LEVEL=INFO
# Optional overrides if you don't keep them in the token file:
# SCHWAB_CLIENT_ID=your_app_key_here
# SCHWAB_CLIENT_SECRET=your_app_secret_here
```

### Initial Token Setup

Create the token file at the path specified in `SCHWAB_TOKEN_PATH` (default: `~/.schwab-mcp/token.json`):

```json
{
  "client_id": "your_app_key_here",
  "client_secret": "your_app_secret_here",
  "access_token": "",
  "refresh_token": "YOUR_REFRESH_TOKEN_HERE",
  "expires_at": 0,
  "token_type": "Bearer"
}
```

Setting `expires_at` to `0` forces an automatic token refresh on first use.

The MCP server will load `client_id` and `client_secret` directly from this token file. Environment variables with the same names can override these values if needed.

---

## Client Setup

### Claude Desktop

#### Windows (Automated)

Run the PowerShell script to automatically configure Claude Desktop:

```powershell
.\scripts\update_claude_schwab.ps1
```

The script will:
1. Stop Claude Desktop (prompts for confirmation)
2. Clear MCP-related cache files
3. Update the config with correct paths to the venv Python and src directory
4. Restart Claude Desktop

**Options:**
- `-RepoPath` - Path to this repo (default: auto-detected)
- `-SkipRestart` - Update config without restarting Claude
- `-ServerName` - MCP server name in config (default: `schwab`)

#### Manual Configuration

**macOS:** Edit `~/Library/Application Support/Claude/claude_desktop_config.json`

**Windows:** Edit `%APPDATA%\Claude\claude_desktop_config.json`

Add the following configuration:

```json
{
  "mcpServers": {
    "schwab": {
      "command": "python",
      "args": ["-m", "schwab_mcp.server"],
      "cwd": "/path/to/schwab-mcp-server",
      "env": {
        "SCHWAB_CALLBACK_URL": "https://127.0.0.1:8182/callback",
        "SCHWAB_TOKEN_PATH": "/path/to/.schwab-mcp/token.json",
        "PYTHONPATH": "/path/to/schwab-mcp-server/src",
        "LOG_LEVEL": "INFO"
      }
    }
  }
}
```

Restart Claude Desktop after saving the configuration.

---

### ChatGPT Desktop

ChatGPT Desktop supports MCP servers through its settings. To configure:

1. Open ChatGPT Desktop
2. Go to **Settings** > **Features** > **MCP Servers**
3. Click **Add Server** and enter:
   - **Name:** `schwab`
   - **Command:** `python`
   - **Arguments:** `-m schwab_mcp.server`
   - **Working Directory:** `/path/to/schwab-mcp-server`
4. Add environment variables (client id/secret are read from the token file):
   - `SCHWAB_CALLBACK_URL`: https://127.0.0.1:8182/callback
   - `SCHWAB_TOKEN_PATH`: /path/to/.schwab-mcp/token.json
5. Save and restart ChatGPT Desktop

File-based setup (auto-detected by ChatGPT Desktop):

- macOS: create `~/Library/Application Support/ChatGPT/.well-known/mcp.json`
- Windows: create `%APPDATA%\ChatGPT\.well-known\mcp.json`

Copy `chatgpt_desktop_config.example.json` to that location and update the paths to match your environment (Python executable, repo path, and token path). Restart ChatGPT Desktop after saving.

---

### Claude Code (CLI)

Add the MCP server to your Claude Code configuration. Create or edit `~/.claude/claude_code_config.json`:

```json
{
  "mcpServers": {
    "schwab": {
      "command": "python",
      "args": ["-m", "schwab_mcp.server"],
      "cwd": "/path/to/schwab-mcp-server",
      "env": {
        "SCHWAB_CALLBACK_URL": "https://127.0.0.1:8182/callback",
        "SCHWAB_TOKEN_PATH": "/path/to/.schwab-mcp/token.json"
      }
    }
  }
}
```

Alternatively, you can add it to your project's `.claude/settings.json` for project-specific configuration:

```json
{
  "mcpServers": {
    "schwab": {
      "command": "python",
      "args": ["-m", "schwab_mcp.server"],
      "cwd": "/path/to/schwab-mcp-server",
      "env": {
        "SCHWAB_CALLBACK_URL": "https://127.0.0.1:8182/callback",
        "SCHWAB_TOKEN_PATH": "/path/to/.schwab-mcp/token.json"
      }
    }
  }
}
```

---

### OpenAI Codex

For Codex CLI, configure the MCP server in your environment:

1. Set up environment variables in your shell profile (`.bashrc`, `.zshrc`, etc.):
```bash
export SCHWAB_CALLBACK_URL="https://127.0.0.1:8182/callback"
export SCHWAB_TOKEN_PATH="$HOME/.schwab-mcp/token.json"
# Optional overrides if you don't keep them in the token file:
# export SCHWAB_CLIENT_ID="your_client_id"
# export SCHWAB_CLIENT_SECRET="your_client_secret"
```

2. Configure Codex to use the MCP server by adding to your Codex configuration:
```json
{
  "mcpServers": {
    "schwab": {
      "command": "python",
      "args": ["-m", "schwab_mcp.server"],
      "cwd": "/path/to/schwab-mcp-server"
    }
  }
}
```

---

## Available Tools

| Tool | Description |
|------|-------------|
| `get_account` | Get account information including type and balances |
| `get_positions` | Get all positions with cost basis and market value |
| `get_quote` | Get real-time quote for a single symbol |
| `get_quotes` | Get real-time quotes for multiple symbols |
| `get_option_chain` | Get options chain with Greeks for a symbol |
| `get_price_history` | Get historical OHLCV price data |

## Usage Examples

Once configured, you can ask your AI assistant questions like:

- "What are my current positions and their cost basis?"
- "Get me a quote for AAPL"
- "Show me the options chain for MSFT expiring in January"
- "What's the price history for NVDA over the last 6 months?"
- "What are my account balances?"

## Testing

### Using MCP Inspector

Test the server locally using the MCP Inspector:

```bash
npx @modelcontextprotocol/inspector python -m schwab_mcp.server
```

### Manual Test

Verify authentication is working:

```bash
python -c "from schwab_mcp.auth import TokenManager; from schwab_mcp.config import settings; tm = TokenManager(settings.schwab_client_id, settings.schwab_client_secret, settings.schwab_token_path); tm.load_token(); print('Token loaded successfully')"
```

## Troubleshooting

### Token Refresh Fails
- Ensure your refresh token is valid and not expired (7-day expiration)
- Verify your client ID and secret are correct
- Check that your Schwab Developer app is approved and active

### Server Won't Start
- Verify Python 3.10+ is installed: `python --version`
- Ensure all dependencies are installed: `pip install -e .`
- Check that the `.env` file exists and has correct values

### No Data Returned
- Confirm your Schwab account has the appropriate permissions
- Check if markets are open (some data limited outside trading hours)
- Review logs for API error messages

## Security

- **Read-only access only** - No trading or account modification capabilities
- **Token storage** - Tokens are stored locally with restricted file permissions (600)
- **No credential logging** - Sensitive data is never written to logs
- **Environment variables** - Credentials should be passed via environment, not hardcoded

## References

- [Schwab Developer Portal](https://developer.schwab.com/)
- [MCP Specification](https://spec.modelcontextprotocol.io/)
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)

## License

MIT - Use at your own risk. Not affiliated with Charles Schwab.

TDQS

A3.6/5.0

Scored across 9 tools

Disambiguation4/5

Most tools have distinct purposes, but there is some overlap between get_quote and get_quotes, which could cause confusion as they differ only in handling single vs. multiple symbols. The load_* tools and query_market_data are clearly differentiated by their data loading vs. querying roles, and other tools like get_positions and get_movers target unique functions.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern using snake_case, such as get_instruments, load_option_chain, and query_market_data. This uniformity makes the tool set predictable and easy to navigate, with no deviations in naming conventions.

Tool Count5/5

With 9 tools, the server is well-scoped for market data and trading analysis, covering key areas like quotes, positions, movers, instruments, and SQL-based data handling. Each tool serves a clear purpose without redundancy, making the count appropriate for the domain.

Completeness4/5

The tool set provides strong coverage for market data retrieval and analysis, including real-time quotes, historical data, options, and positions. A minor gap exists in the lack of tools for executing trades or managing orders, which might be expected in a trading context, but the available tools support comprehensive data workflows.

Maintenance

ActivityInactive
ResponsivenessNo issues