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
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.
Cada copiloto de IA (Claude Code, Cursor, Windsurf, ...) mantém sua própria memória, isolada nessa 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 com zero 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 conectar com o mesmo library-id compartilha a mesma memória; library-ids 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> sempre que vê-lo — funciona de forma idêntica, apenas mais lento para iniciar.
Conectando a partir do 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-o diretamente a ~/.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 desse 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 a partir da extensão VS Code do Claude Code
A extensão compartilha exatamente a mesma configuração MCP da 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 a CLI claude autônoma 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 a partir 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 usar isso proativamente
Conectar o servidor dá ao seu copiloto a capacidade de salvar/recuperar memória — as descrições das ferramentas MCP instructions e do servidor já incentivam qualquer cliente a usar isso 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
| Ferramenta | Finalidade |
|---|---|
save_memory | Salvar um fato/preferência/decisão/tarefa/referência, opcionalmente vinculado a entidades |
search_memory | Busca semântica (vetorial) sobre memórias |
save_episode | Registrar uma interação/evento bruto do qual memórias podem ser derivadas |
list_episodes | Listar episódios registrados, filtráveis por intervalo de tempo/fonte |
save_skill | Salvar um procedimento reutilizável, opcionalmente vinculado a entidades |
search_skills | Busca semântica (vetorial) sobre habilidades, por tarefa/situação |
list_skills | Listar habilidades salvas, filtráveis por tag/fonte |
get_skill | Consultar uma habilidade pelo nome exato ou id |
forget_skill | Arquivar suavemente (padrão) ou excluir permanentemente uma habilidade |
save_adr | Registrar uma decisão arquitetural, opcionalmente substituindo uma anterior |
search_adrs | Busca semântica (vetorial) sobre ADRs, por problema ou área |
list_adrs | Listar o log de ADRs, filtrável por status/tag/fonte |
get_adr | Consultar um ADR completo, por número ou id |
forget_adr | Arquivar suavemente (padrão) ou excluir permanentemente um ADR |
get_entity | Consultar uma entidade e tudo o que a menciona ou se relaciona a ela |
relate_entities | Criar uma relação de grafo entre duas entidades |
forget_memory | Arquivar suavemente (padrão) ou excluir permanentemente uma memória |
export_library | Exportar esta biblioteca para um pacote .memport.json portátil |
import_library | Importar um pacote .memport.json, mesclando ou sobrescrevendo |
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 do zero ("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 escolher 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 de forma alguma.
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, para que uma memória possa apontar de volta ao 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-feira passada?" é uma leitura cronológica de episódios via list_episodes (filtrável por intervalo de tempo e fonte). 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 a essas 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 diga 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).
Habilidades são o que faz "portar habilidades comuns entre IAs" funcionar sem maquinário extra: como 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 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.
Log de ADRs
O mem-port também mantém um log de ADRs — registros de decisão arquitetural, as escolhas técnicas consequentes cujo raciocínio 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 não é o mesmo que 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 corresponde a 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 a mais antiga como superseded e vincula as duas, para que o log permaneça legível de qualquer extremidade em vez de acumular registros contraditórios.
Prefira substituir um ADR a forget_adr — uma decisão que foi revertida geralmente vale a pena manter no registro.
Portando memória entre máquinas
Compartilhamento na mesma máquina entre copilotos não precisa de passo 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 curada 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 é um no-op). 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 roda em primeiro plano — é um daemon de longa duração, não um comando de uso ú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), quase sempre é esse o motivo: nada está realmente escutando. Verifique com lsof -i :8787.
Para uma sessão rápida, coloque em segundo plano: mem-port serve & (ou nohup mem-port serve > ~/.mem-port.log 2>&1 & para sobreviver ao fechar o terminal). Para algo que sobreviva a reinicializações e se reinicie sozinho se cair, configure-o como um serviço de segundo plano 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 próprio shim mem-port. 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 pegar uma nova versão após npm install -g @rsl-innovation/mem-port, reinicie o job 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 job, e bootstrap o registra e 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
| Env var | Padrão | Propósito |
|---|---|---|
MEM_PORT_PORT | 8787 | Porta HTTP |
MEM_PORT_DATA_DIR | Diretório de dados do aplicativo apropriado ao SO | Onde o armazenamento SurrealDB e o modelo de incorporação em cache residem |
MEM_PORT_EMBEDDING_MODEL | Xenova/all-MiniLM-L6-v2 | ID do modelo de incorporação local (reservado para uso futuro) |
MEM_PORT_MODEL_CACHE_DIR | <data-dir>/models | Substituir a localização do cache do modelo de incorporação |
Todo o estado fica em 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.
Desenvolvimento
npm install
npm run dev # start the daemon with tsx, no build step
npm test # vitest: tenancy isolation + export/import round-trip
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. Aumentar a versão cria o commit e a tag vX.Y.Z; enviar a tag é o que aciona todo o resto:
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 do 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 executando 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.1no 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 é executado localmente e pode alcançar o daemon, enquanto a sessão web da mesma conta não pode. - A CLI do Claude Code e a extensão do VS Code já são executadas 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 (ainda sem índice HNSW/DISKANN) — adequada na escala de um armazenamento de memória pessoal, reavalie se uma biblioteca crescer muito.
- O filtro de escopo do
export_librarysuportamemory_typesesince; filtrar porentity_idsainda não está implementado. - Sem autenticação — o daemon vincula-se apenas a
127.0.0.1e confia em qualquer coisa executada localmente na sua máquina. - Os
onnxruntime-node/sharpempacotados do@huggingface/transformerscarregam avisos transitivos conhecidos (bibliotecas de análise de ZIP/imagem) sem correção upstream ainda. O mem-port nunca fornece a eles entrada não confiável, masnpm auditos sinalizará.