NESTE ARTIGO7 SEÇÕES
O servidor MCP do Cord conecta assistentes como o Claude ou o Cursor aos dados reais de um negócio: orçamentos, faturas, contas a receber vencidas. Com uma chave de escrita, ele também envia orçamentos e registra pagamentos. Ou seja: um modelo de linguagem agindo com as credenciais de uma empresa. Nós o auditamos de ponta a ponta, e este foi o resultado.
1. Uma chave de leitura podia escrever
O Cord expõe o MCP por dois transportes: HTTP sem estado (POST /api/mcp) e o SSE legado (GET /api/mcp/sse para abrir a sessão e POST /api/mcp/message para enviar mensagens). A sessão SSE guarda a organização, a chave e a permissão com que foi aberta.
O problema estava em /api/mcp/message. Ele verificava se a chave que envia a mensagem era da mesma organização que a sessão e, depois, executava a ferramenta com a permissão guardada na sessão. Uma chave somente leitura da mesma empresa que conhecesse um sessionId podia executar ferramentas de escrita com a sessão de outra chave.
A correção é uma linha, mas é a linha certa: a sessão pertence a uma chave, não a uma organização.
if (session.orgId !== auth.orgId || session.keyId !== auth.keyId) {
return rpcErrRes(null, -32001, 'Esta API key no corresponde a la sesión.', 403);
}
// e executa com auth.scope, a permissão de quem chama, não a da sessão
A correção tem seu teste: uma chave de leitura com o sessionId de uma chave de escrita recebe 403 e a ferramenta não é executada.
2. Um contrato de saída que não era cumprido
As 19 ferramentas declaravam outputSchema. Na versão 2025-06-18 do protocolo, declará-lo obriga a devolver um structuredContent que cumpra esse esquema. O Cord devolvia só texto. Além disso, os esquemas tinham additionalProperties: false e um next_cursor tipado como string que, na verdade, pode ser null: um cliente que valida teria rejeitado respostas corretas.
Agora toda ferramenta responde com as duas coisas: structuredContent para o cliente que valida e o mesmo JSON compacto como texto para o que não valida. Os esquemas de saída são abertos: um campo novo não quebra ninguém. E o texto deixou de ser indentado, o que economiza tokens em cada chamada.
3. Valores sem moeda
O Cord tem uma regra: um valor sem moeda é um número, não dinheiro. As ferramentas de orçamentos, produtos e clientes devolviam total: 12500 sem dizer em quê. Um agente que presume pesos informa errado a um negócio que vende em dólares. Hoje cada valor viaja com sua moneda.
4. Erros que não chegavam ao modelo
Uma ferramenta de cobrança lançava um erro genérico quando o plano não a incluía. O servidor o tratava como falha interna e o modelo recebia “Erro interno do servidor”, sem ter como explicar ao usuário o que estava acontecendo. Ainda por cima, a mensagem citava o plano errado. Agora os limites de plano são informados como erro de negócio, com o nome do plano que realmente é necessário, lido do mesmo contrato que o resto do Cord usa.
No caminho, descobrimos que o índice da documentação devolvia o stack trace completo quando falhava. Não devolve mais.
5. Sete ferramentas novas
Passamos de 19 para 26. As novas foram pensadas para que um agente possa integrar o Cord, não só operá-lo:
| Ferramenta | Para quê |
|---|---|
contexto_cuenta |
Negócio, país, moeda, plano e se a chave é de teste ou de produção. A primeira chamada. |
proponer_configuracion |
Propõe perfil, impostos e catálogo a partir do site do negócio. Devolve um link; uma pessoa aprova. |
estado_configuracion |
Se essa proposta já foi aprovada, e o que foi aplicado. |
validar_datos_fiscales |
RFC, regime fiscal e uso do CFDI, NIF ou EIN com as mesmas regras usadas ao faturar. |
validar_apariencia |
Revisa um appearance do Cord Elements antes de você colocá-lo no código. |
simular_evento |
Dispara um webhook de teste pelo motor real. Somente com chave de teste. |
buscar_documentacion |
Busca na documentação e devolve as páginas com trecho e URL. |
Nenhuma delas altera as configurações da conta diretamente. proponer_configuracion deixa uma proposta que uma pessoa aprova no Cord, igual à do onboarding.
6. Uma API que se descreve sozinha
Um agente que escreve código contra uma API lê a especificação dela. Dos 343 campos do nosso OpenAPI, 279 não tinham descrição: o agente precisava adivinhar o que era terminos, se total incluía impostos ou em que unidade vinha rate.
Agora existe um glossário, FIELD_DOCS, que descreve cada nome de campo da API, e o gerador o aplica a cada propriedade sem descrição própria. A etapa de CI que compara a especificação com o código falha se uma propriedade ou um parâmetro ficar sem descrição. Adicionar um campo sem explicá-lo não passa mais.
O que não mudou, de propósito
- Nenhuma ferramenta timbra faturas. Criar um rascunho, sim; emiti-lo perante a autoridade fiscal é irreversível, e quem dispara isso é uma pessoa.
- O texto que o cliente final escreve no link público chega ao modelo marcado como
cliente_externoe delimitado, para que o modelo o informe e não o siga como instrução. - As ações que chegam ao cliente (enviar, aprovar, recusar, registrar pagamento) levam
destructiveHint; assim, um cliente MCP bem configurado pede confirmação antes de executá-las.
Se você conectar o Cord ao seu assistente, peça primeiro contexto_cuenta. O resto parte daí.
O servidor MCP do Cord conecta assistentes como o Claude ou o Cursor aos dados reais de um negócio: orçamentos, faturas, contas a receber vencidas. Com uma chave de escrita, ele também envia orçamentos e registra pagamentos. Ou seja: um modelo de linguagem agindo com as credenciais de uma empresa. Nós o auditamos de ponta a ponta, e este foi o resultado.
## 1. Uma chave de leitura podia escrever
O Cord expõe o MCP por dois transportes: HTTP sem estado (`POST /api/mcp`) e o SSE legado (`GET /api/mcp/sse` para abrir a sessão e `POST /api/mcp/message` para enviar mensagens). A sessão SSE guarda a organização, a chave e a permissão com que foi aberta.
O problema estava em `/api/mcp/message`. Ele verificava se a chave que envia a mensagem era **da mesma organização** que a sessão e, depois, executava a ferramenta com a **permissão guardada na sessão**. Uma chave somente leitura da mesma empresa que conhecesse um sessionId podia executar ferramentas de escrita com a sessão de outra chave.
A correção é uma linha, mas é a linha certa: a sessão pertence a uma chave, não a uma organização.
```ts
if (session.orgId !== auth.orgId || session.keyId !== auth.keyId) {
return rpcErrRes(null, -32001, 'Esta API key no corresponde a la sesión.', 403);
}
// e executa com auth.scope, a permissão de quem chama, não a da sessão
```
A correção tem seu teste: uma chave de leitura com o sessionId de uma chave de escrita recebe 403 e a ferramenta não é executada.
## 2. Um contrato de saída que não era cumprido
As 19 ferramentas declaravam `outputSchema`. Na versão 2025-06-18 do protocolo, declará-lo obriga a devolver um `structuredContent` que cumpra esse esquema. O Cord devolvia só texto. Além disso, os esquemas tinham `additionalProperties: false` e um `next_cursor` tipado como string que, na verdade, pode ser `null`: um cliente que valida teria rejeitado respostas corretas.
Agora toda ferramenta responde com as duas coisas: `structuredContent` para o cliente que valida e o mesmo JSON compacto como texto para o que não valida. Os esquemas de saída são abertos: um campo novo não quebra ninguém. E o texto deixou de ser indentado, o que economiza tokens em cada chamada.
## 3. Valores sem moeda
O Cord tem uma regra: um valor sem moeda é um número, não dinheiro. As ferramentas de orçamentos, produtos e clientes devolviam `total: 12500` sem dizer em quê. Um agente que presume pesos informa errado a um negócio que vende em dólares. Hoje cada valor viaja com sua `moneda`.
## 4. Erros que não chegavam ao modelo
Uma ferramenta de cobrança lançava um erro genérico quando o plano não a incluía. O servidor o tratava como falha interna e o modelo recebia “Erro interno do servidor”, sem ter como explicar ao usuário o que estava acontecendo. Ainda por cima, a mensagem citava o plano errado. Agora os limites de plano são informados como erro de negócio, com o nome do plano que realmente é necessário, lido do mesmo contrato que o resto do Cord usa.
No caminho, descobrimos que o índice da documentação devolvia o stack trace completo quando falhava. Não devolve mais.
## 5. Sete ferramentas novas
Passamos de 19 para 26. As novas foram pensadas para que um agente possa integrar o Cord, não só operá-lo:
| Ferramenta | Para quê |
|---|---|
| `contexto_cuenta` | Negócio, país, moeda, plano e se a chave é de teste ou de produção. A primeira chamada. |
| `proponer_configuracion` | Propõe perfil, impostos e catálogo a partir do site do negócio. Devolve um link; uma pessoa aprova. |
| `estado_configuracion` | Se essa proposta já foi aprovada, e o que foi aplicado. |
| `validar_datos_fiscales` | RFC, regime fiscal e uso do CFDI, NIF ou EIN com as mesmas regras usadas ao faturar. |
| `validar_apariencia` | Revisa um `appearance` do Cord Elements antes de você colocá-lo no código. |
| `simular_evento` | Dispara um webhook de teste pelo motor real. Somente com chave de teste. |
| `buscar_documentacion` | Busca na documentação e devolve as páginas com trecho e URL. |
Nenhuma delas altera as configurações da conta diretamente. `proponer_configuracion` deixa uma proposta que uma pessoa aprova no Cord, igual à do onboarding.
## 6. Uma API que se descreve sozinha
Um agente que escreve código contra uma API lê a especificação dela. Dos 343 campos do nosso OpenAPI, 279 não tinham descrição: o agente precisava adivinhar o que era `terminos`, se `total` incluía impostos ou em que unidade vinha `rate`.
Agora existe um glossário, `FIELD_DOCS`, que descreve cada nome de campo da API, e o gerador o aplica a cada propriedade sem descrição própria. A etapa de CI que compara a especificação com o código falha se uma propriedade ou um parâmetro ficar sem descrição. Adicionar um campo sem explicá-lo não passa mais.
## O que não mudou, de propósito
- **Nenhuma ferramenta timbra faturas.** Criar um rascunho, sim; emiti-lo perante a autoridade fiscal é irreversível, e quem dispara isso é uma pessoa.
- **O texto que o cliente final escreve** no link público chega ao modelo marcado como `cliente_externo` e delimitado, para que o modelo o informe e não o siga como instrução.
- **As ações que chegam ao cliente** (enviar, aprovar, recusar, registrar pagamento) levam `destructiveHint`; assim, um cliente MCP bem configurado pede confirmação antes de executá-las.
Se você conectar o Cord ao seu assistente, peça primeiro `contexto_cuenta`. O resto parte daí.