fiscal-mcp
by JoseTorquato
README.md
<!-- mcp-name: io.github.JoseTorquato/fiscal-mcp -->
<img src="https://raw.githubusercontent.com/JoseTorquato/fiscal-mcp/main/docs/logo.svg" width="72" alt="fiscal-mcp">
# fiscal-mcp
**Documento fiscal brasileiro como ferramenta de agente.** Valide NF-e e NFS-e
antes de transmitir — sem certificado, sem cadastro, sem enviar nada para lugar
nenhum.
[](https://github.com/JoseTorquato/fiscal-mcp/blob/main/LICENSE)
[](https://github.com/JoseTorquato/fiscal-mcp/blob/main/pyproject.toml)
[](https://github.com/JoseTorquato/fiscal-mcp/blob/main/tests/)
```bash
pip install "fiscal-mcp[xsd]"
fiscal-mcp validar nota.xml
```
```
[erro] ibs-cclasstrib-prefixo-cst
cClassTrib não corresponde ao CST do item
item 2: imposto/IBSCBS/cClassTrib = '000123' não começa por
imposto/IBSCBS/CST = '200'
→ Os três primeiros dígitos do cClassTrib são o CST do item. Este é o erro
mais comum ao ligar o módulo de IBS/CBS num ERP.
[erro] schema-elemento-fora-de-ordem
cEAN apareceu onde o leiaute espera xProd
item 1, linha 62 do XML
→ O schema da NF-e exige a sequência exata do leiaute — trocar a ordem
reprova mesmo com todos os campos presentes.
2 erro(s), 0 aviso(s)
```
Três camadas, num laudo só: **schema XSD oficial**, **regras fiscais** e **chave
de acesso**. Tudo local — o processo não abre socket, e há teste que prova.
---
## Por que existe
Rejeição da SEFAZ chega tarde, custa uma transmissão e vem com mensagem
críptica. Boa parte dos motivos é aritmética simples ou campo fora de formato —
coisa que dá para pegar **antes** de enviar, na sua própria máquina.
E agora tem prazo: desde **3 de agosto de 2026**, documentos fiscais do regime
regular precisam trazer os campos de IBS e CBS, e notas sem eles podem ser
rejeitadas ([CGIBS](https://www.cgibs.gov.br/novo-marco-da-reforma-tributaria-inicia-em-03-de-agosto-com-preenchimento-obrigatorio-dos-campos-relativos-ao-ibs-e-a-cbs)).
Enquanto isso, um agente de IA consegue mexer no seu Notion e no seu GitHub, mas
não sabe ler uma nota fiscal.
## O que ele faz
| Ferramenta | O que faz |
|---|---|
| `validar_nfe` | schema XSD oficial, regras fiscais (IBS/CBS incluso), totais e chave — com **o que fazer** em cada achado |
| `explicar_nfe` | resumo estruturado do XML, em vez do documento inteiro |
| `validar_nfse` | NFS-e do padrão nacional: estrutura, DPS embutida, prestador, serviço |
| `explicar_nfse` | resumo estruturado da NFS-e |
| `explicar_rejeicao` | código da SEFAZ → significado → ação, e se é reversível |
| `validar_chave_acesso` | decompõe os 44 dígitos da NF-e e confere o dígito verificador |
| `validar_chave_nfse` | decompõe os 50 dígitos da NFS-e nacional |
| `listar_rejeicoes_conhecidas` | o que o catálogo cobre |
**Nenhuma delas assina, transmite, emite ou cancela documento.** Não existe
caminho, nesta versão, para causar efeito fiscal — e um teste verifica isso a
cada mudança.
## Como usar
### Na linha de comando
```bash
fiscal-mcp validar nota.xml # NF-e ou NFS-e, ele descobre sozinho
fiscal-mcp validar nota.xml --json # para script e CI
fiscal-mcp validar nota.xml --sem-schema
fiscal-mcp explicar nota.xml # resumo estruturado
fiscal-mcp chave 4326081234... # 44 dígitos (NF-e) ou 50 (NFS-e)
fiscal-mcp rejeicao 539 # traduz o código da SEFAZ
fiscal-mcp rejeicao # lista o catálogo
fiscal-mcp tabelas # qual tabela oficial está embarcada
```
Sai com código 1 quando encontra erro, então serve direto em CI.
### Como servidor MCP
```bash
pip install "fiscal-mcp[servidor,xsd]"
```
Copie e cole no seu cliente. **Claude Code:**
```bash
claude mcp add fiscal -- fiscal-mcp-servidor
```
**Claude Desktop** (`claude_desktop_config.json`), **Cursor**
(`.cursor/mcp.json`), **VS Code** e a maioria dos outros:
```json
{
"mcpServers": {
"fiscal": { "command": "fiscal-mcp-servidor" }
}
}
```
**Sem instalar Python** — útil para quem trabalha com ERP em Delphi ou C#:
```json
{
"mcpServers": {
"fiscal": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/josetorquato/fiscal-mcp"]
}
}
}
```
Aí é só perguntar ao agente: *"esse XML está pronto para transmitir?"*
## O que ele não faz
Escrito antes das perguntas, porque prometer demais é o jeito mais rápido de
perder a confiança de quem trabalha com fiscal:
- **Não emite, não assina, não transmite.** Sem certificado digital envolvido.
- **Passar aqui não garante autorização.** É validação local: pega o erro
previsível, não substitui a SEFAZ.
- **A validação por schema exige o extra `[xsd]`.** Sem ele, o laudo diz que
essa camada não rodou — nunca finge que rodou.
- **NFS-e só no padrão nacional.** Município com padrão próprio não é
reconhecido ([ADR-0006](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/0006-estrategia-nfse-municipal.md)).
- **Não verifica dígito verificador de NFS-e** — o algoritmo não foi
confirmado, e chutar produziria acusação falsa.
- **Não valida assinatura digital.** Documento não assinado nunca passa no XSD
oficial; o laudo diz isso como informação, não como erro.
- **Não dá conselho tributário.** CFOP, CST e alíquota são do seu contador.
### Estado da validação, por documento
| | Camadas | Regras | Testado contra |
|---|---|---|---|
| NF-e / NFC-e | schema XSD + regras + chave | 28 | ✅ **duas NF-e reais autorizadas em produção**, 17 amostras públicas e XML sintético |
| NFS-e nacional | regras + chave | 10 | ✅ uma nota autorizada de verdade |
### Contra nota real, autorizada em produção
Em 26/08/2026 o validador rodou em duas NF-e reais, ambas **autorizadas pela
SEFAZ em ambiente de produção**:
| Nota | Grupo IBS/CBS | Erros | Avisos |
|---|---|---|---|
| emitida em 26/08/2026 | presente e preenchido | **0** | **0** |
| emitida em 09/04/2026 | ausente (anterior ao grupo) | **0** | 1 — `ibs-grupo-ausente` |
A primeira é o caso que importa: uma nota da reforma, aceita pela SEFAZ, passou
limpa nas 28 regras e no XSD oficial. Qualquer achado ali seria falso positivo
por definição. A segunda mostra o outro lado — a ferramenta **não reprovou** uma
nota pré-reforma, apontou a ausência como aviso, que é a leitura correta da
postergação da UB12-10.
Que as regras não estavam apenas dormentes foi verificado por contraprova:
corrompendo o `cClassTrib` da nota real, três regras disparam; corrompendo o
`CST`, duas.
**O que isso ainda não cobre.** Duas notas, de um item cada, do mesmo emissor,
no caminho tributado normal. Monofasia (CST 620), redução de alíquota, crédito
presumido e ajuste de competência continuam testados só contra fixture. A regra
`ibs-subgrupos-obrigatorios` segue como **aviso** até rodar contra três XMLs
reais distintos sem falso positivo — o contrato está no
[CHANGELOG](https://github.com/JoseTorquato/fiscal-mcp/blob/main/CHANGELOG.md).
**Tem nota real para contribuir?** Continua sendo o que mais ajuda, e agora tem
ferramenta para isso: `python scripts/anonimizar.py nota.xml`.
### Contra o corpus público
As 17 amostras MIT da `nfelib` têm estrutura de documento real — 15 passam com
zero erros, e as duas que não passam são explicáveis: uma é inválida no XSD
oficial de propósito, a outra tem valores de preenchimento incoerentes entre si
(`vIBS = 0` com `vIBSUF = 16`). **Nenhuma regra de tabela acusa nenhuma delas**,
e há teste que trava isso.
Das 28 regras de NF-e, **17 são da Camada A de IBS/CBS**: CST e `cClassTrib`
conferidos contra a **tabela oficial embarcada** da SVRS — 18 CST e 164
classificações, versionadas no repositório com URL de origem, data e sha256
([procedência](https://github.com/JoseTorquato/fiscal-mcp/blob/main/regras/tabelas/PROCEDENCIA.md)).
Mais aritmética por item, exclusividade de regime, presença condicional e as
alíquotas de transição de 2026.
`fiscal-mcp tabelas` diz qual versão da tabela está embarcada. Quem valida contra
tabela precisa saber contra qual.
A regra de IBS/CBS emite **aviso**, não erro: a NT 2025.002 v1.51 reclassificou
a regra de rejeição correspondente (UB12-10) como *implementação futura*, então
a nota não é recusada por isso hoje. Ela carrega data de reavaliação, e um teste
falha quando essa data passa. Acusar errado é pior que não acusar.
**Tem um XML real que pode compartilhar?** É a contribuição mais valiosa
possível agora — abra uma issue com os dados trocados por fictícios.
## Escrever uma regra
Regras são dados, não código. Absorver uma nota técnica deveria ser editar um
YAML — e é:
```yaml
- id: tot-produtos-confere
tipo: soma_itens
severidade: erro # erro | aviso | informacao
campo_item: prod/vProd
campo_total: total/ICMSTot/vProd
tolerancia: "0.01"
mensagem: O total de produtos não bate com a soma dos itens
acao: >
Some o vProd de cada item e compare com total/ICMSTot/vProd.
```
Tipos disponíveis: `existe`, `nao_vazio`, `valor_em`, `formato`, `soma_itens`,
`condicional`.
**Escopo.** Por padrão a regra roda uma vez, na raiz da nota. Com `escopo: item`
ela roda uma vez por item, com os caminhos relativos ao `det` — e o achado diz
qual item, pelo `nItem`:
```yaml
- id: ibs-grupo-ausente
tipo: existe
escopo: item # documento (padrão) | item
campo: imposto/IBSCBS
```
**Caminho absoluto.** Num `escopo: item`, o caminho que começa com `/` vale a
partir da raiz da nota. É o que permite uma regra olhar o item e algo fora dele
ao mesmo tempo:
```yaml
- id: ibs-totais-presentes
tipo: condicional
escopo: item
quando_campo: imposto/IBSCBS/CST # relativo ao item
campo: /total/IBSCBSTot # a partir da raiz
```
**Vigência.** Regra que ainda não estabilizou declara quando será reavaliada.
Não é comentário: um teste falha quando a data passa, e é assim que a manutenção
deixa de depender de memória.
```yaml
vigencia:
reavaliar_em: "2026-09-01"
fonte: "Ato Técnico Conjunto RFB/CGIBS nº 1, de 31/07/2026"
```
**Todo achado precisa de `acao`.** Quem lê é um agente que vai tentar de novo —
erro sem ação vira loop de retry ou nota duplicada. Um teste falha se alguma
regra não tiver.
## Contribuir
O que mais ajuda, em ordem:
1. **XML real anonimizado** — principalmente NF-e, e municípios de NFS-e
diferentes.
2. **Código de rejeição que você levou** e não está no catálogo.
3. **Regra nova** em `regras/`, com as duas fixtures.
4. **Leitura da seção 7 da NT 2025.002-RTC v1.51** — o leiaute de IBS/CBS já
está mapeado; o que falta confirmar em fonte primária são os códigos de
rejeição, e nenhum entra aqui sem leitura humana.
Antes de abrir PR, leia o [CONTRIBUTING](https://github.com/JoseTorquato/fiscal-mcp/blob/main/CONTRIBUTING.md).
Há uma regra sem exceção: **PR com dado fiscal real identificável é fechado sem
merge.**
## Como isso vai crescer
O produto é a **validação**: o validador mais fundo que existe para NF-e, que
roda offline e que você pode conferir antes de transmitir. Emissão está
**suspensa, com gatilho escrito** — ver
[ADR-0011](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/0011-validacao-e-o-produto.md).
O que vem depois, e por que nesta ordem, está escrito:
| Documento | Para quê |
|---|---|
| [ROADMAP.md](https://github.com/JoseTorquato/fiscal-mcp/blob/main/ROADMAP.md) | as fases e o critério de saída de cada uma |
| [BACKLOG.md](https://github.com/JoseTorquato/fiscal-mcp/blob/main/BACKLOG.md) | as tarefas, priorizadas |
| [docs/adr/](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/) | as decisões e por que foram tomadas assim |
| [docs/spec/](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/spec/) | o produto em detalhe |
Três decisões que explicam o resto:
- **[ADR-0011](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/0011-validacao-e-o-produto.md)** — validação é o
produto; emissão sai do caminho crítico e só volta se um gatilho nomeado
disparar.
- **[ADR-0008](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/0008-validar-antes-de-construir.md)** — não escrevo
integração com SEFAZ antes de saber que existe quem pague pela manutenção.
- **[ADR-0005](https://github.com/JoseTorquato/fiscal-mcp/blob/main/docs/adr/0005-certificado-nunca-transita.md)** — certificado
digital de cliente não passa pela minha infra. O A1 é a identidade jurídica da
empresa.
## Licença
MIT — código e regras.
---
Feito por [José Torquato](https://josetorquato.dev), que também mantém o
[Cilada](https://josetorquato.dev/cilada/).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive