# 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 dos anexos, em duas subpastas: `downloads/`, onde `contratos_baixar_anexos` salva o que baixa, e `uploads/`, de onde `compras_lancar_nota_fiscal` tira a DANFE e o boleto pelo número da nota | | `SIENGE_BASE_URL` | — | trocar o host da API | | `SIENGE_NF_SERIE` | — | série com que a nota fiscal de compra é lançada. Vazio = `SU` | | `SIENGE_NF_DOCUMENTO` | — | documento da nota fiscal de compra. Vazio = `NF` | ### 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` | 8 | 🔨 solicitação, pedido e lançamento de nota; falta cotação | | `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 | | `compras_lancar_nota_fiscal` | confere DANFE × boleto × pedido (CNPJs, valores, itens, duplicidade), lança a nota com entrada no pedido, anexa DANFE e boleto ao título e acerta o vencimento ao do boleto, com prévia — **a nota não sai fechada**, ver abaixo | | `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 ✅ · conferir e lançar ✅ · fechar o título ❌ (só pela tela) · registrar pagamento ❌ | As escritas desta etapa são quatro: criar solicitação, decidir solicitação, decidir pedido de compra e lançar nota fiscal. ### 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. ## Como o lançamento de nota fiscal funciona > **Limitação: o lançamento não termina pela API.** Depois que > `compras_lancar_nota_fiscal` grava, alguém precisa **abrir a nota no Sienge e > salvar a aba Título**. Até lá, nota e título ficam inconsistentes e o título > vale R$ 0,00 — não entra em pagamento. Salvar a aba Totalização não resolve. > É limitação do Sienge, confirmada pelo Produto deles, sem prazo para mudar — > ver [Antes de apontar para produção](#antes-de-apontar-para-produção). 1. **Salve a DANFE e o boleto em `uploads/`**, dentro de `SIENGE_PASTA_ANEXOS`, com o número da nota no nome do arquivo — como o fornecedor já costuma mandar ("NOTA FISCAL N. 1234", "BOLETO REF. NF 1234"). 2. **Peça o lançamento ao assistente**, com a DANFE, o boleto e o número do pedido — ou o arquivo do pedido; o número costuma estar nas observações da DANFE. Ele lê os documentos e chama a tool sem confirmar. 3. **Confira a prévia.** Ela mostra o que foi conferido, em que empresa a nota entra, qual item e entrega do pedido recebe cada item da nota, quais arquivos foram achados em `uploads/` e os vencimentos que o título vai ter. Tudo o que não fecha volta de uma vez, com os candidatos do pedido. 4. **Confirme**, depois de a mercadoria ter sido conferida no recebimento. A tool grava o cabeçalho e os itens, anexa os arquivos ao título gerado e acerta o vencimento das parcelas ao do boleto. 5. **Feche na tela.** Abra a nota pelo número sequencial que a resposta traz, vá na aba **Título**, confira valor e vencimento e salve. **O que é conferido antes de gravar:** emitente da nota × fornecedor do pedido; destinatário × empresa de faturamento (em obra de SCP, o CNPJ da sócia ostensiva está certo); cedente do boleto × emitente — boleto de terceiro é sinal de cessão de crédito ou fraude; soma dos boletos × total da nota; nota já lançada; pedido autorizado, não cancelado e com saldo; cada item casado com o pedido pelo preço e pelo saldo, nunca pela descrição. **O que a tool não faz:** frete, IPI, ST ou desconto da nota (só os itens entram — a diferença vai na tela); nota que atende mais de um pedido; alterar o valor do título; registrar linha digitável ou chave PIX. E arquivo anexado ao chat do Claude Desktop não chega ao servidor: só vai ao título o que estiver em `uploads/` ou vier com caminho completo. ## 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. - **Nota fiscal lançada pela API não fecha sozinha — limitação do Sienge.** `compras_lancar_nota_fiscal` grava o cabeçalho e os itens com entrada no pedido, e o Sienge gera o título a pagar junto, mas com **valor total zero**: nota e título ficam inconsistentes ("Soma das parcelas está diferente da soma do título") e a parcela trava na tela. O lançamento só termina quando alguém abre a nota no Sienge e **salva a aba Título** — é ali que o ERP roda as validações finais e deixa acertar valor e vencimento. Salvar a aba Totalização não resolve. A API não tem endpoint equivalente, não aceita campo de valor no lançamento (campos não declarados são ignorados em silêncio) e não deixa alterar o valor de título de origem compras. O Produto do Sienge confirmou o comportamento e registrou a finalização via API como evolução futura, **sem prazo**. Na prática, lançar por aqui ainda exige um passo no ERP para cada nota, e uma nota esquecida fica com título zerado, sem pagamento. A tool faz o resto do que a API permite — anexa a DANFE e o boleto ao título e acerta o vencimento das parcelas ao do boleto (o Sienge calcula pela condição do pedido) —, então salvar a aba Título vira uma revisão. Ela avisa antes de confirmar e repete o passo pendente na resposta de todo lançamento. Os anexos são lidos do disco: arquivo anexado no chat do Claude Desktop não chega ao servidor. Salve a DANFE e o boleto em `uploads/`, dentro de `SIENGE_PASTA_ANEXOS`, e a tool os acha pelo número da nota — a prévia mostra quais achou, para conferir que são desta nota. - **Obra de SCP: a nota é lançada numa empresa e conferida contra outra.** O destinatário impresso na nota é o CNPJ da sócia ostensiva (a empresa de faturamento do pedido), mas o lançamento vai para a empresa da obra. É o esperado — a tool confere e lança assim. - **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 `downloads/`, dentro de `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 │ ├── purchase-invoices-v1.js │ ├── creditor-v1.js │ ├── company-v1.js │ ├── enterprise-v1.js │ └── cost-center-v1.js ├── client/ │ ├── siengeClient.js o único ponto que fala HTTP com o Sienge — │ │ makeRequest para JSON, baixarArquivo para bytes, │ │ enviarArquivo para upload multipart │ ├── purchaseClient.js compõe as funções de api/ no que uma pergunta de │ │ negócio precisa: resolve nomes, agrupa, projeta │ ├── purchaseInvoiceClient.js confere nota × boleto × pedido e lança │ └── 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.