Skip to main content
Glama
vsoneji

Schwab MCP Server

by vsoneji
README.md
# Schwab MCP Server

A Model Context Protocol (MCP) server that enables AI assistants like Claude to
securely interact with Charles Schwab accounts and market data through the
official Schwab API.

## What You Can Do

Ask Claude to:

- "Show me my Schwab account balances and positions"
- "Get real-time quotes for AAPL, GOOGL, and MSFT"
- "What are today's market movers in the $SPX?"
- "Show me the options chain for TSLA with Greeks"
- "Get my transactions from the last 30 days"
- "Search for ETFs related to technology"
- "Check if the markets are open"

## Unofficial MCP Server

This is an unofficial, community-developed TypeScript MCP server for Charles
Schwab. It has not been approved, endorsed, or certified by Charles Schwab. It
is provided as-is, and its functionality may be incomplete or unstable. Use at
your own risk, especially when dealing with financial data or transactions.

## Overview

This MCP server acts as a bridge between AI assistants and the Schwab API,
providing:

- **Secure OAuth Authentication**: Implements Schwab's OAuth 2.0 flow with PKCE
  for secure authentication
- **Comprehensive Trading Tools**: Access to accounts, orders, quotes, and
  transactions
- **Market Data Tools**: Real-time quotes, price history, market hours, movers,
  and options chains
- **Account Privacy**: Built-in account identifier scrubbing to protect
  sensitive information
- **Local-Only**: Runs entirely on your machine with local HTTPS server and file-based token storage

## Features

### Trading Tools

- **Account Management**
  - `getAccounts`: Retrieve all account information with positions and balances
  - `getAccountNumbers`: Get list of account identifiers
- **Order Management**
  - `getOrder`: Get order by ID
  - `getOrders`: Fetch orders with filtering by status, time range, and symbol
  - `getOrdersByAccountNumber`: Get orders by account number
  - `cancelOrder`: Cancel an order (Experimental)
  - `placeOrder`: Place an order (Experimental)
  - `replaceOrder`: Replace an order (Experimental)
- **Market Quotes**
  - `getQuotes`: Get real-time quotes for multiple symbols
  - `getQuoteBySymbolId`: Get detailed quote for a single symbol
- **Transaction History**
  - `getTransactions`: Retrieve transaction history across all accounts with
    date filtering
- **User Preferences**
  - `getUserPreference`: Retrieve user trading preferences and settings

### Market Data Tools

- **Instrument Search**
  - `searchInstruments`: Search for securities by symbol with
    fundamental/reference data
- **Price History**
  - `getPriceHistory`: Get historical price data with customizable periods and
    frequencies
- **Market Hours**
  - `getMarketHours`: Check market operating hours by date
  - `getMarketHoursByMarketId`: Get specific market information
- **Market Movers**
  - `getMovers`: Find top market movers by index ($SPX, $COMPX, $DJI)
- **Options Chains**
  - `getOptionChain`: Retrieve full options chain data with Greeks
  - `getOptionExpirationChain`: Get option expiration dates

## Prerequisites

1. **Schwab Developer Account**: Register at
   [Schwab Developer Portal](https://developer.schwab.com)
2. **Node.js**: Version 20.x or higher (22.x recommended)

## Getting Started

### Quick Setup

```bash
git clone <repository-url>
cd schwab-mcp
npm install

# Configure your environment
cp .env.example .env
# Edit .env with your Schwab app credentials

# Start the MCP server (will prompt for OAuth on first run)
npm run start
```

### Configuration

#### 1. Create a Schwab App

1. Log in to the [Schwab Developer Portal](https://developer.schwab.com)
2. Create a new app with:
   - **App Name**: Your MCP server name (e.g., "My Schwab MCP")
   - **Callback URL**: `https://localhost:3000/callback`
   - **App Type**: Personal
3. Note your **App Key** (Client ID) and generate an **App Secret**

#### 2. Configure Environment Variables

Copy the `.env.example` file to `.env` and fill in your credentials:

```bash
cp .env.example .env
```

Edit `.env`:

```env
# Schwab OAuth Configuration
SCHWAB_CLIENT_ID=your_schwab_app_key_here
SCHWAB_CLIENT_SECRET=your_schwab_app_secret_here

# OAuth Redirect URI (must match what you configured in Schwab Developer Portal)
SCHWAB_REDIRECT_URI=https://localhost:3000/callback

# Optional: Port for HTTPS server (default: 3000)
PORT=3000

# Optional: Log level (trace, debug, info, warn, error, fatal)
LOG_LEVEL=info

# Optional: Environment (development, staging, production)
ENVIRONMENT=production
```

#### 3. First Run - OAuth Authentication

On the first run, the server will:

1. Generate self-signed certificates in `.certs/` directory (no OpenSSL required)
2. Start an HTTPS server on `https://localhost:3000`
3. Open your browser to the Schwab authorization page
4. After you authorize, tokens will be saved to `.auth/` directory
5. The MCP server will start and be ready to use

```bash
npm run start
```

**Note**: You may need to trust the self-signed certificate in your browser or system. The certificate is only used for localhost OAuth callback and is generated automatically using Node.js crypto libraries.

## Usage

### Claude Desktop Configuration

Add the server to your Claude Desktop configuration file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`  
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "schwab": {
      "command": "npx",
      "args": [
        "tsx",
        "/path/to/schwab-mcp/src/index.ts"
      ],
      "env": {
        "SCHWAB_CLIENT_ID": "your_app_key",
        "SCHWAB_CLIENT_SECRET": "your_app_secret",
        "SCHWAB_REDIRECT_URI": "https://localhost:3000/callback"
      }
    }
  }
}
```

Or if you prefer to build first and run the compiled JavaScript:

```json
{
  "mcpServers": {
    "schwab": {
      "command": "node",
      "args": [
        "/path/to/schwab-mcp/dist/index.js"
      ],
      "env": {
        "SCHWAB_CLIENT_ID": "your_app_key",
        "SCHWAB_CLIENT_SECRET": "your_app_secret",
        "SCHWAB_REDIRECT_URI": "https://localhost:3000/callback"
      }
    }
  }
}
```

Note: The first option using `tsx` is recommended as it doesn't require a build step.

Restart Claude Desktop. The server will connect via stdio transport.

### Example Commands

Once connected, you can ask Claude to:

- "Show me my Schwab account balances"
- "Get a quote for AAPL"
- "What are today's market movers in the $SPX?"
- "Show me the options chain for TSLA"
- "Get my recent transactions from the last week"

## Architecture

### Technology Stack

- **Runtime**: Node.js with TypeScript
- **Authentication**: OAuth 2.0 with PKCE via `@sudowealth/schwab-api`
- **API Client**: `@sudowealth/schwab-api` for type-safe Schwab API access
- **MCP Framework**: `@modelcontextprotocol/sdk` with stdio transport
- **State Management**: File-based token storage in `.auth/` directory
- **OAuth Server**: Express with HTTPS for OAuth callback handling

### Security Features

1. **OAuth 2.0 with PKCE**: Secure authentication flow preventing authorization
   code interception
2. **Local Token Storage**: Tokens stored locally in `.auth/` directory (never sent to external servers)
3. **HTTPS Localhost**: Self-signed certificates for secure OAuth callback
4. **Automatic Token Refresh**: Tokens refreshed 5 minutes before expiration
5. **Account Scrubbing**: Sensitive account identifiers automatically replaced with display names
6. **Secret Redaction**: Automatic masking of sensitive data in logs

## Development

### Available Scripts

```bash
npm run start        # Start the MCP server
npm run dev          # Start in development mode (same as start)
npm run build        # Build TypeScript to JavaScript
npm run typecheck    # Run TypeScript type checking
npm run lint         # Run ESLint
npm run format       # Format code with Prettier
npm run validate     # Run typecheck and lint together
```

### Project Structure

```
schwab-mcp/
├── .auth/           # OAuth tokens (git-ignored)
├── .certs/          # Self-signed certificates (git-ignored)
├── src/
│   ├── index.ts     # Main MCP server entry point
│   ├── auth/        # OAuth authentication client
│   ├── server/      # OAuth HTTP server and certificate generation
│   ├── shared/      # Shared utilities (logging, token storage)
│   └── tools/       # MCP tool implementations
├── .env             # Environment variables (git-ignored)
└── .env.example     # Example environment variables
```

### Debugging

The server includes comprehensive logging with configurable levels:

- **Log Levels**: trace, debug, info, warn, error, fatal
- Set via `LOG_LEVEL` environment variable in `.env`

Enable debug logging to see detailed OAuth flow and API interactions:

```env
LOG_LEVEL=debug
```

### Error Handling

The server implements robust error handling with specific error types:

- **Authentication Errors (401)**: Prompt for re-authentication
- **Client Errors (400)**: Invalid parameters, missing data
- **Server Errors (500)**: API failures, configuration issues
- All errors include request IDs for Schwab API troubleshooting

## Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT

## Troubleshooting

### Common Issues

1. **"Certificate error" in browser**

   - This is expected with self-signed certificates
   - Accept the certificate warning during OAuth flow
   - The certificate is only used for `https://localhost:3000/callback`

2. **"Cannot find module" errors**

   - Run `npm install` to ensure all dependencies are installed
   - Make sure you're using Node.js 20.x or higher

3. **Authentication failures**

   - Verify your redirect URI matches exactly: `https://localhost:3000/callback`
   - Check that your Schwab app credentials are correct in `.env`
   - Enable debug logging: `LOG_LEVEL=debug` in `.env`

4. **"Port already in use" error**

   - Change the PORT in `.env` to a different value
   - Make sure no other process is using port 3000

## Recent Updates

- **Local-Only Architecture**: Migrated from Cloudflare Workers to local Node.js server
- **File-Based Token Storage**: Tokens stored securely in local `.auth/` directory
- **HTTPS OAuth Flow**: Self-signed certificates for secure localhost OAuth callback
- **Stdio Transport**: Uses standard MCP SDK with stdio for Claude Desktop integration

## Acknowledgments

- Uses [Model Context Protocol](https://modelcontextprotocol.io/)
- Powered by
  [@sudowealth/schwab-api](https://www.npmjs.com/package/@sudowealth/schwab-api)
- Built with [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk)