Skip to main content
Glama

Painel web + relay para dar a clientes de IA (Claude, ChatGPT, Gemini, Cursor, VS Code…) acesso controlado e revogável às tuas máquinas, via MCP. Open source, deploy em Vercel + Neon.

Repositório: https://github.com/lhabacuc/mortality

Princípio: o painel web é o único plano de controlo. Nenhum cliente acede a nada por ter conta ou token: precisa de um grant explícito (cliente × dispositivo) dado no painel. Por defeito, acesso negado. A CLI mortality não executa tools nem fala MCP: só instala e gere o relay.

 Cliente MCP ──(OAuth/PAT)──▶ /api/mcp (Vercel) ──grant?──▶ job em Postgres (Neon)
                                                                 ▲ claim (SKIP LOCKED)
 Relay (systemd/launchd/Task Scheduler) ──poll outbound──▶ /api/relay/poll ──▶ executa ──▶ /api/relay/result

Segurança — lê isto

  • Guardrails, não sandbox. Restrições de diretórios e comandos bloqueados reduzem o risco mas não isolam o processo: o relay corre com as permissões do teu utilizador. Para risco alto corre o relay num container/VM.

  • Prompt injection. Conteúdo de ficheiros ou páginas pode manipular o modelo. Usa o preset mínimo, aprovação humana em start_process e expirações curtas.

  • Modelo de confiança. Quem controla a tua conta no painel (ou o cliente autorizado) controla as máquinas concedidas. Ativa o 2FA.

  • Permissões efetivas = interseção (grant do servidor ∩ política local ~/.config/mortality/policy.json); o servidor nunca alarga a política local.

  • Tokens guardados só como hash SHA-256; segredos TOTP cifrados (AES-256-GCM); audit log sem conteúdo de ficheiros.

Related MCP server: CesSpace ARC

Guia dentro do painel

O painel inclui uma aba Guia (/guide, pública) com: como funciona, instalação do relay, tabela de comandos da CLI, ligação de clientes, permissões, aprovações, como revogar e resolução de problemas. A barra de abas dá acesso rápido às secções (Dispositivos, Tokens, Clientes, Aprovações, Atividade, Ligar, Guia, Segurança).

Arranque local

git clone https://github.com/lhabacuc/mortality && cd mortality
pnpm install
cp .env.example apps/web/.env      # preenche DATABASE_URL, SESSION_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD
pnpm db:migrate
pnpm dev                            # http://localhost:3000

Faz login com o admin (troca de password obrigatória) e emparelha um dispositivo (ver Instalar o relay):

curl -fsSL http://localhost:3000/install | bash

Deploy (Vercel + Neon)

Testado em 2026-10-14 num deploy real (Vercel Hobby + Neon Free). O passo a passo com comandos está em DEPLOY.md.

  1. Cria um projeto Neon e copia a connection string pooled.

  2. Importa lhabacuc/mortality na Vercel (ou usa a CLI, ver DEPLOY.md) com Root Directory = apps/web (ativa Include source files outside of the Root Directory, necessário para packages/shared).

  3. Define as variáveis de .env.example (no mínimo DATABASE_URL, APP_URL, SESSION_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD). APP_URL tem de ser o URL público e definitivo: é o issuer e o resource OAuth.

  4. O vercel.json corre pnpm db:migrate antes do build (migrações idempotentes). Define DATABASE_URL antes do 1º deploy, senão o build falha logo nessa etapa.

  5. APP_URL é opcional na Vercel: sem ele usa-se VERCEL_PROJECT_PRODUCTION_URL (domínio de produção, https).

  6. Abre o painel, entra com o admin e troca a password. Depois remove ADMIN_PASSWORD das variáveis.

  7. Para um serviço pessoal usa SIGNUP_MODE=invite_only e gera convites em Administração → Convites.

Deploy com a Vercel

O botão acima não foi testado. O caminho validado é o da CLI em DEPLOY.md.

Planos (verificado na documentação em 2026-10-14): Vercel Hobby é só para uso não comercial; inclui 1 000 000 invocações e 4 h de Active CPU por mês; maxDuration até 300 s. Neon Free: 100 CU-h/mês, autosuspend fixo de 5 min.

Consumo e custo do relay sempre ligado

Medido localmente (dev server → Neon us-east-2, RTT elevado) e calculado a partir dos limites acima:

Item

Valor

Queries por poll idle

3 (era 7 antes da otimização)

Latência de um poll idle

~620 ms (era ~1250 ms) — dominada pelo RTT até ao Neon

Polls/dia por dispositivo (idle 20 s)

4 320 → ~130 000/mês (≈13 % das invocações Hobby, por dispositivo)

Modo ativo

1 poll/s durante 5 min após cada job (≤ 300 polls por rajada)

Primeira chamada após inatividade

≤ intervalo idle (20 s)

Problema real: um poll a cada 20 s é muito menor que o autosuspend de 5 min, logo o Neon nunca suspende. 0,25 CU × ~730 h ≈ 182 CU-h/mês, contra 100 CU-h do Free (≈ 16 dias). Opções:

  1. Plano pago do Neon (usa o que já está implementado).

  2. Sinal de "wake" num KV gerido (ex.: Upstash Redis): o poll idle não toca na BD; só jobs a acordam. Proposto, não implementado — requer a tua decisão.

  3. Parar o serviço quando não usas (systemctl --user stop mortality-relay).

  4. Intervalo idle ≥ ~9 min (impraticável: latência).

Active CPU da Vercel não foi medido (precisa de um deploy real).

Instalar o relay

Numa linha, a partir do teu painel (o instalador é servido por ele, por isso o servidor já vem configurado):

curl -fsSL https://<o-teu-painel>/install | bash

O script: verifica Node ≥ 18, descarrega a CLI de /cli/mortality.mjs, verifica o SHA-256, instala em ~/.local/bin/mortality (sem sudo) e pergunta se queres emparelhar já. Para o ler antes: curl -fsSL https://<o-teu-painel>/install. Remover: mortality uninstall e depois curl -fsSL https://<o-teu-painel>/install | bash -s -- --uninstall.

Domínio próprio: para teres curl … https://o-teu-dominio/install | bash, adiciona o domínio ao projeto Vercel (vercel domains add) — o script usa sempre a origem que o serve. Não publiques um comando que aponte para um domínio que não controlas: quem o executar corre o que esse servidor devolver.

Relay

SO

Backend

Estado

Linux

systemctl --user

testado (install, kill -9 → reinício, pause, uninstall)

macOS

launchd (LaunchAgent)

geração do plist testada; não executado em macOS

Windows

Task Scheduler (ao iniciar sessão)

geração dos argumentos testada; não executado em Windows

Em Linux, sem linger o relay só corre com sessão aberta: loginctl enable-linger $USER (o install avisa mas não executa). Reiniciar o serviço mata as sessões de processo em curso.

mortality install [--server URL] mortality status [--json]   mortality logs [-f]
mortality pause | resume         mortality restart           mortality policy show|edit|allow-all|block|unblock
mortality uninstall              mortality run [--service]

Ligar clientes

O painel (/connect) gera os snippets. Estado de compatibilidade:

Cliente

Modo

Estado

MCP Inspector (CLI, HTTP)

PAT

testado

Cliente OAuth simulado (DCR + PKCE + refresh)

OAuth

testado (script + browser)

ChatGPT web (Developer mode, conector personalizado)

OAuth

testado em produção (2026-10-14) — ver abaixo

Claude.ai / Desktop (custom connector)

OAuth

esperado funcionar — não testado contra o cliente real

Claude Code

OAuth / PAT

esperado funcionar — configuração verificada na doc

Gemini CLI

OAuth / PAT

esperado funcionar — verificada na doc

Gemini web (app)

OAuth (registo automático DCR)

testado em uso real (2026-10-14): consentimento, list_directory, read_file, start_search, write_file no audit_log. Se o ecrã pedir Client ID/Secret, cria um em Clientes → Registar cliente OAuth

Cursor

OAuth / PAT

esperado funcionar — verificada na doc

VS Code (Copilot)

PAT (inputs) / OAuth

esperado funcionar — verificada na doc

Grok, Qwen, outros

PAT

depende de o cliente aceitar cabeçalhos num servidor MCP HTTP

Os clientes cloud (Claude.ai, ChatGPT) exigem um servidor acessível pela internet pública (HTTPS).

Resultado do teste com o ChatGPT web (2026-10-14)

Fluxo real contra a Vercel + Neon, com um relay num PC Linux e o preset só leitura:

  1. O ChatGPT descobriu o servidor (WWW-Authenticate → protected resource metadata → authorization server metadata) e registou-se por DCR sem client pré-registado.

  2. No ecrã de consentimento foi escolhido 1 dispositivo, read_only, 1 diretório e 1 hora.

  3. O token OAuth foi emitido com resource = <URL>/api/mcp.

  4. list_directory e read_file executaram no relay e ficaram no audit log (sem conteúdo).

Ainda não validado com o ChatGPT: presets de escrita/terminal, aprovações humanas, refresh do token após 1 h e planos diferentes do usado (o acesso a conectores personalizados depende do plano/workspace).

Permissões

Preset

Inclui

Só leitura

listar, ler, pesquisar, informação do sistema

Leitura e escrita

+ escrever, editar, criar pastas, mover

Terminal

+ processos (por defeito com aprovação humana em start_process)

sudo-for-all

todas as ferramentas e o terminal, sem aprovações. Exige confirmação explícita e dura no máximo 24 h

  • Por defeito qualquer utilizador pode escolher qualquer nível de acesso; o admin pode limitar o preset máximo, as tools e «todos os diretórios» por conta.

  • As alterações aplicam-se de imediato: o servidor lista sempre todas as ferramentas e decide a permissão em cada chamada, por isso mudar o acesso no painel não exige reconectar o cliente (os clientes guardam a lista de ferramentas em cache). whoami devolve as permissões atuais.

  • Todos os diretórios é opt-in por grant; continua limitado pela política local do dispositivo (mortality policy allow-all on).

  • Diretórios bloqueados prevalecem sobre tudo, no servidor e no relay (com realpath: um symlink não os contorna). Aplicam-se às ferramentas de ficheiros e pesquisa; um comando de terminal não é limitado por diretório (guardrail, não sandbox).

  • Um relay anterior à 0.3.0 não impõe bloqueios: o servidor recusa grants com diretórios bloqueados para esse dispositivo.

Variáveis de ambiente

Ver .env.example (comentado).

Testes

pnpm test                                  # web
pnpm --filter mortality test               # relay/CLI
node --experimental-strip-types --test packages/shared/src/index.test.ts

Cobertura: PKCE (vetor RFC 7636), HOTP/TOTP (vetores RFC 4226/6238), sessões, paths/symlinks, interseção de políticas, blocklist, geração systemd/launchd/schtasks, tools do relay. Os fluxos OAuth, aprovações, tetos, convites, isolamento entre utilizadores e 2FA foram validados com scripts e browser contra Neon real (não fazem parte do pnpm test).

Não implementado

  • Login GitHub/Google — precisa de credenciais OAuth tuas e de uma política de associação de contas; não foi implementado para não incluir código de autenticação sem o poder testar.

  • Notificação (push/email) de aprovações pendentes (o utilizador tem de ter o painel aberto; a janela de aprovação é de 30 s).

  • QR code no setup do TOTP (mostra a chave e o URI otpauth://).

Licença

MIT © 2026 lhabacuc

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI clients to securely inspect and edit approved code, use semantic code intelligence, run builds/tests, supervise bounded processes, inspect Git, and execute owner-approved SSH commands on engineering workstations.
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables authorized AI coding agents to securely access development machines, repositories, terminals, and processes through a policy-enforced, audited control plane.
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI clients to securely reach and control tools running on your own computers through a relay and outbound device agent.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI/MCP clients to enumerate, select, and operate authorized remote computers through one OAuth-protected, self-hosted endpoint, with devices connecting outbound and keeping their own cryptographic identities. It layers OAuth scopes, account-to-device grants, server profiles, and device-local ceilings to govern filesystem, shell, process, transfer, Git, and signed agent lifecycle operations with auditable results.
    AGPL 3.0