Schwab MCP
Servidor MCP da Charles Schwab de nível de produção, com segurança em primeiro lugar, auto-hospedado no Cloudflare Workers. Cotações em tempo real, Gregos de opções, OAuth 2.1 com confiança zero e proteções contra alucinações.
Documentação
Schwab MCP no Cloudflare Workers
Inglês | 简体中文
[!CAUTION] Aviso Legal
Este projeto destina-se exclusivamente a fins educacionais, de pesquisa e automação experimental. Ele NÃO constitui aconselhamento financeiro ou recomendação de investimento. Negociar valores mobiliários e opções envolve risco substancial de perda. Os autores e colaboradores não assumem qualquer responsabilidade por ações de negociação, perdas, erros de API, latência de rede ou alucinações de LLM.
Um servidor Model Context Protocol (MCP) remoto de produção, para usuário único, auto-hospedado em seus Workers pessoais do Cloudflare (100% compatível com o plano gratuito). Ele expõe dados de mercado e serviços de conta da Charles Schwab a clientes de LLM (Cursor, Claude Desktop, VS Code) via HTTP Streamable (/mcp) com segurança estrita de confiança zero e proteções contra alucinação.
⚡ Início Rápido em Uma Linha com Agentes de IA
Tem um assistente de codificação com IA (Claude Code, Cursor Composer, Windsurf ou Codex)? Clone este repositório e dê ao seu agente este único prompt:
💬 "Leia
setup.mde me guie passo a passo na configuração e implantação deste servidor Schwab MCP na minha conta Cloudflare."
Seu agente inspecionará automaticamente os pré-requisitos, provisionará KV e Secrets do Cloudflare, validará o código e fará a implantação na borda!
Destaques Principais
- 100% Auto-Hospedado e Zero Manutenção: Implantado diretamente na sua própria conta Cloudflare. Zero VPS, servidores pagos ou hardware de homelab necessários—funciona confortavelmente dentro do generoso plano gratuito do Cloudflare com soberania absoluta de dados e credenciais (sem proxy de terceiros ou intermediário SaaS).
- Acesso de Confiança Zero: Registro dinâmico de clientes OAuth 2.1 protegido por uma lista de permissões imutável de IDs numéricos de usuário do GitHub.
- Escopos Granulares por Cliente: Cada cliente MCP escolhe independentemente
read_only(padrão, ferramentas de negociação ocultas) oufull_access. - Isolamento Físico de Credenciais: Tokens da Schwab, máquinas de estado e logs de auditoria residem estritamente dentro de um Durable Object baseado em SQLite (
SchwabVault). Os segredos nunca vazam para KV ou logs. - Proteções Contra Alucinação:
- Interruptor global de segurança (
ENABLE_TRADING) com padrãofalse. - Confirmação obrigatória em duas fases com repetição literal de frase para ordens imediatas e modificadas.
- Negociação direta por sessão efêmera opcional (timeout de inatividade de 30 min, limite de 8 horas, token de exibição única).
- Ordens condicionais atrasadas determinísticas executadas via Durable Object Alarms.
- Zero novas tentativas cegas em falhas de rede (
EXECUTION_UNCERTAIN).
- Interruptor global de segurança (
Por que Cloudflare Workers? (Comparação)
| Recurso | MCP Local Típico (Python/stdio) | schwab-mcp (Este Projeto) |
|---|---|---|
| Hospedagem e Custo | PC local deve permanecer ligado, ou VPS pago ($5–$20/mês) | Auto-hospedado na conta pessoal Cloudflare ($0/mês, 100% compatível com plano gratuito) |
| Soberania de Dados | Arquivos em texto puro no disco local | Privado e auto-hospedado: você possui suas credenciais no SQLite do DO, zero SaaS de terceiros |
| Runtime e Implantação | Processo em máquina local (morre ao dormir/desligar) | Borda do Cloudflare Workers (Serverless Global 24/7, zero manutenção) |
| Acesso Multi-Client | Apenas desktop único | HTTP Streamable Remoto (/mcp) entre Cursor, Claude, VS Code e mobile |
| Armazenamento de Credenciais | Arquivos em texto puro no disco local | SQLite isolado dentro do Durable Object (SchwabVault), zero vazamentos para KV/logs |
| Permissões do Cliente | Todas as ferramentas expostas incondicionalmente | Escopo OAuth Dinâmico (read_only vs full_access) por cliente |
| Segurança de Negociação | Nenhuma ou dicas fracas baseadas em prompt | Interruptor de segurança na implantação (ENABLE_TRADING), repetição de frase em duas fases, capacidade de sessão |
| Ordens Fora do Horário / Condicionais | Impossível se o computador dormir | Durable Object Alarms acionam ordens condicionais determinísticas na nuvem |
| Segurança em Falha de Rede | Risco de dupla execução em nova tentativa cega | Estado EXECUTION_UNCERTAIN bloqueia reenvios cegos |
Arquitetura
flowchart TD
subgraph ClientSide ["Client"]
Client["MCP Client (Cursor / Claude Desktop / Codex)"]
end
subgraph CloudflareEdge ["Cloudflare Workers Edge"]
Router["Worker Router (/mcp, /authorize, /auth/*)"]
OAuthProvider["@cloudflare/workers-oauth-provider"]
KV[("OAUTH_KV (Client & Grant Meta)")]
subgraph StorageDO ["SchwabVault Durable Object"]
DO["Durable Object Runtime"]
SQLite[("DO SQLite Storage\n- Schwab Tokens & Expiry\n- Proposals & Direct Session\n- Delayed Tasks\n- Redacted Audit Logs")]
Alarm["DO Alarm Scheduler\n(Auto-refresh / Delayed Tasks)"]
end
end
subgraph Upstream ["External Services"]
GitHub["GitHub OAuth (User ID Whitelist)"]
SchwabAPI["Charles Schwab REST APIs\n(Market Data & Trader)"]
end
Client -->|Streamable HTTP POST /mcp| Router
Router -->|Verify Access Token| OAuthProvider
OAuthProvider <-->|Persist Client / Grant| KV
Router -->|Authorized Tool Calls| DO
DO -->|Read GET / Controlled Write| SchwabAPI
Alarm -->|Trigger on Conditions| DO
User["Owner (Browser)"] -->|Authorize Client /authorize| Router
Router -->|Verify Identity| GitHub
User -->|Connect Schwab /auth/schwab/connect| Router
Router -->|Exchange Schwab Code| SchwabAPI
Router -->|Persist Tokens| DO
Exemplos de Fluxos de Trabalho do Agente
1. Análise de Portfólio e Cadeia de Opções (Somente Leitura)
Usuário: "Qual é minha posição atual em NVDA e como está a opção de CALL 140 do próximo mês?"
Agente: (Chamaget_accounteget_option_chain)
"Você possui 100 ações da NVDA a um preço médio de $118,40 (P&L não realizado: +18,2%). O CALL de $140 para 30 dias está sendo negociado atualmente a $4,20 de compra / $4,35 de venda, com Delta 0,38 e IV de 42,1%."
2. Execução de Ordem Protegida (Confirmação em Duas Fases)
Usuário: "Compre 10 ações da AAPL com ordem limitada a $220,00."
Agente: (Chamaprepare_tradecom açãoPLACEpara congelar o snapshot da ordem)
"Preparei e congelei o snapshot da ordem:
- Ação: COMPRAR 10 AAPL @ Limite $220,00 (Dia)
- Conta:...8a9f
- ID da Proposta:prop_01JABC...(Válido por 5 minutos)
⚠️ Para confirmar e executar, responda literalmente com:
CONFIRM BUY 10 AAPL LIMIT 220.00 HASH 8a9f"Usuário:
CONFIRM BUY 10 AAPL LIMIT 220.00 HASH 8a9f
Agente: (Chamaconfirm_tradeconsumindo token de uso único com a frase literal)
"Ordem enviada com sucesso para a Charles Schwab. ID da Ordem:#10098234."
Catálogo de Ferramentas MCP
O servidor registra 24 ferramentas MCP. A validação de esquema segue estritamente as definições Zod em src/schemas.ts.
Ferramentas Somente Leitura e Diagnóstico (15 Ferramentas)
Disponíveis para todos os clientes autenticados. As operações usam HTTP GET:
| Ferramenta | Descrição | Entradas Principais |
|---|---|---|
schwab_connection_status | Verifica a conexão OAuth da Schwab e a expiração | Nenhuma |
get_trading_diagnostics | Verifica o registro de ferramentas, o interruptor de segurança e o status de negociação ativo | Nenhuma (sempre visível) |
get_quotes | Busca cotações de mercado em tempo real | symbols (array de 1–50), fields, indicative |
get_price_history | Barras de velas OHLCV históricas | symbol, periodType, frequencyType, frequency, startDate, endDate |
get_option_chain | Cadeias de opções, Gregas e cotações | symbol, contractType (CALL/PUT/ALL), strikeCount, strategy |
get_option_expiration_chain | Datas de vencimento para o ativo subjacente da opção | symbol |
get_market_hours | Horários de negociação do mercado | markets (equity, option, bond, etc.), date (YYYY-MM-DD) |
get_movers | Ganhadores/perdedores/ativos do mercado | index ($DJI, $COMPX, $SPX), sort, frequency |
search_instruments | Pesquisa tickers ou definições fundamentais | symbol, projection |
list_accounts | Lista identificadores de hash de conta (números reais removidos) | Nenhuma |
get_account | Saldos e posições para um hash de conta | accountHash, includePositions |
get_orders | Histórico de execuções entre contas | fromEnteredTime, toEnteredTime (ISO-8601), status |
get_order | Detalhes de execução para uma ordem específica | accountHash, orderId |
get_transactions | Fluxo de caixa da conta e transações de negociação | accountHash, startDate, endDate (YYYY-MM-DD), types, symbol |
get_user_preferences | Contexto do perfil do usuário e permissões de negociação | Nenhuma |
Ferramentas de Controle de Negociação (9 Ferramentas)
[!WARNING] Registradas apenas para clientes com
full_accessconcedido durante a autorização OAuth. Executar negociações adicionalmente requerENABLE_TRADING=true. O cancelamento permanece disponível mesmo quando a negociação está desativada para reduzir risco de posições.
| Ferramenta | Descrição | Entradas Principais |
|---|---|---|
enable_trading | Verificação somente leitura do status do interruptor de segurança na implantação | Nenhuma (o interruptor não pode ser alternado via MCP) |
prepare_trade | Congela o snapshot da ordem e emite token de uso único de 5 minutos | accountHash, action (PLACE/REPLACE), executionMode, orderPayload, userInstruction |
confirm_trade | Consome token de confirmação com reprodução literal de texto | proposalId, confirmationToken, confirmationText |
enable_session_direct_trading | Habilita colocação direta de ordens para a sessão atual | userInstruction (retorna sessionCapability de exibição única) |
place_order_direct | Envio direto de ordem sem confirmação por ordem | sessionCapability, executionKey (UUID), accountHash, orderPayload, userInstruction |
disable_session_direct_trading | Invalida imediatamente a capacidade de negociação direta ativa | sessionCapability |
cancel_order | Cancela ordem de mercado em aberto (sem necessidade de confirmação) | accountHash, orderId, userInstruction |
list_delayed_trades | Lista ordens condicionais ativas e históricas | status (ACTIVE, EXECUTING, EXECUTED, etc.) |
cancel_delayed_trade | Cancela uma tarefa condicional ativa imediatamente | taskId, userInstruction |
Proteções de Segurança e Confiabilidade
- Interruptor de Segurança na Implantação (
ENABLE_TRADING):- Configurado em
wrangler.jsonc(padrão:"false"). - Quando
false, preparações de ordens, confirmações e concessões de capacidade de sessão falham de forma fechada. Ordens em aberto ainda podem ser canceladas.
- Configurado em
- Confirmação em Duas Fases Contra Alucinação:
prepare_tradegera um snapshot imutável e uma frase de confirmação aleatória.- O usuário deve repetir explicitamente a frase de confirmação antes que
confirm_tradeconsuma o token e execute a ordem.
- Modo de Negociação Direta por Sessão:
- Limitado estritamente a
IMMEDIATE+PLACE(modificações de ordens ainda exigem confirmação). - Expiração por inatividade de 30 minutos, renovada a cada uso; vida útil máxima de 8 horas.
- Reivindicação atômica idempotente usando
executionKeyfornecido pelo cliente.
- Limitado estritamente a
- Ordens Atrasadas Determinísticas:
- Regras baseadas em AST combinando limites de
TIMEeQUOTE. Monitoradas por Durable Object Alarms.
- Regras baseadas em AST combinando limites de
- Sem Novas Tentativas Cegas em Escrita (
EXECUTION_UNCERTAIN):- Erros de rede ou 5xx upstream em solicitações de escrita marcam a ordem como
EXECUTION_UNCERTAIN. Novas tentativas cegas são bloqueadas; inspecione o status manualmente comget_orders.
- Erros de rede ou 5xx upstream em solicitações de escrita marcam a ordem como
- Sanitização de Payload:
- Máximo de 64 KiB de JSON UTF-8, profundidade máxima de 16, máximo de 2048 nós. Números reais de conta e tokens sensíveis são estritamente removidos.
Início Rápido
Pré-requisitos
- Node.js: >= 20.0.0 (LTS recomendado)
- Conta Cloudflare: Workers e Durable Objects habilitados
- Conta GitHub: App OAuth criado para autenticação do proprietário
- Conta de Desenvolvedor Charles Schwab: App de desenvolvedor aprovado com APIs Trader e Market Data
Configuração Local
-
Instale as dependências:
npm install -
Configure as variáveis de desenvolvimento local:
cp .dev.vars.example .dev.varsPreencha
.dev.vars(ignorado pelo git):GITHUB_CLIENT_SECRET="your_github_client_secret" SCHWAB_CLIENT_ID="your_schwab_app_key" SCHWAB_CLIENT_SECRET="your_schwab_app_secret" -
Atualize
wrangler.jsonc:- Defina
GITHUB_CLIENT_IDeALLOWED_GITHUB_USER_ID(encontre viacurl -s https://api.github.com/users/<username> | grep '"id":'). - Defina
SCHWAB_REDIRECT_URIcomohttps://<worker-name>.<subdomain>.workers.dev/auth/schwab/callback. - Mantenha
ENABLE_TRADING="false"durante o desenvolvimento.
- Defina
-
Execute a validação automatizada:
npm run validate(Executa typecheck, lint e a suíte Vitest em ambientes DO simulados do Workers).
Implantação em Produção
Etapa 1: Criar KV e Definir Secrets
# 1. Create KV namespace for OAuth client/token persistence
npx wrangler kv namespace create OAUTH_KV
# Put the returned id into wrangler.jsonc under kv_namespaces
# 2. Upload secrets interactively
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put SCHWAB_CLIENT_ID
npx wrangler secret put SCHWAB_CLIENT_SECRET
Etapa 2: Implantar o Worker
npm run deploy
[!NOTE] A autorização MCP usa
AUTHORIZATION_VERSION = "client-permissions-v1". Atualizar esta versão invalida concessões ativas, exigindo que os clientes se reautentiquem e escolham permissões. Os vínculos de conta Schwab e negociações atrasadas pendentes permanecem intactos. A atualização do S256 PKCE mantém esta versão de autorização inalterada: tokens de acesso e atualização válidos existentes mantêm suas permissões, incluindo concessões emitidas sem PKCE. Códigos de autorização emitidos antes desta atualização devem reiniciar a autorização; fluxos de consentimento pendentes ou de callback do GitHub sem parâmetros S256 válidos também são rejeitados. Reautorizar o mesmo cliente ainda revoga suas concessões anteriores antes de emitir uma substituição.
Etapa 3: Pipeline CI/CD (GitHub Actions)
O repositório inclui .github/workflows/deploy.yml para builds automatizados no main:
- Segredos do Repositório:
CLOUDFLARE_ACCOUNT_ID,CLOUDFLARE_API_TOKEN - Variáveis do Repositório:
PROD_OAUTH_KV_ID,PROD_ALLOWED_GITHUB_USER_ID,PROD_GITHUB_CLIENT_ID,PROD_SCHWAB_REDIRECT_URI,PROD_ENABLE_TRADING(falsepor padrão)
Execute npm run configure:deploy no CI para gerar o wrangler.jsonc de implantação. Nunca coloque segredos de cliente da Schwab ou do GitHub no GitHub Actions!
Etapa 4: Autorizar Charles Schwab
Visite no seu navegador:
https://<your-worker-domain>/auth/schwab/connect
- Autentique-se com o GitHub (deve corresponder ao
ALLOWED_GITHUB_USER_ID). - Faça login na Schwab e aprove o acesso à conta.
- Os tokens são criptografados com segurança no
SchwabVaultDurable Object.
Configuração do Cliente
Endpoint MCP: https://<your-worker-domain>/mcp
Toda nova autorização OAuth do MCP exige RFC 7636 S256 PKCE. Os metadados do servidor anunciam apenas S256; clientes sem suporte a S256 devem atualizar. Envie exatamente um code_challenge (43 caracteres base64url sem preenchimento) e um code_challenge_method=S256 na solicitação de autorização. Mantenha o verificador aleatório gerado independentemente no cliente e envie-o como code_verifier somente ao trocar o código de autorização. Parâmetros de desafio/método ausentes, duplicados ou inválidos retornam HTTP 400 invalid_request antes de iniciar o consentimento. Verificadores ausentes ou incorretos falham na troca de tokens pelo provedor OAuth.
Os testes de protocolo local cobrem registro dinâmico, consentimento no navegador, verificação simulada do proprietário do GitHub, troca de código, atualização e chamadas MCP para ambos os níveis de permissão. Esses testes não estabelecem compatibilidade com aplicativos clientes reais; reconecte e verifique cada cliente após a implantação.
Claude Desktop
Adicione ao claude_desktop_config.json:
{
"mcpServers": {
"schwab": {
"url": "https://<your-worker-domain>/mcp"
}
}
}
Cursor / VS Code / Codex
Configure como um servidor MCP remoto:
- Tipo:
Streamable HTTP - URL:
https://<your-worker-domain>/mcp - Na primeira conexão, conclua o fluxo OAuth no navegador, selecione Somente leitura ou Acesso total e verifique via GitHub.
Códigos de Erro e Operações
| Código | Causa | Ação |
|---|---|---|
AUTH_REQUIRED | Conta Schwab não vinculada | Visite reconnectUrl da resposta |
REAUTHORIZATION_REQUIRED | Token de atualização expirado (7 dias de inatividade) | Visite reconnectUrl para reautenticar |
INVALID_ARGUMENT | Payload falhou na validação Zod | Revise a entrada conforme src/schemas.ts |
RATE_LIMITED | Limite de taxa da API Schwab atingido (HTTP 429) | Aguarde seguindo retryAfterMs |
SCHWAB_REJECTED | Rejeição comercial upstream | Verifique ticker, status do pedido ou horário de mercado |
UPSTREAM_UNAVAILABLE | Erro no gateway Schwab ou Durable Object | Repita a solicitação (leituras são repetidas automaticamente uma vez) |
CONFIRMATION_INVALID | Token de confirmação incompatível ou frase incorreta | Execute novamente a confirmação com o texto exato |
CONFIRMATION_EXPIRED | Token excedeu validade de 5 minutos | Chame prepare_trade novamente |
TRADE_STATE_CONFLICT | Proposta já consumida ou tarefa cancelada | Atualize o estado com list_delayed_trades |
DIRECT_TRADING_SESSION_INVALID | Sessão expirada (30 min ocioso / 8 h máximo) | Chame enable_session_direct_trading |
EXECUTION_UNCERTAIN | Falha de rede durante o envio do pedido | Não repita às cegas. Verifique via get_orders |
Monitoramento de Logs em Tempo Real
Transmita logs JSON estruturados e sanitizados via Cloudflare Workers Observability:
npx wrangler tail schwab-mcp --format json
Todos os cabeçalhos, credenciais, tokens OAuth e números de conta são automaticamente mascarados antes do registro.
Licença
Licenciado sob a Apache License, Versão 2.0.