mempalace.server_registry
Source: mempalace/server_registry.py
Per-palace discovery registry for the MemPalace HTTP MCP hub.
mempalace serve (and mempalace-mcp --transport http) is meant to be the single long-lived writer for a shared palace: once it performs its first mutating tool call it holds the per-palace MCP writer lease (#1818) for its whole lifetime, and every other process is refused palace writes. That is correct for agents — they talk to the hub — but the background save hooks and the plain CLI still spawn short-lived mempalace mine processes, which would be refused while a hub is up and silently stop transcript capture on the hub machine.
This module gives those local processes a way to find the hub instead of fighting it: the HTTP transport records {pid, host, port, scheme, read_only} next to the per-palace bearer token (~/.mempalace/server/<key>/), and callers use :func:read_live_serverinfo to decide "forward this write over HTTP" vs "no hub — do the write directly".
The registry is local-machine only by design: it lives under the user's home, is keyed by the canonical palace path, and a record is trusted only while the recorded pid is still alive — a crashed hub leaves a stale file that every reader ignores and the next hub overwrites.
Functions
server_state_dir
def server_state_dir(palace_path: str) -> PathPer-palace state directory shared by the token and the serverinfo.
Keyed by the canonical palace path so one hub per palace reuses a stable directory across restarts. Must stay in sync with the token location used by mempalace serve (see cli._server_token_path, which delegates here).
server_token_path
def server_token_path(palace_path: str) -> Pathserverinfo_path
def serverinfo_path(palace_path: str) -> Pathwrite_serverinfo
def write_serverinfo(palace_path: str, *, host: str, port: int, scheme: str, read_only: bool)Record this process as the palace's HTTP hub. Returns the file path.
0600 like the token: the record itself is not secret, but the directory convention is "private to the user" and there is no reason to relax it.
mesh_state_path
def mesh_state_path(palace_path: str) -> Pathwrite_mesh_state
def write_mesh_state(palace_path: str, *, peers: dict, profiles: dict) -> PathPublish the hub's mesh estate so other local processes can read it.
The estate — which peers answered last round, their version vectors and advertised profiles — is built by the peer sync loop, and that loop only runs in the HTTP transport. Every other process for this palace (the stdio MCP servers agents actually connect through, the CLI) has the same mempalace_mesh_peers tool and an empty in-memory estate behind it, so without this file they answer "two peers, no status at all" while the hub next door knows the whole picture.
0600 like the token and the serverinfo. The contents are not secret — peers.json tokens never reach the estate — but the directory convention is "private to the user".
read_mesh_state
def read_mesh_state(palace_path: str) -> dictReturn the published estate for this palace.
Always a dict with peers/profiles mappings so callers can merge without None-checks; empty when no hub has published yet or the file is unreadable. writer_alive reports whether the publishing process is still running — a crashed hub leaves a last-known-good estate that is worth showing but must not be read as live.
clear_serverinfo
def clear_serverinfo(palace_path: str) -> NoneRemove this process's serverinfo record, if it is still ours.
Guarded on the recorded pid so a slow atexit from an old hub cannot delete the record a newer hub just wrote for the same palace.
read_live_serverinfo
def read_live_serverinfo(palace_path: str)Return the hub record for this palace, or None.
None when no record exists, the record is unreadable, or the recorded pid is no longer alive (crashed hub — stale file, ignore it).
client_base_url
def client_base_url(info: dict) -> strDial address for a local client, from a serverinfo record.
A wildcard bind ("all interfaces") is dialed via loopback — the record is only ever read on the hub's own machine.
load_server_token
def load_server_token(palace_path: str) -> strThe palace's hub bearer token, or "" when none was ever generated.
