Servers and multiple clients¶
Run one owner when you want a browser UI or several clients sharing a database. The CLI and Python library can also work directly while the file has no other owner.
Start and manage a local server¶
Auto-served and explicitly started daemons register themselves. Use the selected database's commands to manage them:
grag --db ~/.grag/myproj.lbdb start # launch in the background, frees the terminal
grag --db ~/.grag/myproj.lbdb restart # relaunch (picks up new code after an upgrade/edit)
grag --db ~/.grag/myproj.lbdb status # selected server status plus this user's registered servers
grag --db ~/.grag/myproj.lbdb stop # stop this database's server
grag stop --all # stop this user's managed grag servers
grag doctor # install checks, selected owner and client discovery
grag start binds a stable per-database port by default (pass --port to override, --no-mcp for UI+REST only) and inherits your environment, so GRAG_EMBED_PROVIDER=fastembed grag start carries the embedder into the daemon.
grag stop -a, grag stop -all, and grag stop --all are equivalent. New
daemons use a private authenticated shutdown channel so the API and embedded
database close cleanly on every platform. After upgrading from grag 0.4.0, its
older pidfiles cannot be health/PID-verified; inspect the PIDs shown by
grag status, then use grag stop --all --force (or grag restart --force for
one target) once. Stop/refusal failures return a non-zero exit code.
Clean shutdown¶
Shutdown stops accepting work, marks queued jobs cancelled, and drains active
ingestion, code verification, requests, and embedding before closing the database.
Application draining has a 10-second grace period across the server's databases.
The HTTP server first allows up to 10 seconds for transport cleanup, so a stuck
connection cannot prevent application shutdown from beginning. grag stop waits
up to 30 seconds for process exit; this is not a forced termination deadline.
If accepted work outlasts the application grace period,
grag reports the pending jobs/worker and keeps the database open until that work
finishes; it does not close native connections underneath a live worker. A stuck
Python/native call can keep the process alive. Check the daemon log and retained
server registration when stop reports that the process has not exited. Cancelled
jobs need resubmission after restart; job records remain process-local.
Python callers can use service.close(timeout=...) and service.shutdown_status()
to inspect state, engine_closed, active operations, job outcomes, and the
embedding worker. Repeated close calls wait on the same drain. A closed service
or registry cannot be reused; create a new instance after shutdown completes.
Bind address and logs¶
Binding the REST/UI server beyond loopback requires GRAG_API_TOKEN; grag now
refuses an unauthenticated --host 0.0.0.0, --host ::, hostname, or LAN
address. restart preserves the recorded host, port, and MCP mode unless you
explicitly override them; a custom --mcp-path is preserved as well.
Daemon output lands in ~/.grag/logs/<db>-<id>.log. Check it for embedding failures
and startup crashes. In grag 0.10.0, status/doctor preserve stale
registration files and inspect cached index observations without triggering
refresh; explicit stop retains its stale-registration cleanup. See
installation discovery and client verification.
One database owner, multiple harnesses¶
Claude Code and Cursor can use the same graph through one shared server. Run
grag init --client claude and grag init --client cursor in the same checkout;
they reuse its mapping and port. The configured stdio proxies connect to the owner.
Windows harness restrictions may require starting that owner in a separate
terminal.
CLI remember, search, context (plus inspect and retire from 0.10.0),
ingest and ingest-code locate the selected database's registered owner and use
its API at the registered host. Set GRAG_API_TOKEN in the CLI environment when
that owner requires authentication. They open the file directly only
when no matching server is available. A direct stdio session that owns the file
must be closed before changing to init's shared setup; an ownership error explains
that step. A failed server request never falls back to opening a second writer.
After upgrading, restart the owner before using the new ingestion scope options.
There are two distinct ways to hold several projects, depending on whether they relate:
Related projects: one shared database¶
Ingest several repos into the same
database to query their separate Repo nodes together and link authored decisions
across projects. Automatic cross-repo edges depend on parser coverage; Python,
JS/TS and Go static resolution does not infer arbitrary cross-service calls.
// cross-repo imports, inside one db
MATCH (r1:Repo)-[:CONTAINS_REPO_MODULE]->(a:Module)-[:IMPORTS]->(b:Module)<-[:CONTAINS_REPO_MODULE]-(r2:Repo)
WHERE r1.id <> r2.id RETURN a.id, b.id
Unrelated projects: separate databases¶
One file = one isolated universe (no shared entities, no cross-db queries), so a throwaway experiment never pollutes a real project's graph. This is the default local-first pattern: one .lbdb per project, per developer, each queryable locally with zero per-token retrieval cost. To serve many of them at once, opt into multi-db mode with --db-dir:
Database-scoped REST endpoints accept ?db=<name> or an x-grag-db: <name>
header (query param wins). /api/health reports the server/default database;
GET /api/dbs returns {"dbs": ["alpha","beta"], "default": "alpha"}
({"dbs": [], "default": null} in single-db mode). Without a selector the server
prefers the file matching db_path's name, else a lone .lbdb, else 400 with a
hint; an unknown name returns 404 listing available databases. Services open lazily.
Point MCP clients at that same server; each window sends its selected database
name through x-grag-db. Do not start a separate direct database owner for it.
Cursor / .cursor/mcp.json (per window, one header per project):
{
"mcpServers": {
"grag": {
"url": "http://127.0.0.1:8472/mcp",
"headers": { "x-grag-db": "project-a" }
}
}
}
The server is localhost-only by default, and db names are routing hints, not auth — resolution rejects absolute paths and ... Direct stdio (grag --db knowledge.lbdb mcp) opens the database itself and is suitable for a single client. Use init-generated auto-serve proxies for shared access.
HTTP access and security¶
grag has a shared bearer-token boundary, with no
per-user accounts or per-database permissions. Without a token, direct loopback
callers are trusted. A Host-header allow-list (loopbacks plus the bind host)
blocks DNS rebinding, and CORS grants no cross-origin access unless configured
with GRAG_CORS_ORIGINS; the same-origin UI needs none. Non-loopback serving
requires GRAG_API_TOKEN. When set, every public /api/* route except
/api/health and every MCP request requires Authorization: Bearer <token>.
The managed-daemon stop hook uses a separate token in its private registration.
On POSIX, database and WAL files use 0600, and newly created database directories
use 0700.
Remote / team deployment¶
grag's engine is embedded and single-writer, so a shared graph is one grag serve --with-mcp process that owns the .lbdb; nobody else opens the file. Every developer's editor connects to it over HTTPS and CI keeps the code graph fresh through the jobs API. deploy/ has a Dockerfile, compose file, systemd unit, backup and CI scripts, and a walkthrough.
# server (Docker; put a TLS proxy in front)
export GRAG_API_TOKEN="$(openssl rand -hex 32)"
docker compose -f deploy/docker-compose.yml up -d --build
# each client: writes .mcp.json with `grag mcp --server-url …` and references
# ${GRAG_API_TOKEN} from the environment (never stores it)
grag init --server-url https://grag.example.com
What the server does differently from a laptop:
- Remote proxy mode.
grag mcp --server-url URLbridges stdio to the remote server, never spawns a local daemon, and replays the MCP handshake after reconnecting. Repeated outages can exhaust retries; an interrupted tool call may need explicit retry or stored-state inspection. The proxy pins database identity and refuses a silent switch. Plain HTTP beyond loopback requiresGRAG_ALLOW_INSECURE_HTTP=1. - Background ingestion. Code parsing and relationship resolution happen before atomic graph publication. Incremental updates include content, parser and dependency changes; files are still read and hashed; grag 0.10.0 reuses unchanged parses within an owner. Queued ingestion lets the HTTP call return a job ID while other readers use committed data. Native resource contention and write-side work can still affect latency. By default, stored-node embeddings run on a separate worker; query embedding remains part of semantic search. Use
POST /api/jobs/ingest/codeor MCPingest_code(background=true), then poll the job. - Specs become a graph.
grag ingest --sections doc.md(MCP:ingest_docs) turns the heading hierarchy intoDocument → Sectionnodes (SUBSECTION_OF,NEXT_SECTION), chunks each section's body under it (Chunk -IN_SECTION-> Section), and links resolvable backtick-mentioned symbols in the code graph (MENTIONS_FUNCTION/MENTIONS_CLASS/MENTIONS_MODULE/MENTIONS_CONSTANT). EmptyIMPLEMENTS(Function→Section) /IMPLEMENTS_CLASStables are defined for an agent to fill with the semantic spec↔code links. - Online backup.
GET /api/export(CLI:grag export --url URL -o backup.jsonl) captures one committed state including history and retry receipts, then streams the completed snapshot. Writes pause during capture, not during download; the CLI validates completion before publication. Restore into a separate verified copy. Failed WAL replay requires offlinegrag recover; a server launch never silently chooses lossy recovery.