@expertcustom/core-mcp
v0.3.0
Published
MCP server to connect Claude Code (and other MCP clients) to ExpertCustom Core
Downloads
452
Readme
@expertcustom/core-mcp
Servidor MCP do ExpertCustom Core. Dá a uma IA as ferramentas para operar o Core de ponta a ponta: explorar o banco do cliente, expor tabelas como resources, montar telas e ler ou gravar dados.
O consumidor previsto é a IA CORE_BUILDER do Aurora, não uma pessoa. O Aurora executa este
pacote como processo filho em sandbox e injeta a credencial por variável de ambiente — não há
login interativo, arquivo de configuração nem comando de CLI.
A IA enxerga exatamente o que a chave de API permite: mesmo RBAC, mesmo filtro de tenant do painel. Não existe caminho privilegiado por aqui.
Registro no Aurora
No cadastro de MCP server do Aurora, preencha:
command: npx
args: ["-y", "@expertcustom/[email protected]"]
env: { CORE_API_URL: "https://core-api.expertcustom.com.br", CORE_API_KEY: "..." }
timeout: 60000Fixe a versão. Sem ela, uma publicação nova alcança todas as IAs ligadas de uma vez.
npx -y sem versão busca sempre a última publicada. Isso significa que publicar algo quebrado
derruba, no mesmo instante, todas as IAs vinculadas a este MCP — sem deploy, sem aviso e sem
caminho de volta que não seja publicar um patch. Com a versão fixa, a atualização vira uma decisão:
altere o @0.2.0 quando quiser, uma IA de cada vez.
timeout é o do Aurora, não o do MCP: dê a ele folga sobre o CORE_TIMEOUT_MS, senão o Aurora
desiste antes de o Core responder e o erro que a IA lê não diz nada útil.
Variáveis de ambiente
| Variável | Obrigatória | Padrão | O que é |
| ----------------- | ----------- | -------------------------------------- | ----------------------------------------------------------------------- |
| CORE_API_KEY | sim | — | Chave de API do Core. Define qual tenant a IA enxerga e o que pode fazer. |
| CORE_API_URL | não | https://core-api.expertcustom.com.br | URL do Core. Só mude para apontar a um Core local em desenvolvimento. |
| CORE_TIMEOUT_MS | não | 30000 | Tempo limite de cada chamada, em milissegundos. |
As três são validadas no boot: faltando a chave, ou com URL/timeout inválidos, o processo escreve a mensagem no stderr e sai com código 1. É de propósito — um MCP que sobe sem credencial só descobriria o problema no meio de uma conversa, e quem estivesse do outro lado veria "erro ao consultar" sem causa nenhuma.
A chave é emitida no painel do Core (Configurações → Chaves de API). Ela é opaca para este
pacote: vai inteira no header X-API-Key, sem interpretação. Guarde-a apenas no env do MCP
server — ela é uma credencial de vida longa com as permissões do usuário que a emitiu.
CORE_TIMEOUT_MS cobre com folga listagem, gravação e geração de página. Quem estoura 30s é
introspect_provider num banco grande — um dos bancos reais tem 4.179 tabelas. Suba o valor
(120000, por exemplo) se for operar um banco desse porte.
Instalação
Nada a instalar: o npx -y acima baixa e executa. Para desenvolver contra um Core local:
pnpm install
pnpm build
CORE_API_URL=http://localhost:5080 \
CORE_API_KEY=<chave> \
node dist/index.jsPara ligar no Claude Code, em .mcp.json:
{
"mcpServers": {
"core": {
"command": "npx",
"args": ["-y", "@expertcustom/[email protected]"],
"env": { "CORE_API_URL": "http://localhost:5080", "CORE_API_KEY": "..." }
}
}
}As tools
Providers — a conexão com o banco do cliente.
list_providers, get_provider, test_provider, introspect_provider, list_tables,
describe_table.
Resources — o allowlist de tabelas e colunas. Nada no banco do cliente é consultável sem um.
list_resources, get_resource, create_resource_from_table, update_resource,
delete_resource, update_resource_fields, resync_resource, list_relations,
create_relation, delete_relation.
Apps — o sistema gerado.
list_apps, get_app, create_app, update_app, delete_app, get_app_auth_config,
save_app_auth_config.
Pages — as telas.
list_pages, get_page, create_page_from_resource, create_page, update_page, delete_page,
list_page_revisions, revert_page.
Dados — leitura e escrita no banco do cliente, sempre através do allowlist.
get_data_schema, list_records, get_record, create_record, update_record, delete_record,
lookup_records.
E get_status, que responde se o Core está no ar — útil como primeiro diagnóstico quando outra
tool falha.
O caminho típico de construção
list_providers → list_tables (com search!) → describe_table
→ create_resource_from_table
→ update_resource_fields (aplica as suggestions: máscaras, autoFill, rótulos)
→ update_resource (displayColumn, e as flags app* para liberar ao usuário final)
→ create_app
→ create_page_from_resource
→ update_app (homePageId, isPublished)Regras que valem em todas elas
- O Core não executa DDL. Não há tool que crie tabela ou coluna. Se a estrutura não existe no
banco do cliente, o caminho é pedir ao responsável pelo banco e depois
introspect_provider. - Tabela sem chave primária vira resource somente leitura, permanentemente: sem PK não existe
WHEREcapaz de identificar uma única linha, e a API recusa habilitar update ou delete. - Coluna binária não entra em listagem (20 linhas seriam 20 arquivos baixados) nem aceita
filtro ou ordenação. Ela continua legível registro a registro, em
get_record. - O documento de página é uma árvore de blocos validada no servidor. Documento inválido é recusado apontando qual bloco e qual campo — as tools repassam o texto inteiro.
- Campo com
autoFillnão vai no formulário e tem o valor do request descartado: quem grava é o servidor. - Publicar não abre dado.
app.isPublishedtira o sistema do ar,page.isPublishedsome com a tela, e quem corta acesso a DADO são as flagsapp*do resource. São três chaves diferentes.
Sobre as mensagens de erro
As tools traduzem a resposta do Core para algo acionável em vez de repassar o status. Um campo inexistente num filtro, por exemplo, volta assim:
O campo 'nome_cliente' não existe neste resource.
(correlationId 3d35ddbf… — cite-o ao pedir ajuda; é por ele que se acha o erro no log do servidor)
Os campos de 'clientes' que existem e são legíveis: id, nome, email, cpf_cnpj, telefone, …A lista dos campos válidos não vem do Core — ele omite o schema nas mensagens de erro de propósito, porque a resposta é vista por quem só tem permissão de tentar. Aqui a situação é outra: quem chamou a tool já podia ler o schema inteiro. O que faltava não era permissão, era a informação no lugar certo.
Teste de fumaça
Chama cada tool contra um Core real e diz quais responderam. Não valida o conteúdo da resposta: pega o que nenhum type-check pega — rota trocada, nome de parâmetro divergente, envelope inesperado. Exercita os handlers de verdade, não HTTP cru.
pnpm build
# somente leitura
CORE_API_URL=http://localhost:5080 CORE_API_KEY=<chave> pnpm smoke
# também as tools que gravam: criam objetos próprios (prefixo `smoke-`) e os apagam no fim
CORE_API_URL=http://localhost:5080 CORE_API_KEY=<chave> pnpm smoke -- --writeContra um Core local, dá para deixar o script pegar o token sozinho:
CORE_API_URL=http://localhost:5080 \
[email protected] \
CORE_LOGIN_PASSWORD=... \
pnpm smoke -- --writeO /api/auth/login é limitado por taxa: rodando o script várias vezes seguidas ele responde 429.
Nesse caso, guarde o token da primeira resposta e passe-o como CORE_API_KEY — o Core aceita as
duas credenciais, e o cliente escolhe o header pelo formato (JWT vai em Authorization: Bearer,
chave de API em X-API-Key).
O relatório sai como uma linha por tool (OK / FAIL / SKIP, com tempo e motivo) e o processo
sai com código 1 se qualquer tool falhar ou se alguma tool registrada não tiver sido
exercitada — uma tool nova sem roteiro é uma tool que ninguém testou.
Desenvolvimento
src/
index.ts entrada; imprime erro de configuração sem stack trace
config.ts constantes (URL de produção, timeout, versão)
auth/
credentials.ts loadConfig(): valida o ambiente no boot e falha alto
client.ts coreRequest(), apiPath e a tradução de ProblemDetails para texto útil
server/
server.ts ALL_TOOLS e HANDLERS — é só aqui que se registra uma tool
tools/
shared.ts leitura de argumentos; cada função existe pela mensagem que produz
providers.ts o modelo de estilo dos demais módulos
resources.ts apps.ts pages.ts data.ts status.ts
scripts/
smoke.mjs o teste de fumaçaUma tool nova é um XXX_TOOL (JSON Schema de entrada, additionalProperties: false) mais um
runXxx(creds, args) no módulo do assunto, registrados nas duas listas de server.ts — e uma
etapa no roteiro de scripts/smoke.mjs, senão o próprio teste acusa a falta.
A descrição da tool e a mensagem de erro são a interface de uso. É por elas que a IA decide o que chamar e como se corrigir; trate-as como produto, não como comentário. A descrição diz quando usar e quando não usar; o erro diz o que fazer a seguir.
