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 | 简体中文

TypeScript Cloudflare Workers Self-Hosted Glama Score MCP Vitest License

[!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.md e 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) ou full_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ão false.
    • 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).

Por que Cloudflare Workers? (Comparação)

RecursoMCP Local Típico (Python/stdio)schwab-mcp (Este Projeto)
Hospedagem e CustoPC 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 DadosArquivos em texto puro no disco localPrivado e auto-hospedado: você possui suas credenciais no SQLite do DO, zero SaaS de terceiros
Runtime e ImplantaçãoProcesso em máquina local (morre ao dormir/desligar)Borda do Cloudflare Workers (Serverless Global 24/7, zero manutenção)
Acesso Multi-ClientApenas desktop únicoHTTP Streamable Remoto (/mcp) entre Cursor, Claude, VS Code e mobile
Armazenamento de CredenciaisArquivos em texto puro no disco localSQLite isolado dentro do Durable Object (SchwabVault), zero vazamentos para KV/logs
Permissões do ClienteTodas as ferramentas expostas incondicionalmenteEscopo OAuth Dinâmico (read_only vs full_access) por cliente
Segurança de NegociaçãoNenhuma ou dicas fracas baseadas em promptInterruptor de segurança na implantação (ENABLE_TRADING), repetição de frase em duas fases, capacidade de sessão
Ordens Fora do Horário / CondicionaisImpossível se o computador dormirDurable Object Alarms acionam ordens condicionais determinísticas na nuvem
Segurança em Falha de RedeRisco de dupla execução em nova tentativa cegaEstado 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: (Chama get_account e get_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: (Chama prepare_trade com ação PLACE para 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: (Chama confirm_trade consumindo 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:

FerramentaDescriçãoEntradas Principais
schwab_connection_statusVerifica a conexão OAuth da Schwab e a expiraçãoNenhuma
get_trading_diagnosticsVerifica o registro de ferramentas, o interruptor de segurança e o status de negociação ativoNenhuma (sempre visível)
get_quotesBusca cotações de mercado em tempo realsymbols (array de 1–50), fields, indicative
get_price_historyBarras de velas OHLCV históricassymbol, periodType, frequencyType, frequency, startDate, endDate
get_option_chainCadeias de opções, Gregas e cotaçõessymbol, contractType (CALL/PUT/ALL), strikeCount, strategy
get_option_expiration_chainDatas de vencimento para o ativo subjacente da opçãosymbol
get_market_hoursHorários de negociação do mercadomarkets (equity, option, bond, etc.), date (YYYY-MM-DD)
get_moversGanhadores/perdedores/ativos do mercadoindex ($DJI, $COMPX, $SPX), sort, frequency
search_instrumentsPesquisa tickers ou definições fundamentaissymbol, projection
list_accountsLista identificadores de hash de conta (números reais removidos)Nenhuma
get_accountSaldos e posições para um hash de contaaccountHash, includePositions
get_ordersHistórico de execuções entre contasfromEnteredTime, toEnteredTime (ISO-8601), status
get_orderDetalhes de execução para uma ordem específicaaccountHash, orderId
get_transactionsFluxo de caixa da conta e transações de negociaçãoaccountHash, startDate, endDate (YYYY-MM-DD), types, symbol
get_user_preferencesContexto do perfil do usuário e permissões de negociaçãoNenhuma

Ferramentas de Controle de Negociação (9 Ferramentas)

[!WARNING] Registradas apenas para clientes com full_access concedido durante a autorização OAuth. Executar negociações adicionalmente requer ENABLE_TRADING=true. O cancelamento permanece disponível mesmo quando a negociação está desativada para reduzir risco de posições.

FerramentaDescriçãoEntradas Principais
enable_tradingVerificação somente leitura do status do interruptor de segurança na implantaçãoNenhuma (o interruptor não pode ser alternado via MCP)
prepare_tradeCongela o snapshot da ordem e emite token de uso único de 5 minutosaccountHash, action (PLACE/REPLACE), executionMode, orderPayload, userInstruction
confirm_tradeConsome token de confirmação com reprodução literal de textoproposalId, confirmationToken, confirmationText
enable_session_direct_tradingHabilita colocação direta de ordens para a sessão atualuserInstruction (retorna sessionCapability de exibição única)
place_order_directEnvio direto de ordem sem confirmação por ordemsessionCapability, executionKey (UUID), accountHash, orderPayload, userInstruction
disable_session_direct_tradingInvalida imediatamente a capacidade de negociação direta ativasessionCapability
cancel_orderCancela ordem de mercado em aberto (sem necessidade de confirmação)accountHash, orderId, userInstruction
list_delayed_tradesLista ordens condicionais ativas e históricasstatus (ACTIVE, EXECUTING, EXECUTED, etc.)
cancel_delayed_tradeCancela uma tarefa condicional ativa imediatamentetaskId, userInstruction

Proteções de Segurança e Confiabilidade

  1. 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.
  2. Confirmação em Duas Fases Contra Alucinação:
    • prepare_trade gera 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_trade consuma o token e execute a ordem.
  3. 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 executionKey fornecido pelo cliente.
  4. Ordens Atrasadas Determinísticas:
    • Regras baseadas em AST combinando limites de TIME e QUOTE. Monitoradas por Durable Object Alarms.
  5. 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 com get_orders.
  6. 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

  1. Instale as dependências:

    npm install
    
  2. Configure as variáveis de desenvolvimento local:

    cp .dev.vars.example .dev.vars
    

    Preencha .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"
    
  3. Atualize wrangler.jsonc:

    • Defina GITHUB_CLIENT_ID e ALLOWED_GITHUB_USER_ID (encontre via curl -s https://api.github.com/users/<username> | grep '"id":').
    • Defina SCHWAB_REDIRECT_URI como https://<worker-name>.<subdomain>.workers.dev/auth/schwab/callback.
    • Mantenha ENABLE_TRADING="false" durante o desenvolvimento.
  4. 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 (false por 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
  1. Autentique-se com o GitHub (deve corresponder ao ALLOWED_GITHUB_USER_ID).
  2. Faça login na Schwab e aprove o acesso à conta.
  3. Os tokens são criptografados com segurança no SchwabVault Durable 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ódigoCausaAção
AUTH_REQUIREDConta Schwab não vinculadaVisite reconnectUrl da resposta
REAUTHORIZATION_REQUIREDToken de atualização expirado (7 dias de inatividade)Visite reconnectUrl para reautenticar
INVALID_ARGUMENTPayload falhou na validação ZodRevise a entrada conforme src/schemas.ts
RATE_LIMITEDLimite de taxa da API Schwab atingido (HTTP 429)Aguarde seguindo retryAfterMs
SCHWAB_REJECTEDRejeição comercial upstreamVerifique ticker, status do pedido ou horário de mercado
UPSTREAM_UNAVAILABLEErro no gateway Schwab ou Durable ObjectRepita a solicitação (leituras são repetidas automaticamente uma vez)
CONFIRMATION_INVALIDToken de confirmação incompatível ou frase incorretaExecute novamente a confirmação com o texto exato
CONFIRMATION_EXPIREDToken excedeu validade de 5 minutosChame prepare_trade novamente
TRADE_STATE_CONFLICTProposta já consumida ou tarefa canceladaAtualize o estado com list_delayed_trades
DIRECT_TRADING_SESSION_INVALIDSessão expirada (30 min ocioso / 8 h máximo)Chame enable_session_direct_trading
EXECUTION_UNCERTAINFalha de rede durante o envio do pedidoNã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.