Getting Started
This chapter covers installing Dr Strange — from a released binary or from source — initializing a database, issuing queries from the command line, and running the server, both locally and as a container image.
Prerequisites
Installing a released binary requires nothing beyond curl (or PowerShell on
Windows). The remaining prerequisites apply only to the other routes:
- Building from source: a current Rust toolchain (stable channel), installed via rustup.
- The web dashboard: bun to compile the single-page application, and optionally just as the task runner.
- The container workflow: Docker (Engine 24+; BuildKit is only needed if you build the image yourself).
The build links TLS through rustls/ring; no OpenSSL toolchain is required.
Installing a released binary
Every tagged release publishes binaries for Linux, macOS, and Windows. The
installer selects the archive matching the host platform, verifies its published
SHA-256, and places the binary on the PATH. Two binaries are available: the
command-line tool and server, drsg, and the MCP server for LLM agents,
drsg-mcp (Chapter 8).
Linux
# CLI and server — drsg
$ curl -fsSL https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.sh | sh
# MCP server — drsg-mcp
$ curl -fsSL https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.sh | sh -s -- --bin drsg-mcp
macOS — the same script; both Apple silicon and Intel are published.
# CLI and server — drsg
$ curl -fsSL https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.sh | sh
# MCP server — drsg-mcp
$ curl -fsSL https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.sh | sh -s -- --bin drsg-mcp
Windows, in PowerShell. The second form runs the script as a block because a
script piped into iex cannot receive arguments.
# CLI and server — drsg
PS> irm https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.ps1 | iex
# MCP server — drsg-mcp
PS> & ([scriptblock]::Create((irm https://raw.githubusercontent.com/wangyingsm/dr-strange/master/scripts/install.ps1))) -Bin drsg-mcp
Three options adjust the installation, each with an environment-variable equivalent for non-interactive use:
| Option (Windows) | Environment variable | Effect |
|---|---|---|
--bin drsg-mcp (-Bin) | DRSG_INSTALL_BIN | which binary — drsg (default), drsg-mcp, or all |
--version v1.1.0 (-Version) | DRSG_VERSION | pin a release instead of the latest |
--dir <path> (-Dir) | DRSG_INSTALL_DIR | destination directory |
The destination defaults to ~/.local/bin, and to
%LOCALAPPDATA%\Programs\drsg\bin on Windows, where the installer also adds the
directory to the user PATH. On Linux and macOS, add it to the shell profile if
it is not already present:
$ export PATH="$HOME/.local/bin:$PATH"
Upgrading
drsg update resolves the newest release the way the installer does — through
the redirect on releases/latest, rather than the API, which is rate-limited
for unauthenticated callers — and compares it with the running build. When
there is nothing to do it says so and stops:
$ drsg update
drsg 2.27.1 is the latest release — nothing to do
When there is, it prints the command it is about to run and then becomes it: the process is replaced by the same installer a first install runs, so the exit status is the installer’s own and there is no parent left waiting in a binary that has just been overwritten.
$ drsg update
drsg 2.27.0 -> 2.27.1
$ curl -fsSL https://raw.githubusercontent.com/wangyingsm/dr-strange/v2.27.1/scripts/install.sh | sh -s -- --bin 'drsg' --version 'v2.27.1' --dir '/home/me/.local/bin'
Dr Strange v2.27.1 (x86_64-unknown-linux-gnu)
downloading dr-strange-v2.27.1-x86_64-unknown-linux-gnu.tar.gz
checksum verified
installed /home/me/.local/bin/drsg
Both halves cross the network, and both honour a proxy: the version check is a
drsg request (see Working behind an HTTP
proxy), and the installer it hands over to is
curl, which reads the same variables. On a network that needs a proxy the
version check is what fails first, so an update that cannot reach GitHub
never reaches the installer at all.
The installer it runs is the one tagged with the release being installed,
not the copy on master, and the release is passed along as --version: the
script that verifies the archive is the script reviewed and shipped with it,
and moving a branch cannot change what an upgrade executes. The archive’s
.sha256 sidecar is required by both installers — a missing, malformed or
mismatching checksum is a hard failure, as is having no sha256sum/shasum
to check with. --insecure-skip-checksum (-InsecureSkipChecksum in
PowerShell; DRSG_INSECURE_SKIP_CHECKSUM=1 for either, and for drsg update)
installs anyway, with a warning: it exists for a mirror that publishes no
sidecar, and its name says what it costs.
The destination is the directory the running binary is in, not the installer’s
default — an upgrade must replace the copy on the PATH, not add a newer one
somewhere else and leave the old one being run. --dir overrides it for a
drsg installed somewhere unwritable. A drsg-mcp in that same directory is
updated alongside drsg without being asked — the two binaries are one
release, and an agent host launching last release’s server against this
release’s drsg would have nothing to tell it so. --bin names exactly what
to update when that is not wanted: drsg, drsg-mcp, or all.
A build newer than the latest release — from source, or from a branch ahead
of the last tag — is told it is ahead and nothing is installed: update never
moves backwards. On Windows nothing is run at all, because the running
executable is locked against being overwritten; the command to paste into a
fresh terminal is printed instead.
The archives and their checksums may also be downloaded directly from the
releases page; the
installers are a convenience over the same assets, and both scripts live in
scripts/ for
inspection before use.
Building from source
Dr Strange is a Cargo workspace. Compile the command-line binary, drsg:
$ cargo build --release -p drsg
The artifact is target/release/drsg. The default build selects the native LSM
storage engine; the legacy redb backend remains available behind a feature flag.
The web dashboard is embedded into the binary at compile time by the web crate’s build script. To embed the compiled dashboard rather than a placeholder page, build the single-page application before the binary:
$ just web-build # bun install && vite build
$ cargo build --release -p drsg
The MCP server for LLM agents (Chapter 8) is a separate binary,
drsg-mcp:
$ cargo build --release -p dr-strange-mcp
The artifact is target/release/drsg-mcp. Place it on the PATH, or reference
it by absolute path in the host configuration.
On-disk layout
The --db argument selects the database path. Under the native backend the
database is a directory — the write-ahead log and the sorted SST files reside
within it — accompanied by two sidecar files that hold the search indexes:
graph.drsg/ database (WAL + SST files)
graph.drsg.hnsw vector-index sidecar
graph.drsg.bm25 keyword-index sidecar
The database is created on first access; there is no separate initialization step.
Creating a graph
Create a plane, then insert data. The openCypher subset compiles to the same logical plan the engine executes directly:
$ drsg --db graph.drsg plane create social
$ drsg --db graph.drsg cypher --plane social \
'CREATE (a:Person {name:"Ada"}),
(b:Person {name:"Alan"}),
(a)-[:KNOWS]->(b)'
Query the result:
$ drsg --db graph.drsg cypher --plane social \
'MATCH (p:Person)-[:KNOWS]->(q:Person) RETURN q'
Inspect the resulting shape and soft schema:
$ drsg --db graph.drsg stats
$ drsg --db graph.drsg catalog --plane social
Vector index and similarity search
Vectors are ordinary property values. Declare an index on a (label, property)
pair, then query it. Embedding a text query is performed server-side and
therefore requires a provider key in the process environment (for example,
OPENAI_API_KEY); a query against a literal vector requires no provider.
$ drsg --db graph.drsg index ensure Doc embedding --plane social
$ OPENAI_API_KEY=… drsg --db graph.drsg cypher --plane social \
'SEARCH (d:Doc) ON embedding NEAR "a friendly greeting" TOPK 5 RETURN d'
Because the results are graph nodes, traversal can continue from them — the GraphRAG pattern introduced in Chapter 1.
Running the server
$ drsg --db graph.drsg serve
This starts the JSON-RPC 2.0 API, the WebSocket change feed, and the embedded
dashboard, and reports the bound address (default 127.0.0.1:7700).
Authentication. With no token configured, only the same-origin browser UI is
authorized to call the API. To permit programmatic access from the SDKs or
curl, configure a shared token and present it as a bearer credential:
$ DRSG_TOKEN=please-change-me drsg --db graph.drsg serve
A token is required for any bind address other than loopback: drsg serve --addr 0.0.0.0:7700 without DRSG_TOKEN (or [server] token) refuses to
start, because off loopback the same-origin check is no protection at all. On
such an address the served dashboard does not carry the token either — the
browser asks for it once per tab (see the Web UI chapter) — and the dashboard’s
own origin must be named in allowed_origins. Send the token as
Authorization: Bearer <token> on HTTP and, preferably, on the WebSocket
upgrade as well; ?token= on /ws exists for browsers, which cannot set
headers there, and a query string is what proxies and browsers tend to log.
A peer that presents a wrong token more than five times is answered 429
with a Retry-After that doubles up to five minutes; a correct token clears
it.
Over the API a provider name is one of the presets or exactly the server’s
configured embed_provider; a base URL is accepted from the CLI and the
configuration file, never from a request.
Configuration file
Server, logging, and provider settings may be supplied through a TOML
configuration file instead of individual flags and environment variables. The
file is resolved from --config <path>, then $DRSG_CONFIG, then ./drsg.toml
if present. Unknown keys are rejected.
[server]
addr = "0.0.0.0:7700" # bind address (CLI --addr overrides)
token = "please-change-me" # shared API token (→ DRSG_TOKEN); prefer exporting it, see below
max_concurrent = 1024 # ceiling on in-flight requests
write_timeout_secs = 30 # how long a write waits for the single writer slot before failing retryably (0 waits forever)
query_timeout_secs = 60 # how long one request's queries may run before stopping the same way (0 runs to completion)
retain_commits = 20 # commits of history time-travel can reach; older versions are reclaimed (0 keeps all). Applies to every drsg command, not only serve
source_root = "/srv/myrepo" # source tree behind the grep/snippet agent tools (serve watch sets it from --dir)
mount_root = "/srv/repos" # the one directory `drsg mount` may open a database from (default: your home directory); a plane inside it is also readable by grep/snippet
allowed_origins = ["https://app.example.com"] # additional browser origins
# page_token = false # never write the token into the page (→ DRSG_PAGE_TOKEN); for a loopback bind behind a reverse proxy, see the Web UI chapter
# /mcp behind a hostname: DRSG_ALLOWED_HOSTS=memory.example.com (env; honoured only with a token — see the MCP chapter)
[server.tls] # present ⇒ serve HTTPS
cert = "/etc/drsg/cert.pem" # PEM certificate chain
key = "/etc/drsg/key.pem" # PEM private key
[logging]
dir = "/var/log/drsg" # directory for the rolling log file
[llm] # provider keys, exported to the environment
OPENAI_API_KEY = "sk-…"
DEEPSEEK_API_KEY = "…"
DASHSCOPE_API_KEY = "…"
DRSG_CHAT_OMIT = "temperature,max_tokens" # optional: chat fields to leave out, for a model that rejects them
[digest] # server-side AIgest tuning
concurrency = 8 # per-chunk extraction calls in flight
chunk_chars = 4000 # target chunk size
embed_provider = "openai" # embedding provider for search / write_nodes / watch re-vectorization
embed_model = "text-embedding-3-small" # its model (each provider has a default)
embed_key_env = "OPENAI_API_KEY" # env var holding its key
ignore_files = ["gitignore"] # whose ignore files decide what is source; [] honours neither
tracked_only = false # true reads only what git tracks
code_only = false # true reads only what a handler claims (no Markdown/PDF)
[plugins] # preprocessor sandbox tuning (all optional)
fuel = 200000000000 # instruction budget per sandbox call (0 disables)
memory_mb = 3072 # guest linear memory per call, MiB (wasm32 allows at most 4096)
deadline_secs = 300 # wall-clock ceiling per call, seconds (0 disables)
total_memory_mb = 6144 # linear memory all calls may hold together, MiB (0 = twice memory_mb)
hostcall_mb = 1024 # what one call may copy out of the sandbox, MiB (0 = wasmtime's 128)
[fetch] # URL ingestion (Chapter 3)
enabled = true # false refuses URL fetching outright
max_pages = 10 # ceiling on pages kept per crawl
max_depth = 3 # ceiling on link-following depth a request may ask for
concurrency = 4 # requests in flight
allow_private = [] # see below — normally left empty
[network] # where this binary's own requests go
proxy = "http://127.0.0.1:7897" # or socks5://…; overridden by ALL_PROXY / HTTPS_PROXY / HTTP_PROXY
no_proxy = ".internal, 192.168.1.10" # NO_PROXY-style bypass list; overridden by NO_PROXY
[fetch] changes the server’s network posture, and is worth reading before
enabling anything in it. With URL ingestion on — the default — a client can name
an address that the server then connects to. The server’s position on the
network is usually the more privileged one, so every non-routable address is
refused: loopback, RFC-1918 private space, link-local (169.254.0.0/16, where
cloud instance metadata services answer credentials), and the rest. The check is
made on the resolved address, not the hostname, and repeated at every
redirect hop; an IPv6 address that carries an IPv4 one inside it (v4-mapped,
6to4, Teredo, NAT64’s 64:ff9b::/96) is judged as that IPv4 address.
allow_private re-permits specific CIDR blocks — ["10.0.0.0/8"] to read an
intranet wiki — and is the one deliberate exception to that. It is not a switch
that turns the guard off, and a server reachable by untrusted clients should
leave it empty. To refuse URL fetching entirely, set enabled = false.
Working behind an HTTP proxy
[network] governs the requests drsg makes on your behalf: drsg plugin install, drsg update, drsg digest <url>, the LLM provider, and a replica’s
snapshot fetch. Set it in the file, or export any of ALL_PROXY, HTTPS_PROXY
or HTTP_PROXY (each also read lowercase). socks5:// works as well as
http://.
Three rules are worth knowing, because each one is a decision rather than an accident:
A URL a caller names is never proxied. /digest/fetch and plugin.install
over JSON-RPC take a URL from whoever is calling, and the address guard
described above is what stops that URL naming the server’s own loopback or a
cloud metadata endpoint. A proxy resolves the name and connects on the server’s
behalf, so the guard would have no address left to judge — it cannot be enforced
through a proxy, and drsg does not pretend otherwise. Those requests stay direct
and guarded. drsg serve says so once at startup when a proxy is configured.
Loopback is never proxied, whatever is configured. A local model server —
the ollama preset is http://localhost:11434/v1 — is not what anyone setting
https_proxy for the outside world meant to redirect. Any other address obeys
the configuration; only localhost and the loopback ranges are automatic.
The environment wins. A variable that is set overrides the file, so a single command can go through a different proxy without editing anything:
https_proxy=http://127.0.0.1:1080 drsg plugin install ts # this run only
https_proxy= drsg plugin install ts # set but empty: go direct
no_proxy takes a NO_PROXY-style list, comma- or space-separated: an exact
host, a .suffix or *.suffix wildcard, an IP literal, an entry with a port
(example.com:8080), or * for everything. CIDR blocks are not supported.
NO_PROXY in the environment replaces the file’s list rather than adding to
it.
Precedence is fixed: an environment variable already set in the process always
takes precedence over the corresponding file value, and the --addr flag
overrides [server].addr. Providing [server.tls] switches the server to
HTTPS.
Secrets in the file become process environment. token and every [llm]
key are applied with setenv at startup, so they are inherited by every
process drsg starts — the detached serve watch that drsg init spawns,
plugin hosts, anything a hook runs — and are readable in /proc by the same
user. That is what makes the provider layer’s “look the key up by env-var
name” work, and it is why the shipped drsg.example.toml leaves token
commented out: a copied example with a well-known token is a server with no
token. Keep a drsg.toml holding secrets out of version control, or export
them in the launching environment and leave them out of the file.
retain_commits is read by every drsg command that opens the database,
not only serve: a store driven from the command line — import, cypher,
digest, vectorize — lives by the same window (and the same default of 20)
as it would under the server, so its history is reclaimed at compaction rather
than kept forever. The stdio drsg-mcp binary reads no config file but opens
with the same default; DRSG_RETAIN_COMMITS changes it there (Chapter
8).
Read-only replicas (serve --follow)
A second drsg serve, started with --follow, mirrors a running one
read-only — for scaling reads across a cluster of agents without funnelling
every query through one process:
$ drsg --db replica.drsg serve --addr 127.0.0.1:7701 \
--follow ws://master-host:7700 --follow-token please-change-me
Every write RPC is refused regardless of token. On startup — and again after
any disconnect — the replica pulls a full, consistent snapshot from the
master’s /snapshot endpoint, then tails its /ws/wal for new commits;
every reconnect resyncs from scratch (arch/01 §9), so --db must name an
empty directory or one this same replica already owns (a .drsg-follower
marker records that) — anything else is refused rather than silently wiped.
--follow-token (or DRSG_FOLLOW_TOKEN) is the credential presented to the
master; it is independent of this replica’s own DRSG_TOKEN, which still
gates its own downstream clients. Point --follow at wss:// rather than
ws:// for a master reachable over anything but loopback — the bearer token
otherwise travels in plaintext, the same requirement [server.tls] already
carries for inbound connections.
Container image
A multi-arch image (linux/amd64 and linux/arm64) is published to the GitHub
Container Registry. Pull and run it — no build required:
$ docker run -p 7700:7700 -v drsg-data:/data \
-e DRSG_TOKEN=please-change-me \
ghcr.io/wangyingsm/dr-strange:latest
docker run pulls the image on first use. Pin a release with a version tag —
ghcr.io/wangyingsm/dr-strange:1.0.2 — instead of :latest for reproducible
deployments. The runtime image binds to 0.0.0.0:7700 and stores the database on
the /data volume (the native backend database is a directory, which the volume
persists). Provider keys are supplied as environment variables.
DRSG_TOKEN is required in a container: the image binds 0.0.0.0, and
drsg serve refuses a non-loopback bind without a token rather than serve the
API and the dashboard to whoever reaches the port (the container exits with the
message naming the variable). Every client presents that token — the SDKs and
curl as a bearer, the dashboard through its prompt. Opening the dashboard at
http://localhost:7700 on the Docker host works as is; a browser reaching the
container by any other name — http://graph.example:7700 — must have that origin
listed in DRSG_ALLOWED_ORIGINS (comma-separated, or [server] allowed_origins),
because off loopback the served page carries no token and an unlisted origin is
refused as cross-site.
For a persistent deployment, docker-compose.yml pulls the same image and defines
a named volume. It marks DRSG_TOKEN as required, so an unset token stops
docker compose up with a message instead of starting a container that exits;
DRSG_ALLOWED_ORIGINS is passed through when set:
$ DRSG_TOKEN=please-change-me docker compose up
$ DRSG_TOKEN=please-change-me DRSG_ALLOWED_ORIGINS=http://graph.example:7700 docker compose up
To build the image locally instead, the repository ships a multi-stage
Dockerfile that compiles the dashboard, embeds it in the binary, and produces a
minimal runtime image: docker build -t dr-strange:latest ..
Next steps
- Chapter 3 — AI Native: embeddings, hybrid retrieval, natural-language querying, and document ingestion.
- Chapter 4 — Query Language: the openCypher subset and the underlying logical plan.
- By interface: Chapter 6 — SDK (application code), Chapter 7 — Embedded CLI (operations), Chapter 8 — MCP (LLM agents).