MCP Sienge Node
# Conector Sienge para Claude — servidor MCP do ERP Sienge
**Servidor [MCP](https://modelcontextprotocol.io) que põe o
[Sienge](https://www.sienge.com.br) dentro do Claude** — Claude Code, Claude
Desktop ou qualquer cliente MCP conversando com a API do Sienge, o ERP de
construção civil e incorporação da Softplan. Integração **não oficial**,
escrita por quem usa o ERP.
Compras, contratos de suprimentos e medições viram ferramentas que o assistente
chama direto: consultar um contrato com valor, prazo e saldo, baixar os anexos,
ver a fila de aprovação, criar uma solicitação de compra. Tudo **em português e
por nome** — "Residencial Aurora", "tubo de esgoto", "instalações hidrossanitárias" —,
com os códigos internos resolvidos dentro do servidor.
```bash
npx -y mcp-sienge-node
```
JavaScript puro (ESM), sem etapa de build e sem dependência além do SDK do MCP.
> ⚠️ **ALFA — 0.13.1.** Em reescrita. A arquitetura mudou por inteiro na série 0.7 e
> nomes de tool, formato de retorno e variáveis de ambiente ainda vão mudar sem
> aviso. Compras e contratos já **gravam no ERP**: use primeiro num ambiente de
> homologação, e leia a seção [Antes de apontar para produção](#antes-de-apontar-para-produção).
## Instalação no Claude Desktop
Edite o arquivo de configuração:
| Sistema | Caminho |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
```json
{
"mcpServers": {
"sienge": {
"command": "npx",
"args": ["-y", "mcp-sienge-node"],
"env": {
"SIENGE_USERNAME": "seu-usuario",
"SIENGE_PASSWORD": "sua-senha",
"SIENGE_SUBDOMAIN": "sua-empresa",
"SIENGE_PROFILE": "compras,contratos"
}
}
}
}
```
**Reinicie o Claude Desktop depois de salvar** — ele lê esse arquivo só na
inicialização, e fechar a janela não encerra o processo. Use Cmd+Q (macOS) ou
saia pela bandeja (Windows).
`SIENGE_PROFILE` pré-carrega os módulos na subida. No Claude Desktop ele não é
opcional na prática: sem ele, as ferramentas carregadas no meio da conversa
podem não aparecer — ver [Se as ferramentas não aparecerem](#se-as-ferramentas-não-aparecerem-depois-de-carregar_).
Deixe só os módulos que você usa; cada um custa tokens em toda mensagem.
Se preferir Bearer Token no lugar de usuário e senha, troque as duas primeiras
variáveis por `"SIENGE_API_KEY": "sua-chave"`. `SIENGE_SUBDOMAIN` é sempre
necessário: ele compõe a URL de toda chamada.
Outros clientes MCP (Claude Code, Cursor, Zed) usam o mesmo formato de
`command`/`args`/`env`, em arquivo próprio.
### Verificando que funcionou
Depois de reiniciar, peça ao assistente: *"testa a conexão com o Sienge"*. Ele
deve chamar `testar_conexao` e responder com a latência. Se a autenticação
estiver incompleta, `verificar_autenticacao` diz o que falta sem gastar chamada
na API.
## Configuração
| Variável | Obrigatória | Para quê |
|---|---|---|
| `SIENGE_SUBDOMAIN` | ✅ | subdomínio da empresa; compõe a URL de toda chamada |
| `SIENGE_API_KEY` | uma das duas | Bearer Token |
| `SIENGE_USERNAME` + `SIENGE_PASSWORD` | uma das duas | Basic Auth |
| `SIENGE_PROFILE` | — | módulos carregados já na subida. Vazio = só o núcleo |
| `SIENGE_PASTA_ANEXOS` | — | pasta onde `contratos_baixar_anexos` salva os arquivos |
| `SIENGE_BASE_URL` | — | trocar o host da API |
### Para criar solicitações de compra
A criação exige dados que não saem da credencial nem do pedido do usuário. São
constantes da instalação, então ficam no ambiente e não custam nada no schema
das tools:
| Variável | Obrigatória | Para quê |
|---|---|---|
| `SIENGE_SOLICITANTE` | ✅ | usuário do Sienge que **assina** a solicitação |
| `SIENGE_CADASTRANTE` | — | quem **registra**, se for diferente de quem assina. Vazio = o solicitante |
| `SIENGE_NIVEL_APROPRIACAO` | — | nível da EAP em que a obra apropria: `02.032` é nível 2. Vazio = todos |
| `SIENGE_DEPARTAMENTO` | — | preencha se o Sienge recusar a criação citando o departamento |
| `SIENGE_CATEGORIA` | — | idem, para a categoria |
`SIENGE_NIVEL_APROPRIACAO` merece atenção: sem ele, um item de orçamento de
qualquer profundidade vira alvo de apropriação, e apropriar no nível errado é
erro de cadastro. Com ele, a lista de itens candidatos encolhe a ponto de o
assistente escolher sozinho e só confirmar com você.
Lista completa e comentada em [`.env.example`](.env.example).
## Como o catálogo é carregado
**A descrição e o schema de cada tool são reenviados ao modelo a cada
mensagem.** Não é uma vez na conexão: é em toda pergunta, junto com o histórico
inteiro da conversa. Uma tool que ninguém vai usar naquela conversa continua
sendo paga, mensagem após mensagem — e um catálogo grande também piora a
escolha do modelo, que passa a decidir entre dezenas de opções parecidas.
Por isso o servidor sobe **só com o núcleo** e o resto entra sob demanda,
agrupado por assunto:
| Módulo | Carrega com | Tools | Assunto |
|---|---|---|---|
| núcleo | *(sempre carregado)* | 3 | diagnóstico, credencial, conexão |
| `compras` | `carregar_compras` | 7 | solicitação, pedido, aprovação, recebimento |
| `contratos` | `carregar_contratos` | 4 | contrato de suprimentos, anexos e aprovação |
| `financeiro` | `carregar_financeiro` | 1 | contas a pagar e receber *(esqueleto)* |
Na prática: alguém pergunta **"quais pedidos estão esperando aprovação?"**. O
assistente vê no catálogo apenas as três tools do núcleo e as ferramentas
`carregar_*`, cada uma com uma linha dizendo o que traz. Ele chama
`carregar_compras`, as sete tools de compras entram, e a conversa segue. **As
tools de contratos e de financeiro nunca são carregadas** — e nunca são pagas.
O que isso poupa, medido no catálogo real deste servidor:
| Sessão | O que fica carregado | Custo por mensagem |
|---|---|---|
| só compras | núcleo + compras | ~8,4 KB |
| só contratos | núcleo + contratos | ~5,3 KB |
| tudo carregado | núcleo + os três módulos | ~13,1 KB |
A diferença parece pequena em bytes e não é: ela é **multiplicada pelo número
de mensagens da conversa**. Numa conversa de trinta trocas sobre compras,
carregar contratos e financeiro junto custaria uns 141 KB de contexto que
ninguém leu.
`descarregar_modulos` faz o caminho de volta e devolve o catálogo ao núcleo,
para quando o assunto muda no meio da conversa.
Para uma operação que sempre usa os mesmos módulos,
`SIENGE_PROFILE=compras,contratos` deixa o recorte pronto na subida, sem
depender do carregamento dinâmico. É a escolha certa quando você **sabe** o que
vai usar; o carregamento sob demanda existe para quando não se sabe.
### Se as ferramentas não aparecerem depois de `carregar_*`
Quando o catálogo muda, o servidor avisa o cliente (`tools/list_changed`) para
ele buscar a lista de novo. **Alguns clientes, entre eles o Claude Desktop, não
reagem no meio da conversa**: a lista que o assistente enxerga foi congelada
quando a sessão começou. As tools ficam registradas no servidor e invisíveis do
outro lado.
O sintoma engana. O assistente não recebe "ainda não indexei"; ele recebe uma
recusa e conclui que as ferramentas não existem — foi assim que uma sessão
chegou a afirmar que o módulo de contratos não estava disponível, com o módulo
carregado e funcionando.
Dois caminhos:
- **Na hora:** a resposta do `carregar_*` traz os nomes exatos das ferramentas.
Chamar pelo nome costuma funcionar mesmo com a lista desatualizada.
- **De vez:** pré-carregue com `SIENGE_PROFILE=compras,contratos`. Os módulos
sobem antes do primeiro `tools/list`, então não há mudança de catálogo para o
cliente ignorar. O preço é pagar esses módulos em toda mensagem, inclusive
nas conversas que não usam nenhum deles.
## Estado
Reescrita em andamento. A 0.7.0 trocou a arquitetura inteira e recomeçou o
catálogo de tools pelo ciclo de compras; a 0.10.0 abriu o de contratos de
suprimentos, que é onde a obra contrata serviço e paga por medição.
| Módulo | Tools | Estado |
|---|---|---|
| `nucleo` | 3 | ✅ diagnóstico e autenticação |
| `compras` | 7 | 🔨 solicitação e pedido; falta cotação e nota fiscal |
| `contratos` | 4 | 🔨 consulta, anexos e aprovação; medição pronta em `client/`, sem tool |
| `financeiro` | 1 | ⚠️ apenas um esqueleto de teste, não lê nada do ERP |
### As tools de hoje
| Tool | O que faz |
|---|---|
| `status_servidor` | confirma que o servidor está no ar e há quanto tempo |
| `testar_conexao` | testa a credencial contra a API, com uma chamada barata |
| `verificar_autenticacao` | qual mecanismo está configurado, sem chamar a API |
| `compras_processo` | o processo de compras de ponta a ponta, e o que este servidor **não** cobre |
| `compras_criar_solicitacao` | cria uma solicitação, com vários itens, a partir de nomes e com prévia antes de gravar |
| `compras_solicitacoes_para_aprovacao` | a fila de solicitações pendentes, agrupada por solicitação |
| `compras_decidir_solicitacoes` | aprova ou reprova itens e solicitações, conferindo antes contra a fila real |
| `compras_pedidos_para_aprovacao` | a fila de pedidos pendentes, com itens e fornecedor resolvidos |
| `compras_decidir_pedidos` | aprova ou reprova pedidos de compra, com o valor na prévia — **não envia e-mail**, ver abaixo |
| `compras_pedidos_pendentes_recebimento` | o que foi aprovado e ainda não chegou |
| `contratos_detalhar` | tudo de um contrato numa chamada: fornecedor, valor, prazo, saldo e os itens com preço unitário |
| `contratos_baixar_anexos` | salva os anexos do contrato numa pasta local e devolve o caminho |
| `contratos_pendentes_aprovacao` | contratos e aditivos pendentes, com fornecedor, valor, prazo, motivo e itens |
| `contratos_decidir` | aprova ou reprova os contratos escolhidos, conferindo contra a fila real e com prévia |
| `carregar_compras` / `carregar_contratos` / `carregar_financeiro` | trazem as tools do módulo |
| `descarregar_modulos` | libera o contexto dos módulos carregados |
### O processo de compras, e o que falta
O Sienge percorre até seis etapas. `compras_processo` descreve todas ao
assistente — inclusive as que este servidor não cobre, para que ele não
prometa o que não faz.
| Etapa | Cobertura |
|---|---|
| 1 · Solicitação | criar ✅ · consultar ❌ |
| 2 · Aprovação da solicitação | fila ✅ · aprovar ✅ · reprovar ✅ |
| 3 · Cotação | ❌ |
| 4 · Pedido de compra | fila ✅ |
| 5 · Aprovação do pedido | fila ✅ · aprovar ✅ · reprovar ✅ |
| 6 · Nota fiscal | pendências ✅ · lançar ❌ |
As escritas desta etapa são três: criar solicitação, decidir solicitação e
decidir pedido de compra.
### O ciclo do contrato de suprimentos
**Outro módulo, outro ciclo** — `contratos_*`, carregado à parte por
`carregar_contratos`, sem nenhuma dependência de compras. Não é a continuação
do processo de compra: é o caminho alternativo. A compra termina numa
**entrega**; o contrato, numa **medição** — alguém confere quanto do serviço
foi executado, e é isso que vira conta a pagar.
O módulo expõe **quatro tools**: `contratos_detalhar`,
`contratos_baixar_anexos`, `contratos_pendentes_aprovacao` e
`contratos_decidir`. O resto do ciclo já está implementado e testado em
`client/supplyContractClient.js`, sem tool declarada — porque tool parada custa
tokens em toda mensagem, e porque as escritas de medição gravam no ERP.
| Etapa | Client | Tool |
|---|---|---|
| Contrato — consultar | ✅ | ✅ `contratos_detalhar` |
| Contrato — listar por obra e período | ✅ | — |
| Anexos — baixar | ✅ | ✅ `contratos_baixar_anexos` |
| Anexos — anexar | ❌ | — |
| Contrato e aditivo — listar pendentes | ✅ | ✅ `contratos_pendentes_aprovacao` |
| Contrato e aditivo — aprovar e reprovar | ✅ | ✅ `contratos_decidir` ✏️ grava |
| Medição — consultar | ✅ | — |
| Medição — criar | ✅ | — ✏️ grava |
| Medição — autorizar e reprovar | ✅ | — ✏️ grava |
| Liberação (o título a pagar) | ✅ consultar · ❌ liberar — a API não expõe | — |
| Aditivos — consultar | ✅ | — |
Três coisas deste recurso não se adivinham, e as tools já as tratam por dentro:
- **O contrato não tem id.** A identidade é o par documento + número (`CTS`,
`325`), e ninguém sabe de cabeça que o documento é `CTS`. As tools aceitam o
número solto, parte do objeto ou só a obra.
- **Não existe listagem sem período.** Toda busca varre uma janela de 4 anos e
**diz na resposta** qual janela varreu — ausente na janela não é inexistente.
- **Não existe saldo de item de contrato.** O saldo que a prévia de medição
mostra é derivado da última medição e vai rotulado como tal; ele ignora
aditivo posterior, então estourá-lo é aviso, nunca bloqueio.
**Uma escrita de contrato está exposta: decidir** (aprovar ou reprovar). Criar
medição e decidir medição existem no client e esperam ser pedidas.
## Como `contratos_detalhar` resolve um contrato
Vale abrir esta, porque quase tudo que ela faz existe para contornar um jeito
de a resposta sair errada **sem erro nenhum**.
A informação está espalhada por cinco endpoints: o cabeçalho num, o saldo
noutro, o fornecedor no cadastro de credores, as obras num terceiro, os itens
num quarto — e os itens ainda vivem por planilha. Encadear isso como tools
faria **cada passo reenviar a conversa inteira** ao modelo. Por isso é uma
chamada só, e a tradução acontece no servidor, onde é de graça.
**1 · De quem estamos falando.** O contrato não tem id: a identidade é o par
documento + número (`CTS`, `325`), e ninguém sabe de cabeça que o documento é
`CTS`. Quatro caminhos, do mais barato ao mais caro:
| Você informa | O que acontece |
|---|---|
| documento **e** número | um GET direto, confirma que existe |
| só o número | varre a janela e casa pelo número |
| um texto (`"instalações hidrossanitárias"`) | varre a janela e casa pelo objeto |
| só a obra | varre a janela dela; havendo um contrato só, resolve |
A obra vem antes, por nome, descartando os cadastros marcados "NÃO USAR" —
obra desativada que a conta mantém por histórico.
**2 · A janela.** A API não lista contrato sem período; não existe "todos". A
varredura usa **4 anos até hoje e devolve, na resposta, qual janela varreu**.
Sem isso, "não achei" vira "não existe", que é outra coisa — a mensagem diz
onde olhou e que `desde` amplia.
**3 · Quando o nome não casa, a tool não adivinha.** O nome do cadastro
raramente é o nome que a pessoa usa: em produção, "instalações
hidrossanitárias" está gravado como *"SERVIÇO DE INSTALAÇÃO HIDRAULICA,
ESGOTO, GÁS E INCÊNDIO"*. Isso é sinonímia de obra, não de grafia, e nenhuma
regra de texto liga os dois sem chutar. Então a resposta traz **os contratos da
janela ordenados por relevância**, cada um com o seu par — a obra em que isso
aconteceu tem 75 contratos em quatro anos, e ordenar por data escondia
justamente o certo.
**4 · O cabeçalho é buscado de novo**, mesmo quando o passo 1 já achou o
contrato na listagem: só o GET de um contrato devolve `materialBalance` e
`laborBalance`. A listagem não traz saldo.
**5 · A obra tem dois ids, e o óbvio é o errado.**
`/supply-contracts/buildings` devolve `buildingID` (interno) e
`buildingIdView` (código no Sienge). Só o View é aceito nos demais endpoints:
```
/supply-contracts/items?buildingId=21 → 404 "Obra 21 não encontrada"
/supply-contracts/all?buildingId=21 → 200 com 85 contratos de OUTRA obra
/supply-contracts/all?buildingId=20 → 200 com os 75 contratos certos
```
O 404 aparece; os 85 contratos errados, não. Conferido contra produção — é a
única falha aqui capaz de produzir uma resposta confiante e completamente
errada.
**6 · Os itens saem por planilha** (obra × unidade construtiva); não existe
"todos os itens do contrato". `incluir_itens: false` corta essas N chamadas
quando a pergunta não envolve item.
### O que a tool calcula, porque o Sienge não devolve pronto
O ERP guarda material e mão de obra sempre separados — eles medem e pagam
separado. Ninguém pergunta assim.
| Campo | De onde sai |
|---|---|
| `valor_total` | material + mão de obra |
| `saldo_total` | saldo de material + saldo de mão de obra |
| `prazo` | início, fim e `dias_restantes` (negativo se já venceu) |
| `precoUnitario` | preço de material + de mão de obra, por item |
| `valorTotal` | quantidade × preço unitário |
| `mensuravel` | derivado: item sem `resourceId` nem `workItemId` é agrupador |
Uma regra atravessa todas: **ausência não vira zero**. `saldo_total` some da
resposta quando a API não mandou o campo, em vez de virar `0` — saldo zero é
"acabou", saldo ausente é "não sei", e a listagem nunca traz saldo.
## Como a aprovação de contratos funciona
Duas perguntas, duas tools:
> **"Quais contratos estão pendentes de aprovação no Sienge?"**
> → `contratos_pendentes_aprovacao`
>
> **"Aprova o CTS/524 e o CTS/596"**, **"aprova todos"** ou **"reprova o CTS/596"**
> → `contratos_decidir`
**A lista vem completa numa chamada.** Para cada pendente: fornecedor, obra,
valor, prazo, o **motivo** de estar pendente — o Sienge informa, por exemplo,
*"valor total do contrato excede o limite permitido para o usuário"* — e os
itens com preço unitário. Nada disso exige o assistente chamar outra tool.
**Aditivo aparece como aditivo.** O Sienge não tem aprovação separada de
aditivo: quando um aditivo é registrado, o contrato inteiro volta a aguardar
autorização, na alçada `ADDENDUM`, e é aprovado pelo mesmo caminho. A lista traz
o que o aditivo mais recente mudou — mas **a API não diz qual aditivo está
pendente**, então isso vai rotulado como "o mais recente", não como certeza.
**"Aprova todos" não é um atalho.** Não existe "aprovar tudo que estiver
pendente". O assistente passa a lista que acabou de mostrar — e é isso que
impede aprovar um contrato que entrou na fila **depois** da listagem, sem
ninguém ter olhado para ele. O que ficou de fora volta em `continuam_pendentes`.
**Antes de gravar, a fila é relida.** Se outra pessoa aprovou um dos contratos
nesse meio-tempo, ele não está mais lá e não é gravado. E se **uma** referência
da lista estiver errada, **nenhuma** é aprovada — um número trocado no meio não
deixa metade aprovada.
**Aprovar e reprovar ficam na mesma tool**, escolhidos em `decisao`, e passam
pela mesma conferência — nenhuma das duas tem volta. Sem `decisao`, a tool
aprova; reprovar nunca é o padrão. Ao reprovar, o motivo vai em `observacao` e
fica gravado no contrato.
**Só aparece o que ainda está para decidir.** A fila "aguardando autorização"
do Sienge traz mais do que o nome promete, e três grupos ficam de fora — da
lista e da decisão:
| Fica de fora | Por quê |
|---|---|
| Cadastro em inclusão | alguém ainda está cadastrando: valor zerado, sem obra |
| Reprovados | reprovar **não** tira o contrato da fila de "aguardando" |
| Concluídos e revogados | o contrato já terminou ou foi desfeito; não há o que autorizar |
O segundo é o que mais engana. Em produção, dois contratos reprovados apareciam
ao mesmo tempo no filtro de "aguardando autorização" e no de "reprovados".
Confiando no nome do filtro, a tool ofereceria para aprovar o que alguém já
reprovou. Dos 9 contratos que o Sienge chamava de pendentes, **3 eram de fato
para decidir**.
Uma recusa do Sienge num contrato não desfaz os outros.
## Antes de apontar para produção
- **Comece em homologação.** Uma solicitação criada por engano não pode ser
apagada pela API: o Sienge não expõe `DELETE` de solicitação.
- **A criação não é atômica.** A API grava cabeçalho e itens em dois `POST`.
Se o segundo falhar, fica uma solicitação sem itens; o retorno diz o id para
você resolver pela tela.
- **Decidir não tem volta.** A API não expõe endpoint que desfaça uma
autorização nem uma reprovação. A tool confere contra a fila real e exige
`confirmar: true`, mas depois de gravado só o ERP resolve. Deixar um item
sem decisão é legítimo: liste só o que foi decidido.
- **A prévia é o portão.** Sem `confirmar: true`, `compras_criar_solicitacao`
resolve tudo e devolve o que seria gravado, sem gravar. Confira a unidade de
medida e o item de orçamento ali — é o último ponto antes do ERP.
- **Aprovar pedido pela API não envia e-mail — bug do Sienge.** Na tela, aprovar
um pedido dispara os envios parametrizados: a via ao fornecedor, o aviso ao
usuário do Sienge e o relatório à obra. Pelo endpoint, **nenhum deles sai**,
mesmo com o envio automático ligado no ERP. Não é configuração faltando nem
limitação deste servidor: é o endpoint que não executa o gatilho que a tela
executa. O pedido fica aprovado e ninguém é avisado — combine o envio por
fora. A tool repete esse aviso em toda resposta de aprovação.
- **Aprovar ou reprovar contrato não tem volta.** A API não expõe endpoint que
desfaça nenhuma das duas. A tool relê a fila e exige `confirmar: true`, mas depois de
gravado só o ERP resolve. O aviso ao responsável só sai se o ERP estiver
parametrizado para sempre enviar.
- **Baixar anexo escreve no seu disco, não no ERP.** Os arquivos vão para
`SIENGE_PASTA_ANEXOS`, numa subpasta por contrato. A tool grava os bytes como
vieram e **não lê o conteúdo** — não espere dela um resumo do PDF.
- **Não há trilha de auditoria.** A versão anterior gravava um log de escrita;
essa parte ainda não foi reescrita.
## Desenvolvimento
```bash
git clone https://github.com/frlorenzon/MCP-Sienge-Node.git
cd MCP-Sienge-Node
npm install
cp .env.example .env # preencha as credenciais
npm start
```
### Testes
```bash
npm test
```
137 testes com o runner nativo do Node, sem dependência nenhuma. **Nenhum toca
a API do Sienge** — sobem um servidor HTTP local que responde nos schemas de
`spec/openapi.yaml`, então rodam offline e não consomem cota.
Testar contra HTTP de verdade, em vez de dublar `makeRequest`, é o que faz a
suíte cobrir o que mais quebrou neste projeto: o corpo exato enviado ao ERP, o
formato de erro do Sienge e a paginação. Cada caso corresponde a um defeito que
já aconteceu contra o Sienge real.
### Estrutura
```
src/
├── index.js bootstrap stdio
├── config.js credenciais e resolução de auth
├── toolsGroupRouter.js tools/list, tools/call e carregamento sob demanda
├── api/ um arquivo por recurso REST do Sienge
│ ├── purchase-requests-v1.js
│ ├── purchase-orders-v1.js
│ ├── supply-contracts-v1.js
│ ├── supply-contracts-measurements-v1.js
│ ├── building-cost-estimations-v1.js
│ ├── creditor-v1.js
│ └── cost-center-v1.js
├── client/
│ ├── siengeClient.js o único ponto que fala HTTP com o Sienge —
│ │ makeRequest para JSON, baixarArquivo para bytes
│ ├── purchaseClient.js compõe as funções de api/ no que uma pergunta de
│ │ negócio precisa: resolve nomes, agrupa, projeta
│ └── supplyContractClient.js idem, para contratos e medições
├── modules/ o que vira tool: core, purchase, supplyContract,
│ financial
└── knowledge/ o processo de compras (conhecimento, não API)
spec/openapi.yaml a especificação publicada do Sienge
test/ Sienge falso + os casos
```
Três camadas, e a divisão importa por causa do custo: **`modules/` é a
superfície MCP e custa tokens em toda requisição; `api/` e `client/` não custam
nada.** Toda lógica que puder descer, desce — é por isso que
`compras_criar_solicitacao` aceita "tubo de esgoto" e "instalações
hidráulicas" em vez de ids: resolver nomes dentro do servidor é de graça,
enquanto fazer o modelo encadear quatro tools para descobrir os mesmos ids
reenvia a conversa inteira a cada passo.
### Sobre `spec/openapi.yaml`
Cópia local da especificação do Sienge, com a procedência em
[`spec/README.md`](spec/README.md). Sem ela, nome de campo vira palpite — e
palpite falha em silêncio: um filtro inexistente é ignorado pelo servidor, um
campo com nome errado volta `undefined`, e o resultado sai vazio sem erro
nenhum. Confira ali antes de escrever qualquer coisa em `src/api/`.
## Licença
[PolyForm Noncommercial 1.0.0](LICENSE) — uso livre para fins não comerciais.
Para uso comercial, contate o autor.
TDQS
Scored across 5 tools
The two authentication tools (testar_conexao and verificar_autenticacao) are similar but explicitly cross-referenced to clarify their distinct purposes: one tests real API access, the other inspects configuration. The remaining three tools each target a clearly unique aspect (quotas, process explanation, and loading purchasing tools), so overall ambiguity is low.
All tool names follow a consistent verb_noun pattern in snake_case: testar_conexao, verificar_autenticacao, consultar_cota, explicar_processo_compras, carregar_compras. This uniform structure makes the set predictable and easy to navigate.
With 5 tools, the server is well within the ideal 3-15 range. Each tool serves a distinct purpose—auth testing, auth config, quota checking, process explanation, and loading purchase tools—so the count is appropriate for a focused Sienge integration server.
The tool set lacks direct CRUD or query operations for core Sienge resources like purchase orders, suppliers, or invoices, which are essential for a purchasing-focused server. The description of explicar_processo_compras explicitly states that not every stage of the procurement process is covered, and carregar_compras hints at additional tools that are not actually exposed, leaving significant functional gaps.