lorenzopasquali.dev

Como construí uma integração de LLMs via MCP para um MES on-premise

· 6 min de leitura · também em English

Como construí um MCP para conectar um MES on-premise a GPT, Claude e Gemini

Quando começamos a levar IA para o nosso MES, a ideia parecia simples: permitir que alguém perguntasse em português “qual foi o OEE da linha 3 ontem?” e recebesse a resposta com os dados reais da fábrica.

O problema é que esse MES não roda em uma nuvem central. Ele roda dentro da infraestrutura de cada cliente, muitas vezes em um servidor que não pode ser atualizado a qualquer hora e cuja saída para a internet é bastante controlada.

Então a pergunta deixou de ser “qual modelo vamos usar?” e passou a ser outra:

Como fazer uma IA consultar dados industriais sem dar acesso direto ao banco, respeitando as permissões existentes e ainda permitindo que o cliente escolha o modelo?

A solução acabou envolvendo MCP, um serviço de IA separado e algumas decisões de arquitetura que só apareceram quando o projeto começou a funcionar de verdade.

Antes do MCP: um JSON gigante

Antes de existir o MCP, já tínhamos uma primeira versão da integração com IA.

A ideia era relativamente simples: a tela do MES sabia quais dados o usuário estava visualizando e enviava esse conteúdo para o serviço de IA em um JSON grande com os dados daquele contexto.

Para perguntas sobre aquela tela, funcionava.

Se o usuário estivesse olhando um relatório de produção e perguntasse algo sobre aqueles dados, o modelo conseguia responder.

Mas existia uma limitação importante: o modelo só sabia aquilo que a tela tinha enviado.

Se a pessoa perguntasse:

“E quais foram os principais motivos de parada dessa máquina?”

mas a tela atual não continha essas informações, a IA não tinha como descobrir.

Ela não tinha acesso ao banco.

Também não tinha uma ferramenta para pedir ao MES uma informação adicional.

Na prática, o fluxo era:

Usuário
   │
   ▼
Tela do MES
   │
   │ JSON com os dados visíveis
   ▼
Serviço de IA
   │
   ▼
Modelo

Era uma abordagem segura, porque o modelo recebia somente os dados que já estavam no contexto da tela.

Mas também era bastante limitada.

A IA conseguia interpretar os dados que recebia, mas não conseguia buscar novos dados.

Foi justamente essa limitação que levou ao MCP.

MCP como camada entre a IA e o MES

Em vez de continuar aumentando o tamanho do JSON enviado para cada tela, a ideia passou a ser permitir que o modelo pudesse pedir informações adicionais de maneira controlada.

O MES passou então a expor funcionalidades específicas como tools:

O modelo não precisa saber como o dado é armazenado.

Para responder sobre o OEE, ele pode pedir algo como:

get_daily_oee
line = 3
date = 2026-09-21

A execução acontece dentro do próprio MES.

Isso cria uma separação importante:

Modelo de IA
     │
     │ tool call
     ▼
    MCP
     │
     │ regras de negócio
     ▼
    MES
     │
     ▼
   Banco

O modelo não acessa o banco e não executa SQL arbitrário.

A regra que virou princípio

Existe uma regra que considero especialmente importante:

a IA não pode discordar da tela.

O MES já possui a lógica usada pelos relatórios. Se um relatório de paradas mostra 42 minutos, não faz sentido implementar outro cálculo para a IA e correr o risco de ela responder 39.

As tools reutilizam as regras existentes.

A IA deve ser uma nova interface para a fonte de verdade do sistema, não uma segunda implementação dela.

Onde colocar a IA?

O próximo problema era arquitetural.

O MES é uma aplicação Java madura. Colocar dentro dele os SDKs da Anthropic, OpenAI e Google funcionaria, mas criaria um acoplamento ruim.

Modelos e SDKs mudam rapidamente. Um sistema industrial precisa de outro ritmo.

Então a IA ganhou um serviço próprio, instalado na mesma infraestrutura do cliente.

A arquitetura ficou aproximadamente assim:

┌─────────────────────────────────────────────────────┐
│              infraestrutura do cliente              │
│                                                     │
│  ┌──────────────┐        ┌──────────────────────┐   │
│  │     MES      │        │     AI Service       │   │
│  │              │◀─MCP──│                      │───┼──▶ GPT
│  │   /mcp       │        │    orquestrador      │───┼──▶ Claude
│  │              │        │                      │───┼──▶ Gemini
│  └──────┬───────┘        └──────────────────────┘   │
│         │                                           │
│         ▼                                           │
│       Banco                                         │
└─────────────────────────────────────────────────────┘

O MES continua responsável pelos dados e pelas regras.

O serviço de IA conversa com os modelos e executa as tools através do MCP.

Assim, o banco continua isolado e apenas o componente que precisa falar com a internet faz chamadas externas.

Um serviço, três provedores

Outro requisito era não ficar preso a um único modelo.

O cliente pode já ter um contrato corporativo com um provedor, ou simplesmente preferir um modelo diferente.

Por isso, provedor, modelo e chave passaram a ser configurações do cliente.

Por baixo existem os SDKs oficiais dos três provedores, mas o serviço trabalha com uma abstração comum.

Isso trouxe um benefício importante: trocar de modelo não exige alterar ou reinstalar o MES.

O loop de tool calling

Uma pergunta também pode exigir mais de uma consulta.

Por exemplo:

“Quais foram as principais perdas da linha 3 ontem e qual foi o impacto delas no OEE?”

O modelo pode pedir os motivos de parada, receber os dados, perceber que precisa do OEE e fazer outra chamada.

O serviço então executa um loop:

pergunta
   │
   ▼
modelo
   │
   ├── precisa de dados? ── sim ──▶ tool
   │                                  │
   │                                  ▼
   │                              resultado
   │                                  │
   └──────────────────────────────────┘
                  │
                  ▼
            resposta final

Mas existe um limite de iterações.

Perguntas normais usam poucas chamadas. O limite existe para impedir que o modelo entre em um ciclo de chamadas que aumenta desnecessariamente latência e custo.

Também surgiu outro problema: não dá para simplesmente mandar o resultado bruto de qualquer consulta para o modelo.

Um relatório de um mês, com dezenas de máquinas, pode gerar milhares de linhas.

Então alguns resultados são compactados antes de voltar para o modelo. O objetivo é manter a informação necessária para o raciocínio sem transformar o contexto em uma cópia do banco.

Menos tools também pode ser melhor

Outra decisão foi fazer o catálogo de tools depender do contexto.

Se o usuário está dentro de um relatório de paradas, não existe motivo para apresentar ao modelo todas as ferramentas disponíveis no MES.

Ele recebe apenas as relacionadas àquele contexto.

Isso reduz a quantidade de decisões que o modelo precisa tomar.

A mesma lógica vale para gráficos: a tool que permite pedir uma visualização só é disponibilizada quando a interface atual consegue renderizá-la.

Em vez de dizer ao modelo “você pode fazer tudo”, a arquitetura tenta dizer:

neste contexto, estas são as coisas que você pode fazer.

Segurança: principalmente o que a IA não pode fazer

Em um sistema industrial, dar liberdade demais para um modelo é uma decisão difícil de justificar.

Por isso, várias restrições foram colocadas diretamente na arquitetura.

O modelo:

O endpoint MCP possui autenticação própria. Token ausente não significa acesso aberto: significa acesso negado.

E existe uma separação importante entre a sessão do serviço de IA e a autorização do usuário.

A conexão entre os serviços pode ser compartilhada, mas cada chamada de tool leva a identificação de quem fez a pergunta. O MES então aplica as regras de acesso existentes.

Assim, a IA não vira um atalho para acessar dados que o usuário normalmente não poderia visualizar.

E os dados que saem da fábrica?

Como o serviço de IA pode conversar com provedores externos, surgiu outra questão: e se o cliente não quiser enviar determinados nomes para o modelo?

Foi implementada uma opção de anonimização.

Antes de a conversa sair, nomes de máquinas, itens e motivos podem ser substituídos por identificadores.

CNC-04
  ↓
MACHINE_001

Produto ABC-123
  ↓
ITEM_014

Na volta, esses identificadores podem ser reconstruídos.

O MCP acabou sendo maior que o chatbot

Talvez a consequência mais interessante do projeto tenha aparecido depois.

Quando o MES passou a expor suas capacidades através de MCP, ele deixou de depender da nossa própria interface para ser consumido por uma IA.

O mesmo endpoint pode ser utilizado por outros clientes compatíveis com o protocolo.

A arquitetura deixou de ser:

MES → nosso chatbot

e passou a ser:

                 ┌── nosso chat
                 │
MES ── MCP ──────┼── cliente MCP externo
                 │
                 └── futuras IAs

Foi aí que o MCP deixou de parecer apenas uma tecnologia para conectar um chatbot ao sistema.

Ele passou a ser uma interface padronizada para as capacidades do MES.

O que ficou de aprendizado

No começo, o problema parecia ser integrar um LLM com um sistema industrial.

Não era.

O problema era criar uma fronteira segura entre um modelo probabilístico e um sistema que precisa continuar sendo previsível.

O modelo pode mudar.

O provedor pode mudar.

O SDK pode mudar.

Mas a regra de negócio do MES precisa continuar sendo a mesma.

Por isso, talvez a decisão mais importante do projeto não tenha sido escolher entre GPT, Claude ou Gemini.

Foi decidir o que nenhum deles poderia fazer.

A IA não precisa conhecer a fábrica inteira.

Ela precisa conhecer exatamente aquilo que o sistema decidiu mostrar.

E, em software industrial, talvez seja essa a diferença entre simplesmente colocar IA em um produto e realmente construir uma arquitetura de IA.


Stack: Java/Spring · MCP sobre HTTP e stdio · SDKs oficiais da Anthropic, OpenAI e Google · Angular · Docker Compose · PostgreSQL · MES on-premise

← Todos os posts