
Como tornamos nosso SaaS de nicho pronto para MCP (e vimos o ChatGPT chamar nossas ferramentas de despacho)
Nota: Este é um resumo em inglês de o post original do Zenn (japonês). Leia lá para o cronograma completo e rastreamento a nível de commit.
TL;DR
- Estamos lançando tasteck, um SaaS B2B para a indústria de entretenimento noturno japonesa (gerenciamento de despacho + turnos de elenco). 8 anos de dados operacionais, ~100 locais ativos.
- Dois dias após o post de design do MCP, o ChatGPT Plus pode chamar nossas ferramentas ao vivo: "Quem está disponível esta noite?" → MCP
list_available_drivers→ JSON → resposta em linguagem natural. - Sprint estimado de OAuth B2 = 2 semanas (16/06–01/07). Real = 1 dia, lendo a especificação cuidadosamente antes de tocar no código.
- Encontramos 12 armadilhas distintas entre "a emissão de OAuth funciona" e "o ChatGPT realmente invoca a ferramenta." Os logs de QA capturaram cada uma.
O que entregamos
3 ferramentas de leitura (B1):
-
list_available_drivers— motoristas disponíveis esta noite -
list_cast_shifts— escala de turnos de elenco de hoje -
list_assignable_casts— resolução conjunta:roster ∧ stage-name set ∧ shop match
Auxiliar de data em linguagem natural: resolveBusinessDate(naturalText, company) — lida com "hoje / amanhã / depois de amanhã" e o limite de dia útil por inquilino (por exemplo, a mudança de dia às 04:00 ou 05:00, configurada por Company.changeDateTime).
Servidor MCP SDK + transporte SSE: @modelcontextprotocol/sdk conectado a um controlador NestJS. Uma conexão SSE = uma instância de McpServer, com escopo de empresa, com um Map de session_id roteando POST /messages.
Fluxo OAuth (B2, finalizado em um dia em 7 etapas)
| Etapa | O que | Commit |
|---|---|---|
| 1 | Ponto de extremidade de Metadados de Recurso Protegido (RFC 9728) | d6f05ff6 |
| 2 |
/authorize + tela de consentimento + início do PKCE |
107edbcb |
| 3 |
/token + verificação PKCE + emissão de JWT + resource (RFC 8707) |
ffd0468c |
| 4 |
OAuthAccessTokenGuard (RS256 + HS256 fallback, extrai companyId / staffId) |
f2c9bed4 |
| 5 | Transporte HTTP transmitível (SSE → POST /sse/:companyId para JSON-RPC) |
3a28d92f |
| 6 |
resolveBusinessDate fallback indefinido (`(naturalText |
|
| 7 | Redeploy de QA + demonstração ao vivo do ChatGPT | — |
As 12 armadilhas (comprimidas)
O cronograma completo está no post japonês; a lista abreviada:
-
Desvio no caminho de descoberta. O ChatGPT esperava {% raw %}
.well-known/oauth-protected-resourcena raiz do servidor; publicamos sob/v1/api/staff/mcp/.... -
Desvio de transporte. O ChatGPT espera HTTP transmitível (POST
/ssecom JSON-RPC diretamente). Começamos com o legadoSSEServerTransport. Log de QA: POST/sse/1→ 404. - Ilusão de cache. O ChatGPT armazenou em cache "este conector não tem ferramentas" através de nossas correções. Pensamos que era um bug do servidor. Necessário: desconectar → reconectar através do fluxo OAuth.
-
Desvio de Guard (o grande). Tokens OAuth são assinados com o segredo do OAuth; o existente
StaffJwtGuardvalida o JWT de login do staff (segredo diferente). 401 em cada POST/sse/1. Correção: novoOAuthAccessTokenGuardcom fallback RS256 + HS256. -
undefined.trim(). O esquema da ferramenta declaroudatecomo opcional, o ChatGPT chamou sem argumentos, nosso manipulador feznaturalText.trim(). Falha → erro de resultado da ferramenta. Correção:(naturalText || "hoje").trim(). -
A fase "parece conectado, mas sem ferramentas".
tools/listestava retornando um payload vazio porque o handshake de transporte nunca foi totalmente concluído sob o Guard errado. Uma vez que a correção do Guard foi implementada + cache limpo,tools/listretornou todas as três. - Confusão de cache + Guard ao mesmo tempo. Inicialmente "descartamos" o cache. O log de QA provou que ambos eram reais e simultâneos — o raciocínio de causa única é a armadilha, não qualquer um dos problemas.
-
Adivinhação de convenção de nomenclatura. O ChatGPT especulou que a ferramenta seria chamada
get_available_drivers(convenção do OpenAI) quando a nossa élist_available_drivers. Funcionou uma vez quetools/listrealmente carregou — as convenções de nome nunca foram o problema. -
shop_idnão exposto eminputSchema. O ChatGPT não pode passar argumentos que não consegue ver.list_assignable_castsrecai para um padrão de loja do lado do servidor. Pendente B3. -
Limite de dia útil por inquilino. Hardcoding
new Date().toISOString().slice(0,10)teria sido errado para qualquer inquilino cujo dia útil muda às 04:00–05:00.resolveBusinessDatelêCompany.changeDateTime. -
Multi-inquilino em um processo SSE. Um processo, N empresas. Chaveamos a instância de
McpServerpelocompanyIdda reivindicação de token validada, então o vazamento entre inquilinos é estruturalmente impossível. - "Funciona no curl" ≠ "funciona no ChatGPT". Nosso script de handshake retornou 200 OK em todas as três ferramentas muito antes do ChatGPT poder invocar qualquer uma delas. Curl não modela o estado do OAuth, cache ou descoberta — apenas uma demonstração ao vivo prova o loop.
O momento em que funcionou
Após a correção do Guard, a limpeza do cache, e o fallback de undefined todos implementados:
ChatGPT: "As ferramentas foram chamadas ✓. Motoristas disponíveis para 2026-06-10: 0 pessoas. O payload retornado é
drivers: [], então não há motoristas atribuíveis para esta noite."
Então list_cast_shifts e list_assignable_casts dispararam limpos na mesma conversa. 3/3 ferramentas, caminho de dados real, síntese de linguagem natural real.
O que diríamos a qualquer um que estiver lançando OAuth MCP amanhã
- Leia a especificação duas vezes antes de abrir seu editor. O cronograma de 1 semana à frente foi leitura da especificação, não velocidade de codificação.
- Não confunda transporte e descoberta. Eles falham de maneiras diferentes e os logs são diferentes. Trate-os como problemas separados.
-
Registre cada hit de UA
openai-mcp. A presença/ausência dessas solicitações é seu diagnóstico mais rápido de cache vs servidor. - Planeje para dois Guards desde o primeiro dia. Staff JWT e token de acesso OAuth são materiais de assinatura diferentes. Não reutilize.
-
tools/listvazio ≠ ferramentas quebradas. Verifique se o handshake de transporte foi concluído primeiro. Ferramentas vazias geralmente são um sintoma três camadas acima.
Para onde isso vai a seguir
- B3: expor
shop_id(e outros args) eminputSchemapara que o LLM possa direcionar uma loja específica. - Escrever ferramentas (B4): mesmo caminho OAuth, mas consentimento + mudanças de forma de auditoria para
assign_*mutações. - Lado da indústria: este é o primeiro SaaS B2B pronto para MCP na indústria de entretenimento noturno japonesa.
A implementação do protocolo MCP pode ser um diferencial competitivo para empresas brasileiras que buscam integrar IA em seus serviços. A experiência compartilhada ilustra desafios e soluções práticas que podem ser aplicadas em diversos setores. Isso pode acelerar a adoção de tecnologias de IA no Brasil.
