Hosted Server (Beta)
The hosted server is the cloud side of claude-mem: a Postgres-backed HTTP service (/v1) plus a separate BullMQ generation worker. Where the local plugin keeps
memory in ~/.claude-mem/claude-mem.db on your machine, the hosted server keeps
it per team and per project in Postgres, and exposes it back to any MCP
client over an authenticated link.
Three capabilities landed together and are documented here:
Remote MCP recall
Paste an authenticated link into Claude Code to recall your cloud memory —
read-only, team/project-scoped.
Paid-readiness
Opt-in rate limiting, monthly request/token quotas, and usage metering —
the guards a paid tier needs.
Data deletion
Right-to-erasure: forget a single memory, or purge everything captured for a
project.
The shape of the system
(team_id, project_id). An API key carries a team
(always) and an optional project scope; that scoping bounds every read,
write, and delete.
Authentication
SetCLAUDE_MEM_AUTH_MODE=api-key and send Authorization: Bearer <key> on every
request. Scopes gate access:
- Read endpoints (search, context, recall, usage) require
memories:read. - Write endpoints (ingest, key issuance, deletion) require
memories:write.
api_keys table; the raw cm_... value
is shown exactly once, at mint time.
Remote authenticated MCP recall
/v1/mcp is a streamable-HTTP MCP server. It’s
the secure link a user pastes into Claude Code to recall their cloud memory. It is
read-only and authenticated by the same API key as the REST routes
(memories:read); the key’s team — and project, if the key is project-scoped —
bounds every read.
The transport is stateless — one MCP server + transport per request — so it
needs no session affinity behind a load balancer. Mutating tools are
intentionally absent: a pasted recall link can never write or delete. Every read
is written to
audit_log as an observation.read event, the same as
POST /v1/search.Connecting a client: key issuance + connect
Two routes turn “I have a server” into “Claude Code is recalling my cloud memory”:-
POST /v1/keys(requiresmemories:write) mints a read-only API key for the caller’s team and returns a paste-ready connect command. The raw key appears once. Body:{ "expiresInDays"?: number }. Minting requires write scope so a read-only key can’t escalate itself into more keys. -
GET /v1/connect(requiresmemories:read) returns the same command with a<YOUR_API_KEY>placeholder — a GET never mints. ThemcpUrlis built fromCLAUDE_MEM_PUBLIC_URL(recommended when behind a proxy or load balancer) or, failing that, the request host.
Paid-readiness: rate limiting, quotas, metering
These guards run after auth and are opt-in via environment variables. Unset (the default) means no rate limit, no quota, and no metering — behavior is identical to a server without them. Every guard fails open: a backing-store error never blocks a legitimate request.
Token and observation metering is written to the same
usage_events table from
the generation worker, so usage reflects real provider spend, not just HTTP calls.
GET /v1/usage returns the caller team’s per-kind totals for the current month:
“Gates writes only” is deliberate: ingestion is what drives generation, which is
what costs tokens. A team that blows its token budget can still read its
existing memory — you never lock someone out of their own data over billing.
Data deletion (forget)
Right-to-erasure. Both routes requirememories:write and are scoped to the
caller’s team. Both write an audit_log entry.
-
DELETE /v1/memories/:id— delete a single observation; itsobservation_sourcescascade. Returns404if no such observation exists for the team. Audited asobservation.deleted. -
DELETE /v1/projects/:projectId/memory— purge all captured content for a project in one transaction: observations, raw agent events, server sessions, and generation jobs. The project shell (config/membership) is kept so the team can keep using it. Returns per-tablecounts. Returns404if the project doesn’t belong to the team. Audited asproject.memory_purged.
Deletion is team-scoped at the SQL layer, so a key can only ever erase its own
team’s data — a cross-team or nonexistent
projectId returns 404 rather than a
misleading success.Event generation semantics
Ingestion (POST /v1/events) accepts two query flags that control observation
generation:
generate=false— write the event but do not enqueue a generation job.wait=true— return thegenerationJobdescriptor so callers can pollGET /v1/jobs/:idfor completion.
wait=true, the response includes the new event row plus a best-effort
generationJob field. With wait=true, that field is always populated (or null
only when generation was explicitly disabled). The actual provider call happens in
the separate BullMQ worker (claude-mem server worker start) — the HTTP path
never blocks on a provider response.
Generation concurrency
By default the generation worker processes one job at a time in each of its two lanes:event (per-event observations) and summary (session summaries). If
events arrive faster than one job at a time can process them, jobs still complete
but the backlog grows, and observations land hours after the work they describe.
Set CLAUDE_MEM_SERVER_GENERATION_CONCURRENCY=N on the generation worker to run up
to N jobs in parallel per lane. Both lanes use the value, so one worker process
can have up to 2N provider calls in flight. Size N against your provider’s rate
limit. Values above 64 are clamped to 64, and invalid values are ignored with a
warning.
If a worker stops making progress, stale processing locks can be reclaimed for
another attempt. Each attempt may persist observations or report a failure only
while it still owns the Postgres processing lock. A response from a reclaimed
attempt is discarded so it cannot complete or fail the replacement attempt.
This ownership check also applies to session summaries and does not hold a
database lock during the provider call. Recovery may still require another paid
provider call; it does not guarantee exactly-once provider execution.
Custom generation provider (advanced)
The generation worker ships withclaude, gemini and openrouter providers, and
each one reads a single set of credentials from its environment. If your deployment
needs something else, such as a different key per project, set
CLAUDE_MEM_SERVER_PROVIDER=custom and point CLAUDE_MEM_CUSTOM_PROVIDER_MODULE
at a JavaScript module. The module must export createProvider(helpers), which
returns an object with a generate(context) method. helpers gives the module this
server’s prompt builder, its Anthropic client (ClaudeObservationProvider, which
accepts a baseUrl), its retryable error class, the resolved maxOutputTokens, and
an apiVersion (currently 1) that changes whenever any of those contracts
change.
Endpoint reference
All endpoints are mounted under/v1; legacy worker routes remain under /api.
What’s solid vs. what’s coming
Solid today: Postgres-backed multi-tenant storage, api-key auth with
read/write scopes, the
/v1/mcp recall link, opt-in rate limiting + quotas +
metering, and audited data deletion. All covered by the Postgres-gated e2e suite.Still being built (UX / devex): a web dashboard for the first-key bootstrap and
key management, self-serve onboarding, a billing/plan UI on top of the metering
primitives, and a smoother “connect Claude Code to my cloud memory” flow than
pasting a CLI command. These are the next focus — the primitives above are the
foundation they’ll sit on.
