Skip to main content
Glama
adventureandre

@expertcustom/funilaria-mcp

@expertcustom/funilaria-mcp

Servidor MCP (Model Context Protocol) com as tools tipadas que a IA do Aurora usa para escrever e ler no portal Funilaria & Pintura.

Ele substitui o mcp-fetch montando requisição HTTP à mão com o segredo escrito no system prompt: aqui cada operação é uma tool com schema, descrição e erro em português.

IA do Aurora ──stdio──> npx @expertcustom/funilaria-mcp ──HTTPS──> backend NestJS

Pela ADR-001, este pacote é adaptador: nenhuma regra de negócio mora aqui. Toda tool chama um endpoint que já existe, e o serviço do backend continua sendo o dono da decisão.

Tools — quem define é o backend

Este pacote não sabe quais tools existem. Na subida ele busca GET /mcp/catalogo e publica o que vier; a execução vai por POST /mcp/executar, e o backend resolve o nome para o serviço.

subida  ──> GET  /mcp/catalogo   → lista publicada em tools/list
chamada ──> POST /mcp/executar   → { name, args }

O motivo é operacional: antes, tool nova custava editar este pacote, buildar, commitar, publicar e reinstalar o MCP no Aurora — cinco passos para expor um endpoint que já existia no backend. Agora é commit de um módulo só, em backend/src/mcp.

A lista corrente sai de GET /mcp/catalogo. Não há cópia dela aqui de propósito: cópia envelhece e passa a mentir.

Quando o catálogo não responde, o servidor publica uma única tool, funilaria_catalogo_indisponivel, cuja descrição explica o que verificar. Sem ela o sintoma seria "sumiram as tools" e o diagnóstico começaria pelo lugar errado.

O catálogo é buscado na subida e relido a cada 5 minutos (FUNILARIA_CATALOGO_INTERVALO, em segundos; 0 desliga). Quando a lista muda — nome, descrição, schema ou annotation —, o servidor manda notifications/tools/list_changed e o Aurora troca as tools da IA sem reinstalar. Releitura que falha não mexe em nada: a última lista boa continua valendo. Se a subida caiu na funilaria_catalogo_indisponivel, a primeira releitura boa a substitui pela lista real.

Leitura ou escrita (annotations)

Cada tool sai com readOnlyHint, e as de escrita com destructiveHint: true — com a política "pedir confirmação" do Aurora, é isso que faz a escrita esperar o usuário aprovar. O critério, em ordem (src/server/catalog/anotacoes.ts):

  1. annotations que o backend mandar na tool — hoje ele não manda; quando mandar, vence;

  2. a descrição: "Somente leitura" ou a rota citada ((GET /...) é leitura, (POST /...) é escrita);

  3. o nome: consultar_, buscar_, listar_, identificar_, ver_ são leitura;

  4. sem sinal nenhum, vale como escrita destrutiva.

O jeito certo é o backend mandar annotations: { readOnlyHint, destructiveHint } em cada item de GET /mcp/catalogo — ele é quem sabe se responder_busca_peca, por exemplo, só acrescenta (destructiveHint: false).

Instruções

No initialize o servidor manda instructions curtas — consultar antes de gravar, não inventar id nem valor, confirmar com a pessoa quando faltar dado — que o Aurora anexa ao prompt da IA. O detalhe de cada tool continua na descrição dela.

Related MCP server: Telegram Bot MCP Server

Autenticação

Credencial de serviço com shopId explícito é o caminho principal, tanto para escrever quanto para ler. Header x-aurora-secret, o mesmo valor de AURORA_WEBHOOK_SECRET no backend; ele não representa pessoa nenhuma, representa o serviço.

Uma IA que atende várias oficinas não tem sessão, então a oficina é parâmetro, nunca contexto implícito. Do lado do backend isso é o @AllowService() nas rotas de leitura de estoque: o JwtAuthGuard aceita o segredo no lugar do JWT e o ShopContextGuard passa a exigir o shopId — id inexistente responde 404 Oficina não encontrada, e não uma lista vazia que se confundiria com "oficina sem estoque".

Sessão de usuário (JWT de POST /auth/entrar) continua existindo para os comandos de CLI, mas não vale mais para as tools: POST /mcp/executar é sempre chamada de serviço, e a oficina é sempre parâmetro explícito. Uma IA que atende várias oficinas nunca teve sessão; manter os dois modos só criava um caminho em que shopId omitido significava coisas diferentes.

Configuração — env é o caminho principal

Em produção quem sobe este processo é o runtime do Aurora, que injeta as variáveis: não há terminal, e nenhum comando de login é executado. O servidor funciona com o disco totalmente vazio.

Env

Apelido aceito

Para quê

FUNILARIA_API_URL

PUBLIC_API_URL

Base da API

FUNILARIA_SERVICE_SECRET

AURORA_WEBHOOK_SECRET

Segredo de serviço (x-aurora-secret)

FUNILARIA_SIGNING_SECRET

AURORA_WEBHOOK_SIGNING_SECRET

Segredo da assinatura HMAC (opcional)

FUNILARIA_SHOP_ID

—

Oficina padrão de consultar_estoque

FUNILARIA_PERFIL

—

Qual IA do conjunto este servidor atende: pecas, estoque. Sem definir, o catálogo vem inteiro

FUNILARIA_TOKEN

—

JWT de usuário, se houver (opcional)

FUNILARIA_CATALOGO_INTERVALO

—

Segundos entre releituras do catálogo (padrão 300; 0 desliga)

O FUNILARIA_PERFIL existe porque o Aurora liga servidor inteiro a uma IA — não há filtro de tool por IA no painel dele. Então o escopo sai do catálogo: o backend devolve só as tools daquele perfil, do mesmo jeito que o FUNILARIA_SHOP_ID já limita a oficina. Assim a IA de peças não recebe lancar_consumo, e a de estoque não recebe responder_busca_peca.

Perfil desconhecido vale como ausente e devolve tudo: atender demais é recuperável, atender de menos deixa a IA sem ferramenta e o sintoma aparece longe da causa. Por isso o status imprime o perfil que ele leu.

Os apelidos existem para o erro clássico de copiar o .env do backend e o segredo "sumir" por causa do prefixo diferente — AURORA_WEBHOOK_SECRET é exatamente o mesmo valor dos dois lados.

Segredo nunca é hardcoded nem lido de prompt. O arquivo ~/.config/funilaria-mcp/credentials.json (modo 0600) é conveniência de desenvolvimento local; a env sempre vence e nunca é gravada em disco.

No boot, o servidor escreve em stderr (stdout é do protocolo MCP) uma linha dizendo o que está configurado e de qual env veio cada coisa — nunca o valor. É o que aparece no log do Aurora quando alguém erra o nome da variável:

[funilaria-mcp] API: https://api.exemplo.com (FUNILARIA_API_URL) · Credencial de serviço: configurada via AURORA_WEBHOOK_SECRET · ...
[funilaria-mcp] Sem credencial de serviço: as tools de escrita vão recusar toda chamada. Defina FUNILARIA_SERVICE_SECRET no ambiente deste processo.

Assinatura HMAC

Quando FUNILARIA_SIGNING_SECRET existe, toda escrita leva também:

x-timestamp: <epoch em segundos>
x-signature: sha256=<HMAC-SHA256(`${timestamp}.${corpo}`)>

É a melhoria mapeada na ADR-001 (fecha replay e vazamento por log). O backend ainda não verifica — header desconhecido é ignorado, então dá para ligar o lado do servidor sem quebrar quem já está rodando.

Instalação

Na IA do Aurora (produção)

Registre o servidor com as variáveis no próprio cadastro do MCP — nada de login, nada de segredo no system prompt:

{
  "command": "npx",
  "args": ["-y", "@expertcustom/funilaria-mcp"],
  "env": {
    "FUNILARIA_API_URL": "https://<api-do-portal>",
    "FUNILARIA_SERVICE_SECRET": "<mesmo valor de AURORA_WEBHOOK_SECRET>"
  }
}

Local, para desenvolver

# opção A — env no shell (igual à produção)
FUNILARIA_API_URL=http://localhost:3334 FUNILARIA_SERVICE_SECRET=... npx @expertcustom/funilaria-mcp

# opção B — guardar em ~/.config para não exportar em todo shell
npx @expertcustom/funilaria-mcp login-servico

# sessão de usuário: só é necessária para consultar_estoque sem shopId
npx @expertcustom/funilaria-mcp login

# conferir o que está valendo e de onde veio (nunca imprime segredo)
npx @expertcustom/funilaria-mcp status

# registrar no Claude Code
claude mcp add funilaria --env FUNILARIA_API_URL=http://localhost:3334 -- npx -y @expertcustom/funilaria-mcp

Pendências no backend

As quatro pendências originais (webhook de estoque inalcançável, leitura sem credencial de serviço, segredo checado depois da validação, distância como código morto) foram corrigidas no backend e revalidadas contra localhost:3334. O que sobrou:

  1. A IA não tem como descobrir o shopId. É o único dado que ela precisa saber de cor, e hoje só chega por FUNILARIA_SHOP_ID — o que amarra um servidor a uma oficina e derruba o caso multi-oficina que motivou o desenho de serviço.

    O ponto mais barato de resolver é o lancar_consumo: o backend já identificou funcionário e oficina pelo número do WhatsApp, mas devolve só o texto de confirmação. Se IntakeResult incluísse shopId e memberId, a conversa fluiria — "usei 100ml de verniz" → "quanto gastei esse mês?" seria consultar_balancete com os dois ids em mãos. Sem isso, a segunda pergunta não tem resposta possível.

  2. GET /estoque/movimentos ficou fora do @AllowService(). O shopId está declarado no ListMovementsDto, mas a rota não aceita credencial de serviço — o parâmetro não tem como ser usado. Ou marca a rota, ou tira o campo do DTO para não sugerir capacidade que não existe.

  3. Assinatura HMAC ainda não é verificada. O cliente já envia x-timestamp e x-signature quando há segredo de assinatura (ver acima). Falta o lado do servidor para fechar replay e vazamento por log, como prevê a ADR-001.

Desenvolvimento

npm install
npm run build     # tsc estrito, gera dist/
npm start         # sobe o servidor MCP em stdio

Available Tools

1 tool
funilaria_catalogo_indisponivelCatálogo do Funilaria indisponívelA

O backend do Funilaria não respondeu GET /mcp/catalogo, então NENHUMA tool do portal está disponível nesta sessão — não é que elas não existam. Verifique se FUNILARIA_API_URL aponta para o backend certo e se FUNILARIA_SERVICE_SECRET (ou AURORA_WEBHOOK_SECRET) está definido no processo deste MCP; o motivo exato saiu no stderr do servidor. Não tente cumprir a tarefa por outro caminho: não monte requisição HTTP à mão, não invente URL nem segredo. Avise quem pediu que a integração com o portal está fora do ar.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It explains that the tool is not a functional operation but a signal that the catalog is down, that no portal tools are available, and that the exact cause is in the server's stderr. It also discloses the expected fallback behavior (informing the requester) and explicitly forbids bypass attempts. This fully discloses the tool's non-functional nature and the reasoning behind it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, dense paragraph that front-loads the core message (backend down, no tools available) and then provides corrective actions and prohibitions. Every sentence contributes essential information: the root cause, the implication, troubleshooting steps, and explicit instructions. There is no fluff or redundancy, making it both concise and structurally sound.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there is no output schema and the tool is essentially a status indicator, the description is complete. It specifies what happened, why, what to check, what not to do, and what to tell the requester. All necessary operational context is provided, and because the tool has zero parameters and no output expectations, the description fully covers what an agent needs to handle this situation correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema is empty with 0 parameters, so the baseline is 4. The description does not need to add parameter details, but it explains why the tool takes no arguments (it is a placeholder for an unavailable catalog). It also provides environment variable checks (FUNILARIA_API_URL, FUNILARIA_SERVICE_SECRET) as context, which indirectly explains the setup. This exceeds the baseline by giving operational context beyond the mere absence of parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool indicates the backend did not respond to GET /mcp/catalogo, making the entire portal's tools unavailable in this session. It does not use a classic verb+resource phrasing, but its purpose as an unavailability signal is unambiguous and distinct from any normal tool. Since there are no siblings, the distinguishing aspect is not relevant, but the description still clearly explains what it represents.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly instructs when to use this tool (when it appears, treat it as a session-wide unavailability) and what NOT to do: do not attempt to fulfill the task by other means, do not craft HTTP requests manually, do not invent URLs or secrets. It also tells the agent to inform the requester. This is direct, actionable context with explicit exclusions, covering both usage and alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedfunilaria_catalogo_indisponivel

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of tool confusion. The tool's purpose—to report that the catalog is unavailable—is uniquely clear.

Naming Consistency5/5

The single tool name is descriptive and domain-specific, following a clear pattern. With no other tools, there is no inconsistency to penalize.

Tool Count1/5

A single tool that serves only as an error placeholder is far below the expected scope for a catalog server. This is an extreme mismatch with the apparent purpose.

Completeness1/5

The server provides no actual catalog operations; the only tool explicitly states that all functional tools are unavailable. The surface is severely incomplete and unusable.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers