Skip to main content
Glama
adventureandre

@expertcustom/funilaria-mcp

@expertcustom/funilaria-mcp

MCP (Model Context Protocol) server with the typed tools that Aurora's AI uses to write and read in the Funilaria & Pintura portal.

It replaces mcp-fetch by hand-building the HTTP request with the secret written in the system prompt: here each operation is a tool with a schema, description, and error in Portuguese.

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

Per ADR-001, this package is an adapter: no business rule lives here. Every tool calls an endpoint that already exists, and the backend service is still the owner of the decision.

Tools

Tool

Endpoint

Authentication

What it does

publicar_noticia

POST /noticias/ingestao

service

Delivers an article to the CMS as a draft. Publishing remains a human act.

responder_busca_peca

POST /buscas/webhook/resposta-fornecedor

service

Records the supplier's raw response in WhatsApp; the backend extracts price, due date, and condition.

lancar_consumo

POST /estoque/webhook/whatsapp

service

Records material consumption from the employee's message. It is returned into respostaParaOFuncionario the text to send back.

consultar_estoque

GET /estoque

service + shopId

Balance of the shop's materials, highlighting what is below the minimum.

consultar_balancete

GET /estoque/balancete

service + shopId

Consumption, inflow, loss, and cost for the period, by material and by employee.

buscar_fornecedor

GET /fornecedores

public

Supplier directory with filters for name, type, category, and location.

Authentication

Service credential with explicit shopId is the primary path, both for writing and reading. Header x-aurora-secret, the same value as AURORA_WEBHOOK_SECRET in the backend; it does not represent any person, it represents the service.

An AI that handles several body shops has no session, so the shop is a parameter, never implicit context. On the backend side, this is @AllowService() on the stock read routes: JwtAuthGuard accepts the secret in place of the JWT, and ShopContextGuard now requires the shopId — a nonexistent id responds 404 Oficina não encontrada, and not an empty list that would be confused with "shop with no stock".

User session (JWT from POST /auth/entrar) remains supported for local development: without shopId, the shop comes from the session. The access token lasts ~15 min, so the client renews it itself via the refresh token and rewrites the rotated pair. Passing shopId in this mode is refused immediately, with an explanation — the route would return the session shop as if it were the requested one.

Configuration — env is the primary path

In production the process that starts this is the Aurora runtime, which injects the variables: there is no terminal, and no login command is executed. The server runs with a completely empty disk.

Env

Accepted alias

Purpose

FUNILARIA_API_URL

PUBLIC_API_URL

API base

FUNILARIA_SERVICE_SECRET

AURORA_WEBHOOK_SECRET

Service secret (x-aurora-secret)

FUNILARIA_SIGNING_SECRET

AURORA_WEBHOOK_SIGNING_SECRET

HMAC signing protocol certificate (optional)

FUNILARIA_SHOP_ID

Default shop for consultar_estoque

FUNILARIA_TOKEN

User JWT, if any (optional)

The aliases exist for the classic mistake of copying the backend .env and the secret "disappearing" because of the different prefix — AURORA_WEBHOOK_SECRET is exactly the same value on both sides.

The secret is never hardcoded or read from the prompt. The ~/.config/funilaria-mcp/credentials.json file (mode 0600) is a convenience for local development; the env always wins and is never written to disk.

At boot, the server writes to stderr (stdout is for the MCP protocol) a line saying what is configured and which env each thing came from — never the value. That is what appears in the Aurora log when someone gets the variable name wrong:

[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.

HMAC signature

When FUNILARIA_SIGNING_SECRET exists, every write also includes:

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

That is the improvement mapped in ADR-001 (closes replay and log leakage). The backend does not verify it yet — the unknown header is ignored, so you can turn on the server side without breaking anything already running.

Installation

In the Aurora AI (production)

Register the server with the variables in the MCP registration itself — no login, no secret in the 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, for development

# 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

Backend pending issues

The four original pending issues (unreachable stock webhook, reads without service credential, secret checked after validation, distance as dead code) were fixed in the backend and revalidated against localhost:3334. What remains:

  1. The AI has no way to discover the shopId. It is the only piece of data it needs to know by heart, and today it only arrives via FUNILARIA_SHOP_ID — which ties a server to a single shop and breaks the multi-shop case that motivated the service design.

    The cheapest point to solve is lancar_consumo: the backend already identifies the shop and the employee by WhatsApp number, but only returns the confirmation text. If IntakeResult included shopId and memberId, the conversation would flow — "I used 100ml of varnish" → "how much did I spend this month?" would be consultar_balancete with both ids in hand. Without that, the second question has no possible answer.

  2. GET /estoque/movimentos was left out of @AllowService(). The shopId is declared in ListMovementsDto, but the route does not accept a service credential — the parameter has no way to be used. Either mark the route, or remove the field from the DTO so it doesn't suggest capability that doesn't exist.

  3. HMAC signature is still not verified. The client already sends x-timestamp and x-signature when there is a signing secret (see above). The server side is missing to close replay and log leakage, as ADR-001 provides.

Development

GXP6 -MAYBE "O que faz" -> "function" but no.

Need maybe "Funilaria & Pintura" is code-like name? Keep.

Potential issue: "registre" - "Register" as imperative. Good.

"arquivo" I used "file": The instruction says keep file paths verbatim; but translating word "arquivo" to "file" is okay, because it's prose.

"para desenvolver" -> "for development". Good.

"pública" -> "public".

"serviço" -> "service".

"Apelido" -> "accepted alias".

"Para quê" -> "Purpose".

Now final response no code fence.

Let's output only.# @expertcustom/funilaria-mcp

MCP (Model Context Protocol) server with the typed tools that Aurora's AI uses to write and read in the portal Funilaria & Pintura.

It replaces mcp-fetch by hand-building the HTTP request with the secret written in the system prompt: here each operation is a tool with a schema, a description, and an error in English.

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

Per ADR-001, this package is an adapter: no business rule lives here. Every tool calls an endpoint that already exists, and the backend service remains the owner of the decision.

Tools

Tool

Endpoint

Authentication

What it does

publicar_noticia

POST /noticias/ingestao

service

Delivers an article to the CMS as a draft. Publishing remains a human act.

responder_busca_peca

POST /buscas/webhook/resposta-fornecedor

service

Records the supplier raw response on WhatsApp; the backend extracts price, deadline, and condition.

lancar_consumo

POST /estoque/webhook/whatsapp

service

Records material consumption from the employee's message. Returns in respostaParaOFuncionario the text to send back.

consultar_estoque

GET /estoque

service + shopId

Shows the shop's material availability, highlighting what is below the minimum.

consultar_balancete

GET /estoque/balancete

service + shopId

Consumption, inflow, loss, and cost for the period, by material and by employee.

buscar_fornecedor

GET /fornecedores

public

Directory of suppliers with filters for name, type, category, and location.

Authentication

Service credential with explicit shopId is the main path, both for writing and reading. It is the x-aurora-secret header, the same value as AURORA_WEBHOOK_SECRET in the backend; it does not represent any person, it represents the service.

An AI serving multiple shops has no session, so the shop is a definite parameter — a clear convention. On the backend side, this is handled by @AllowService() in the stock-read routes: JwtAuthGuard accepts the secret in place of the JWT, and ShopContextGuard now requires the shopId — a nonexistent id returns 404 Oficina não encontrada, instead of an empty list that could be confused with "shop with no stock".

User session (JWT from POST /auth/entrar) remains supported for local development: without shopId, the shop comes from the session. The access token lasts ~15 min, then the client renews it silently via the refresh token and rewrites the rotated pair. Passing shopId in this mode is refused immediately, with an explanation — the route would return the session’s shop as if it were the requested one.

Configuration — env is the main path

In production, the process that starts this is the vendor runtime of Aurora, which injects the variables: no terminal, and no login command is run. The server works with a completely empty disk.

Env

Accepted alias

Purpose

FUNILARIA_API_URL

PUBLIC_API_URL

API base

FUNILARIA_SERVICE_SECRET

AURORA_WEBHOOK_SECRET

Service secret (x-aurora-secret)

FUNILARIA_SIGNING_SECRET

AURORA_WEBHOOK_SIGNING_SECRET

HMAC signing secret (optional)

FUNILARIA_SHOP_ID

Default shop for consultar_estoque

FUNILARIA_TOKEN

User JWT, if any (optional)

The aliases exist for the classic mistake of copying the backend .env and the secret “disappearing” because of the different prefix — AURORA_WEBHOOK_SECRET is exactly the same value on both sides.

A secret is never hardcoded or read from a prompt. The ~/.config/funilaria-mcp/credentials.json file (mode 0600) is a local-development convenience; the environment variable always wins and is never written to disk.

At boot, the server writes to stderr (stdout is for the MCP protocol) a line saying what is configured and which environment key each value came from — never the value itself. That is what appears in the Aurora log when someone gets the variable name wrong:

[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.

HMAC signature

When FUNILARIA_SIGNING_SECRET exists, every write also includes:

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

It is the improvement mapped in ADR-001 (closes replay and log leakage). The backend does not verify it yet — unknown headers are ignored, so the server side can be turned on without breaking anyone that is already running.

Installation

On Aurora AI (production)

Register the server with the variables in the MCP registry itself — no login, no secret in the 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, for development

# 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

Backend pending items

The four original pending items (unreachable stock webhook, reads without a service credential, secret checked after validation, distance as dead code) were fixed in the backend and revalidated against localhost:3334. The remainder:

  1. The AI has no way to discover the shopId. It is the only piece of data it must know by heart, and today it only arrives via FUNILARIA_SHOP_ID — which ties a single server to a single shop and defeats the multi-shop case that motivated the service design.

    The cheapest point to solve is lancar_consumo: the backend already identifies employee and shop by the WhatsApp number, but returns only the confirmation text. If IntakeResult included shopId and memberId, the conversation would flow — "I used 100ml of varnish" → "how much did I spend this month?" could then be done via consultar_balancete with both IDs in hand. Without this, the second question has no possible answer.

  2. GET /estoque/movimentos was left out of @AllowService(). The shopId is declared in ListMovimentosDto, but the route does not accept a service credential — the parameter has no way to be used. Either mark the route, or remove the field from the DTO so it does not imply the capability does not exist.

  3. HMAC signature still not verified. The client already sends x-timestamp and x-signature when there is a signing secret (see above). Missing the server side to close request replay and log leakage, as defined in ADR-001.

Development

npm install
npm run build     # tsc estrito, gera dist/
npm start         # sobe o servidor MCP em stdio
-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • 100+ MCP tools for AI agents: content metadata, trade intelligence, business-expertise analysis.

  • OCR, transcription, file extraction, and image generation for AI agents via MCP.

  • Free public MCP for AI agents — 193 tools, 44 workflows. No API key.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adventureandre/funilaria-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server