mem-port

Um servidor MCP (Model Context Protocol) local para memória agêntica portátil e de longo prazo, um pendrive para o seu contexto de IA.

Documentação

mem-port

npm version

mem-port.com

Um servidor MCP (Model Context Protocol) local para memória agêntica portátil e de longo prazo — um pen drive para o contexto da sua IA.

Cada copiloto de IA (Claude Code, Cursor, Windsurf, ...) mantém sua própria memória, isolada naquela ferramenta. A solução usual — copiar e colar contexto, resumos ou notas exportadas de um agente para outro — apenas captura um instantâneo congelado no momento em que você o criou. A partir daí, as cópias divergem: cada agente continua aprendendo por conta própria, nada mantém as cópias sincronizadas, e quanto mais tempo passa, mais seus copilotos discordam sobre o que é realmente verdade. O mem-port roda como um único daemon local ao qual qualquer número de copilotos pode se conectar, apoiado por um grafo de conhecimento embutido (entidades, episódios, memórias, habilidades, registros de decisão arquitetural e as relações entre eles) que sobrevive a reinicializações e pode ser exportado para um arquivo portátil e movido para qualquer lugar. Cada copiloto conectado lê e escreve no mesmo grafo, então não há nada para colar e nada para divergir.

Diferente de outros projetos de memória para agentes, o mem-port não precisa de serviços externos — sem Postgres, sem Qdrant, sem Neo4j. É um único processo, uma instância embutida do SurrealDB combinando armazenamento em grafo e busca vetorial, e busca semântica local sem configuração (sem necessidade de chave de API).

Conectar um cliente (abaixo) dá a ele a capacidade de usar o mem-port; para instruções mais detalhadas e ajustáveis sobre o que ele deve realmente salvar e quando — incluindo manter memória pessoal/da equipe/do projeto em escopos separados — veja MEMORY_GUIDE.md.

Início rápido

npx @rsl-innovation/mem-port serve

Isso inicia um daemon em http://127.0.0.1:8787/mcp. Aponte qualquer cliente MCP para ele via Streamable HTTP, com um cabeçalho library-id identificando seu workspace. Todo copiloto que se conecta com o mesmo library-id compartilha a mesma memória; library-id diferentes são totalmente isolados entre si (cada um mapeia para seu próprio namespace/banco de dados SurrealDB) — não há vazamento entre locatários.

npx verifica o registro a cada invocação. Se você for executar comandos do mem-port com frequência, instale-o globalmente para que mem-port seja um comando simples no seu PATH:

npm install -g @rsl-innovation/mem-port
mem-port serve

O restante deste README usa mem-port <command> por brevidade. Se você não instalou globalmente, substitua npx @rsl-innovation/mem-port <command> onde quer que o veja — funciona de forma idêntica, apenas inicia mais devagar.

Conectando pelo Claude Code

Mais fácil: use a CLI (--header aceita qualquer número de pares Key: Value). Adicione --scope user para que o servidor esteja disponível em todos os projetos desta máquina, não apenas naquele em que você está ao executar o comando — o escopo padrão local o vincula a um único diretório de projeto:

claude mcp add --transport http mem-port http://127.0.0.1:8787/mcp \
  --header "library-id: my-personal-workspace" \
  --scope user

Ou adicione diretamente ao ~/.claude.json (escopo do usuário, aplica-se em qualquer lugar) ou .mcp.json (escopo do projeto, compartilhável via controle de versão com a equipe daquele repositório). O campo type é obrigatório — uma entrada com url mas sem type é tratada como um servidor stdio mal configurado:

{
  "mcpServers": {
    "mem-port": {
      "type": "http",
      "url": "http://127.0.0.1:8787/mcp",
      "headers": { "library-id": "my-personal-workspace" }
    }
  }
}

Execute /mcp dentro do Claude Code para confirmar que ele mostra mem-port como conectado.

Conectando pela extensão VS Code do Claude Code

A extensão compartilha exatamente a mesma configuração MCP que a CLI (.mcp.json / ~/.claude.json) — não há uma interface de configurações separada para adicionar um servidor. Abra o terminal integrado (Ctrl+` / Cmd+`) e execute o mesmo comando acima:

claude mcp add --transport http mem-port http://127.0.0.1:8787/mcp \
  --header "library-id: my-personal-workspace" \
  --scope user

Isso requer que a CLI claude autônoma esteja instalada — a extensão inclui sua própria cópia privada para o painel de chat e não coloca claude no PATH do seu terminal, então claude mcp add não funcionará no terminal integrado até que você instale a CLI separadamente. Editar .mcp.json diretamente (o bloco JSON acima) também funciona e não precisa da CLI.

Depois de adicionado, digite /mcp no painel de chat para confirmar que o mem-port aparece como conectado, ou para habilitar/desabilitar/reconectar.

Conectando de outros clientes MCP

Qualquer cliente que suporte Streamable HTTP com cabeçalhos personalizados pode se conectar da mesma forma. Clientes que só suportam servidores baseados em stdio (algumas configurações do Claude Desktop, por exemplo) precisam de uma ponte stdio-para-HTTP, como mcp-remote:

{
  "mcpServers": {
    "mem-port": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "http://127.0.0.1:8787/mcp", "--header", "library-id:my-personal-workspace"]
    }
  }
}

Fazendo seu copiloto usá-lo proativamente

Conectar o servidor dá ao seu copiloto a capacidade de salvar/recuperar memória — as descrições das ferramentas MCP instructions do servidor já incentivam qualquer cliente a usá-lo proativamente. Para controle mais explícito (e para manter memória pessoal/organizacional/do projeto em escopos library-id separados em vez de um único balde), veja MEMORY_GUIDE.md para instruções a colar no arquivo de instruções personalizadas do seu copiloto.

Ferramentas

FerramentaFinalidade
save_memorySalvar um fato/preferência/decisão/tarefa/referência, opcionalmente vinculado a entidades
search_memoryBusca semântica (vetorial) sobre memórias
save_episodeRegistrar uma interação/evento bruto do qual memórias podem ser derivadas
list_episodesListar episódios registrados, filtráveis por intervalo de tempo/origem
save_skillSalvar um procedimento reutilizável, opcionalmente vinculado a entidades
search_skillsBusca semântica (vetorial) sobre habilidades, por tarefa/situação — apenas descrições
list_skillsListar habilidades salvas, filtráveis por tag/origem — apenas descrições
get_skillConsultar uma habilidade pelo nome ou id exato
forget_skillArquivamento suave (padrão) ou exclusão permanente de uma habilidade
save_adrRegistrar uma decisão arquitetural, opcionalmente substituindo uma anterior
search_adrsBusca semântica (vetorial) sobre ADRs, por problema ou área
list_adrsListar o log de ADRs, filtrável por status/tag/origem
get_adrConsultar um ADR completo, por número ou id
forget_adrArquivamento suave (padrão) ou exclusão permanente de um ADR
get_entityConsultar uma entidade além de tudo que a menciona ou se relaciona a ela
relate_entitiesCriar uma relação de grafo entre duas entidades
forget_memoryArquivamento suave (padrão) ou exclusão permanente de uma memória
export_libraryExportar esta biblioteca para um pacote .memport.json portátil
import_libraryImportar um pacote .memport.json, mesclando ou sobrescrevendo

Conexões somente leitura

Dez dessas ferramentas apenas leem: search_memory, list_episodes, get_entity, search_skills, list_skills, get_skill, search_adrs, list_adrs, get_adr e export_library. As outras nove podem alterar a biblioteca.

Uma conexão pode ser limitada à metade de leitura, e as ferramentas de escrita então não são registradas de forma alguma — uma tools/call para save_memory retorna como ferramenta desconhecida, porque o servidor construído para aquela solicitação nunca a teve. Duas coisas independentes podem solicitar isso, e a mais restritiva vence:

grant is read-only   ──▶ read-only, always  (set by an admin; the member cannot opt out)
read-only: 1 header  ──▶ read-only          (set by the client, on itself)
otherwise            ──▶ read-write

Por membro. Com autenticação ativada, toda concessão de workspace no painel administrativo é leitura-escrita ou somente leitura. Esta é a opção ideal quando alguém deve consultar uma biblioteca curada sem adicionar a ela — o copiloto dessa pessoa nunca recebe as ferramentas, então não pode escrever no workspace mesmo que decida fazê-lo.

Por cliente. Qualquer cliente pode remover suas próprias ferramentas de escrita com um cabeçalho read-only: 1 ao lado de library-id, da mesma forma que mcp-apps: 0 desativa resultados renderizados:

{
  "mcpServers": {
    "mem-port": {
      "type": "http",
      "url": "http://127.0.0.1:8787/mcp",
      "headers": { "library-id": "my-personal-workspace", "read-only": "1" }
    }
  }
}

Isso é útil para um trabalho de CI, uma máquina compartilhada ou um copiloto que você prefere que não mexa em uma biblioteca que você cura manualmente. Ele só pode remover ferramentas: uma concessão somente leitura permanece somente leitura independentemente de como o cliente está configurado, e não há variável de ambiente que torne todo o daemon somente leitura — com MEM_PORT_AUTH=off não há concessões, então o cabeçalho é a única coisa em jogo.

Resultados renderizados (MCP Apps)

As nove ferramentas de leitura — search_memory, list_episodes, search_skills, list_skills, get_skill, search_adrs, list_adrs, get_adr, get_entity — renderizam seus resultados como cartões em hosts que suportam MCP Apps, em vez de mostrar o JSON que seu copiloto lê. Listas retornam como cartões de resultado; get_* como uma visualização de detalhes.

Cada ferramenta de leitura declara _meta.ui.resourceUri apontando para um único recurso ui://mem-port/results.html. O host busca essa página, renderiza-a em um iframe com sandbox e envia o resultado da ferramenta para dentro dela — o modelo de visualização viaja no _meta do resultado, então o cartão que você vê e o JSON que o modelo lê vêm da mesma descrição e não podem discordar.

Suportado por Claude e Claude Desktop, VS Code Copilot, ChatGPT, Cursor, Goose e outros — veja a matriz de clientes. Claude Code não está entre eles, então os resultados permanecem como texto lá. O bloco de texto permanece inalterado e sempre em primeiro lugar, então um host que não renderiza MCP Apps se comporta exatamente como antes.

Está ativado por padrão. Para desativá-lo, adicione um cabeçalho mcp-apps: 0 ao lado de library-id onde você configura o cliente:

{
  "mcpServers": {
    "mem-port": {
      "type": "http",
      "url": "http://127.0.0.1:8787/mcp",
      "headers": { "library-id": "my-personal-workspace", "mcp-apps": "0" }
    }
  }
}

Ou desative-o para todos os clientes de uma vez iniciando o daemon com MCP_APPS=0 mem-port serve. Um cabeçalho mcp-apps explícito vence a variável de ambiente em ambas as direções, então um cliente pode optar por reativar em um daemon que o tenha desativado.

Memórias e episódios

Memórias são a unidade central — uma declaração durável e autocontida que vale a pena recordar em uma sessão posterior que começa sem contexto ("Usuário prefere modo escuro em todos os editores"). Cada uma carrega um memory_type (fact, preference, decision, task ou reference) que search_memory pode filtrar, e um importance de 0 a 1. O tipo vale a pena ser escolhido deliberadamente: é a diferença entre uma biblioteca pesquisável e uma pilha plana de texto — veja MEMORY_GUIDE.md para saber como escolher e o que não pertence à memória.

Episódios são o material bruto do qual as memórias são derivadas — uma conversa, uma sessão de depuração, uma reunião — registrados com um title, content, um source (qual copiloto o registrou) e occurred_at. Onde uma memória é uma afirmação destilada, um episódio é um registro não editado de algo que aconteceu. save_memory aceita um source_episode_id, então uma memória pode apontar de volta para o episódio de onde veio e manter sua proveniência.

Os dois respondem a perguntas diferentes, e é por isso que ambos existem: "o que é verdade sobre este projeto?" é uma busca semântica sobre memórias, enquanto "o que aconteceu na terça passada?" é uma leitura cronológica de episódios via list_episodes (filtrável por intervalo de tempo e origem). Memórias são o que você pesquisa; episódios são o que você reproduz.

Entidades — pessoas, projetos, ferramentas — são o tecido conjuntivo. Passar entity_refs ao salvar qualquer coisa vincula isso às entidades, criando-as na primeira menção. get_entity então retorna toda memória, episódio, habilidade e ADR que menciona a entidade, além de suas entidades relacionadas, o que torna "me conte tudo relevante para checkout-service" uma única consulta em vez de várias buscas. relate_entities adiciona arestas tipadas entre as próprias entidades (Alice —lidera→ mem-port).

Memória de habilidades

Junto com episódios e memórias, o mem-port armazena habilidades — procedimentos reutilizáveis para tarefas recorrentes (ex.: "como depurar um teste instável neste repositório," "os passos de deploy para checkout-service"). Uma habilidade tem um name, um description (a condição de gatilho — quando um copiloto deve recorrer a ela, correspondida por search_skills) e content (as instruções reais). search_skills e list_skills retornam descrições e metadados, mas não content, e get_skill fornece o corpo da única habilidade que você escolheu. Como ambos são feitos para serem chamados proativamente no início de uma tarefa, retornar todos os corpos de procedimentos colocaria a biblioteca inteira no contexto do modelo para responder "existe uma habilidade para isso?" — medido em 68 kB para 21 habilidades, contra 12 kB para a mesma chamada agora.

Habilidades são o que faz "portar habilidades comuns entre IAs" funcionar sem maquinário extra: como elas vivem no mesmo grafo de conhecimento compartilhado que todo o resto, uma habilidade salva pelo Claude Code fica imediatamente visível para o Cursor ou Windsurf no momento em que eles se conectam com o mesmo library-id — sem necessidade de conversão de formato de arquivo. export_library/import_library transportam habilidades entre máquinas exatamente como entidades, episódios e memórias.

Registro de ADR

O mem-port também mantém um registro de ADR — registros de decisão arquitetural, as escolhas técnicas consequentes cuja justificativa importa meses depois. Cada ADR recebe um número sequencial dentro de sua biblioteca (ADR-0001, ADR-0002, ...) e contém as quatro coisas que um registro de decisão precisa: o context que forçou a decisão, o decision em si, seu consequences e o alternatives que perdeu.

Isso é deliberadamente diferente de save_memory(memory_type: "decision"). Uma memória registra que algo foi decidido; um ADR mantém o enquadramento do problema e as opções rejeitadas, que é o que você realmente precisa quando alguém propõe a opção rejeitada novamente um ano depois. search_adrs faz correspondência com título + contexto + decisão, então "por que não estamos usando Postgres?" encontra o registro mesmo quando não compartilha nenhuma palavra com ele.

Decisões são revertidas, então os ADRs têm um ciclo de vida (proposed → accepted, depois superseded ou deprecated) e uma cadeia de substituição. Passar supersedes ao registrar uma decisão mais recente — como um ID de registro, um número ou sua forma de exibição como ADR-0003 — marca automaticamente o mais antigo como superseded e vincula os dois, para que o registro permaneça legível de qualquer extremidade em vez de acumular registros contraditórios.

Prefira substituir um ADR em vez de forget_adr — uma decisão que foi revertida geralmente vale a pena manter no registro.

Portando memória entre máquinas

O compartilhamento na mesma máquina entre copilotos não precisa de etapa extra — eles apenas se conectam ao mesmo daemon com o mesmo library-id. export_library/import_library resolvem um problema diferente: mudar para uma nova máquina, fazer backup, versionamento (o pacote é JSON simples — faça commit em um repositório git privado se quiser) ou entregar uma fatia selecionada de memória para outra pessoa.

# on the old machine
mem-port export --library-id my-personal-workspace
# -> writes <data-dir>/exports/my-personal-workspace-<timestamp>.memport.json

# on the new machine, after copying the file over
mem-port import --library-id my-personal-workspace --in ./my-personal-workspace-....memport.json

import tem como padrão --mode merge (deduplica entidades por nome+tipo, memórias/episódios/habilidades/ADRs por hash de conteúdo — importar o mesmo pacote duas vezes é uma operação nula). ADRs importados são renumerados para o final da sequência da biblioteca de destino em vez de colidir com seus números existentes; links de substituição são transportados por referência de registro, então uma cadeia sobrevive à renumeração intacta. Passe --mode overwrite para limpar a biblioteca de destino primeiro, ou --dry-run para ver o que aconteceria sem escrever nada.

Executando de forma persistente

mem-port serve executa em primeiro plano — é um daemon de longa duração, não um comando único, então ele bloqueia o terminal que o iniciou e morre quando esse terminal fecha. Se seu cliente MCP não conseguir conectar (ECONNREFUSED 127.0.0.1:8787), essa é quase sempre a razão: nada está realmente escutando. Verifique com lsof -i :8787.

Para uma sessão rápida, coloque-o em segundo plano: mem-port serve & (ou nohup mem-port serve > ~/.mem-port.log 2>&1 & para sobreviver ao fechamento do terminal). Para algo que sobreviva a reinicializações e se reinicie se cair, configure-o como um serviço de sistema adequado.

macOS (launchd)

which node        # note this path
which mem-port     # note this path too, then resolve the symlink:
readlink -f "$(which mem-port)"   # -> .../lib/node_modules/@rsl-innovation/mem-port/bin/mem-port.js

Escreva ~/Library/LaunchAgents/com.rsl-innovation.mem-port.plist, substituindo os dois caminhos acima. Invoque node diretamente com o caminho do script resolvido — não aponte ProgramArguments para o shim mem-port em si. launchd não herda o PATH do seu shell, então o shebang #!/usr/bin/env node do shim falha com env: node: No such file or directory quando o launchd o executa:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.rsl-innovation.mem-port</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/node</string>
        <string>/usr/local/lib/node_modules/@rsl-innovation/mem-port/bin/mem-port.js</string>
        <string>serve</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/YOUR_USERNAME/Library/Logs/mem-port.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/YOUR_USERNAME/Library/Logs/mem-port.error.log</string>
</dict>
</plist>
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.rsl-innovation.mem-port.plist   # start now + on every login
launchctl bootout gui/$(id -u)/com.rsl-innovation.mem-port                                  # stop and unregister
tail -f ~/Library/Logs/mem-port.log ~/Library/Logs/mem-port.error.log                       # logs

Para adotar uma nova versão após npm install -g @rsl-innovation/mem-port, reinicie o trabalho em execução no lugar — não é necessário descarregar/recarregar o plist:

launchctl kickstart -k gui/$(id -u)/com.rsl-innovation.mem-port

Para pará-lo e iniciá-lo novamente mais tarde, use bootout/bootstrap (acima) em vez de launchctl stop — o KeepAlive deste plist é incondicionalmente true, então um stop simples é imediatamente relançado pelo launchd. bootout realmente desregistra o trabalho, e bootstrap registra e o inicia novamente.

Linux (systemd --user)

# ~/.config/systemd/user/mem-port.service
[Unit]
Description=mem-port

[Service]
ExecStart=/usr/bin/node /path/to/lib/node_modules/@rsl-innovation/mem-port/bin/mem-port.js serve
Restart=on-failure

[Install]
WantedBy=default.target
systemctl --user enable --now mem-port
journalctl --user -u mem-port -f

Configuração

Variável de ambientePadrãoFinalidade
MEM_PORT_PORT8787Porta HTTP
MEM_PORT_DATA_DIRDiretório de dados de aplicativo apropriado ao SOOnde o armazenamento SurrealDB e o modelo de incorporação em cache vivem
MEM_PORT_EMBEDDING_MODELXenova/all-MiniLM-L6-v2ID do modelo de incorporação local (reservado para uso futuro)
MEM_PORT_MODEL_CACHE_DIR<data-dir>/modelsSubstituir o local do cache do modelo de incorporação
MCP_APPS / MEM_PORT_MCP_APPSativadoDefina como 0 para impedir que as ferramentas de leitura declarem uma interface MCP Apps

Por padrão, todo o estado vive sob um único diretório de dados — o armazenamento SurrealDB (surrealkv://, persistente entre reinicializações) e o modelo de incorporação local em cache. Exclua o diretório de dados para redefinir completamente.

Usando um SurrealDB hospedado

Aponte o mem-port para um servidor SurrealDB existente em vez do mecanismo incorporado:

Variável de ambientePadrãoFinalidade
MEM_PORT_DB_URLsurrealkv://<data-dir>/memport.dbURL do banco de dados. Uma URL ws:// ou wss:// seleciona o driver hospedado
MEM_PORT_DB_NAMESPACEmemportNamespace que contém um banco de dados por ID de biblioteca
MEM_PORT_DB_USER / MEM_PORT_DB_PASS—Credenciais. Obrigatórias para um servidor hospedado
MEM_PORT_DB_TOKEN—Token de portador, como alternativa a usuário/senha
MEM_PORT_DB_PREFIXnenhumPrefixo para nomes de bancos de dados de locatário, em um cluster compartilhado com outros aplicativos
MEM_PORT_DB_MAX_SESSIONS256Sessões por biblioteca em cache antes que a menos recentemente usada seja fechada
MEM_PORT_DB_URL=wss://your-instance.surreal.cloud \
MEM_PORT_DB_USER=root \
MEM_PORT_DB_PASS=... \
  mem-port serve

Três restrições que valem a pena conhecer antes de apontar isso para um cluster. Cada uma é verificada na inicialização, então uma configuração incorreta falha uma vez com uma explicação em vez de em cada chamada de ferramenta:

  • SurrealDB 3.0 ou mais recente. Sessões e transações são ambos recursos do lado do servidor da versão 3.0, e o mem-port precisa de ambos — uma sessão bifurcada por ID de biblioteca para locação, e uma transação para import_library. Um servidor 2.x conecta bem e depois falha em cada solicitação.
  • Somente WebSocket. O mecanismo HTTP do SurrealDB não suporta nenhum desses recursos, independentemente da versão do servidor, então uma URL http(s):// é rejeitada.
  • O usuário deve ser de nível root ou namespace. O mem-port cria um banco de dados por ID de biblioteca dentro de seu namespace e define o esquema desse banco de dados no primeiro uso, o que um usuário com escopo de banco de dados não pode fazer.

Contas e o painel de administração

Por padrão, o mem-port não tem contas: ele vincula ao loopback, e o sistema operacional é o limite. Isso é correto para um daemon pessoal e errado no momento em que o daemon é alcançável de qualquer outro lugar, então a autenticação é ativada com exposição — desativada no loopback, obrigatória em qualquer outra interface, e MEM_PORT_AUTH substitui de qualquer forma.

Com a autenticação ativada, um painel de administração é servido em /admin. Entre com o administrador de inicialização (MEM_PORT_ADMIN_USER / MEM_PORT_ADMIN_PASSWORD, usado apenas enquanto nenhum administrador existir) e a partir daí:

  • crie espaços de trabalho — um espaço de trabalho é um grafo de conhecimento isolado, e seu nome é o que os clientes enviam como library-id
  • crie usuários e emita uma chave de API para cada um (mostrada uma vez; apenas um hash é mantido) com revogação quando uma chave precisar ser rotacionada
  • conceda a um usuário acesso a espaços de trabalho específicos, cada concessão de leitura-escrita ou somente leitura — um membro somente leitura recebe apenas as dez ferramentas de leitura, então seu copiloto não tem como escrever nesse espaço de trabalho (veja Conexões somente leitura). Cada usuário também carrega um nível padrão que pré-seleciona a escolha quando você concede a ele um espaço de trabalho.
  • explore o grafo de um espaço de trabalho — uma visão somente leitura do que um espaço de trabalho contém e como suas entidades se conectam, que é a maneira mais rápida de verificar se um cliente recém-conectado está realmente escrevendo algo

O painel também serve sua própria documentação em /admin/docs, cobrindo tanto o portal quanto o produto, com configuração de cliente copiável e colável para a URL que o administrador realmente alcançou.

Os clientes então enviam dois cabeçalhos, e nada mais muda:

Authorization: Bearer <the user's key>
library-id: <a workspace they were granted>

Atualizar uma implantação existente não muda nada por si só: concessões que antecedem isso mantêm acesso de leitura-escrita até que um administrador diga o contrário.

Ser administrador não confere acesso a dados — administradores decidem quem pode alcançar o quê, que é um poder diferente de lê-lo, então um administrador que quer um espaço de trabalho concede-o a si mesmo.

Implantação

Imagem de contêiner, uma pilha Compose local e manifestos Cloud Run vivem em deployments/. A configuração é documentada em .env.example.

Duas coisas mudam quando o mem-port para de rodar em localhost, ambas cobertas lá: um banco de dados hospedado se torna obrigatório (o mecanismo incorporado perde dados em sistemas de arquivos efêmeros e deixa réplicas divergirem), e o vínculo de loopback que atualmente serve como o limite de segurança desaparece — o mem-port não tem autenticação própria, então outra coisa precisa fornecer uma. Os manifestos fornecidos têm como padrão fechado por esse motivo.

Usando Postgres em vez disso

O mem-port inclui dois drivers de armazenamento. O padrão é SurrealDB incorporado, que não precisa de nada instalado. A alternativa é Postgres com pgvector:

npm install pg                      # optional dependency, only for this driver
MEM_PORT_DB_URL=postgres://user:pass@host:5432/memport mem-port serve

Cada espaço de trabalho recebe seu próprio esquema Postgres, então o isolamento é estrutural em vez de uma cláusula WHERE. O pgvector é obrigatório — toda busca que o mem-port oferece é uma similaridade de cosseno sobre uma incorporação — e o mem-port tenta CREATE EXTENSION ele mesmo, o que funciona na maioria dos serviços gerenciados onde está disponível, mas não habilitado.

Os dois drivers são intercambiáveis, e isso é imposto em vez de apenas afirmado: test/crossDriver.test.ts semeia o mesmo fixture através de ambos e afirma que toda ferramenta de leitura retorna saída byte-idêntica.

Adicionando outro banco de dados

O armazenamento fica atrás de um contrato em src/interfaces/, expresso em termos de domínio — store.skills.search(vector, filter), store.entities.detail({ name }) — sem linguagem de consulta, objetos de ID de registro ou sintaxe de grafo nele. Cada mecanismo é confinado ao seu próprio diretório (src/db/surreal/, src/db/postgres/); nada sob src/mcp/, src/port/ ou src/services/ importa um driver.

Para adicionar um:

  1. Implemente LibraryStore e seus sete sub-armazenamentos, além de StoreProvider, sob src/db/<engine>/.
  2. Adicione um caso a createStoreProvider e um valor de driver em src/config.ts.

As obrigações que valem a pena ler duas vezes são aquelas com as quais o driver Postgres teve que ter cuidado: IDs e carimbos de data/hora cruzam o limite como strings; campos opcionais não definidos permanecem ausentes em vez de se tornarem null (SurrealDB retorna undefined, Postgres retorna um nulo explícito, e JSON.stringify os trata de forma diferente); search classifica por similaridade de cosseno e exclui linhas sem incorporação; entities.detail responde a uma junção de quatro vias sem um N+1; e transaction(fn) reverte em Rollback enquanto ainda retorna sua carga útil.

Aponte crossDriver.test.ts para um novo driver e ele lhe dirá se o contrato realmente se sustenta.

Desenvolvimento

npm install
npm run dev        # start the daemon with tsx, no build step
npm test           # vitest: golden output, tenancy, skills, ADRs, export/import round-trip
                   # the hosted-SurrealDB suite needs Docker; it skips (with a warning) without it
npm run typecheck
npm run build       # tsup -> dist/, what npx @rsl-innovation/mem-port actually runs

scripts/smoke.sh é um teste de fumaça com curl simples contra um daemon já em execução (sem dependência de Node/Inspector, utilizável em CI):

npm run dev &
./scripts/smoke.sh

Lançamento

Os lançamentos são automatizados. O incremento da versão cria o commit e a tag vX.Y.Z; o push da tag é o que aciona todo o restante:

npm version patch   # or minor / major — runs typecheck + tests first
git push --follow-tags

O workflow Release então verifica se a tag corresponde a package.json, reexecuta a verificação de tipos e os testes, publica no npm (com proveniência, via publicação confiável OIDC — nenhum token armazenado em qualquer lugar) e cria o Release no GitHub com notas geradas a partir dos commits desde a tag anterior.

Não execute npm publish manualmente; um push de tag é o único caminho suportado.

Armadilhas

  • mem-port é um servidor localhost — ele só funciona com clientes executados na mesma máquina. Sessões de chat hospedadas na web/nuvem (por exemplo, chatgpt.com ou claude.ai em uma aba do navegador) são executadas no lado do servidor e não têm rota para 127.0.0.1 no seu computador, então não conseguem alcançar o mem-port, independentemente de como ele esteja configurado. Para conectar ChatGPT, Claude ou ferramentas similares, instale o aplicativo de desktop deles e adicione o mem-port lá — o aplicativo de desktop roda localmente e consegue alcançar o daemon, enquanto a sessão web da mesma conta não consegue.
  • O CLI do Claude Code e a extensão do VS Code já rodam localmente, então funcionam de imediato (veja as instruções de conexão acima) — essa armadilha importa principalmente para ferramentas que você poderia usar apenas pelo navegador.

Limitações conhecidas (v1)

  • A busca vetorial é por força bruta (sem índice HNSW/DISKANN ainda) — adequada para a escala de um armazenamento de memória pessoal; reavalie se uma biblioteca crescer muito.
  • A filtragem de escopo do export_library suporta memory_types e since; a filtragem por entity_ids ainda não foi implementada.
  • Sem autenticação — o daemon vincula-se apenas a 127.0.0.1 e confia em qualquer coisa executada localmente na sua máquina.
  • O @huggingface/transformers incluído no onnxruntime-node/sharp carrega avisos transitivos conhecidos (bibliotecas de análise de ZIP/imagens) sem correção upstream ainda. O mem-port nunca alimenta esses componentes com entrada não confiável, mas o npm audit os sinalizará.