Install
mcpd is one binary with storage compiled in, so installing it means putting that binary somewhere and starting it. Pick whichever route below matches how you already run things.
Docker
No release yet.
The image, the chart and the packages the commands on this site name are the intended distribution. None of them has been published. What works today is a build from source, which needs Rust and Node:
Build and run it
git clone https://github.com/mcpd/mcpd && cd mcpd && make build && ./target/release/mcpd demo The published image is the shortest route in, and the one the rest of the documentation assumes.
docker run -p 4317:4317 -v mcpd-data:/data -e MCPD_DATA=/data/mcpd.db ghcr.io/mcpd/mcpd
The volume matters. mcpd writes a SQLite database at the path
MCPD_DATA names, and a container without a volume takes your
traces with it when it stops. Naming the path explicitly costs one flag and
removes the question of where the file went.
Every flag has an environment variable, so anything you can pass on the
command line you can also set with -e. Pin a tag rather than
tracking latest in anything you care about. The image is built
for linux/amd64 and linux/arm64, and Docker pulls
the one that matches your machine.
Docker Compose
The same container, written down. This is the form to use when mcpd sits beside your application on one host.
services:
mcpd:
image: ghcr.io/mcpd/mcpd:latest
restart: unless-stopped
ports:
- "4317:4317"
volumes:
- mcpd-data:/data
environment:
MCPD_DATA: /data/mcpd.db
MCPD_PROJECT: default
# Set these before this port is reachable by anything but you.
# MCPD_INGEST_KEYS: key-for-your-agents
# MCPD_READ_KEYS: key-for-your-team
# MCPD_RETENTION_DAYS: 30
volumes:
mcpd-data: The three commented keys are the ones to fill in before this reaches a network anyone else is on. With no keys set, ingest and reads are both open, which is correct on your laptop and wrong everywhere else. Deploy covers what to set and why.
Kubernetes
Each release pushes the chart to the same registry as the image, as an OCI artefact, so there is no repository to add first. The chart refuses to install without an ingest key and a read key, so generate both first.
helm install mcpd oci://ghcr.io/mcpd/charts/mcpd -n mcpd --create-namespace --set keys.ingest=$INGEST_KEY --set keys.read=$READ_KEY
mcpd handles SIGTERM and shuts down gracefully, which is what
Kubernetes sends. It exposes /healthz for liveness and
/readyz for readiness, and the two are deliberately different:
liveness never touches storage, so a slow database cannot restart the pod
into a loop. Values, probes and the Deployment shape are on
Docker and Kubernetes.
A release binary
Each release attaches an archive for Linux on x86_64 and arm64, static and
built against musl so there is no libc to match, and for macOS on Apple
silicon and Intel. A SHA256SUMS file and a build provenance
attestation come with them. Unpack the archive for your machine and run
./mcpd; with no arguments it starts the daemon.
There is no cargo install mcpd. Nothing is published to
crates.io, and a crate built from the registry could not carry the
interface, which is built with npm and compiled into the binary. With a Rust
toolchain, build from source instead.
From source
Clone the repository and run the distribution build. It uses full link-time
optimisation and a single codegen unit, which is slower to compile and
produces the smaller, faster binary. Go through make rather
than cargo directly: the interface is compiled into the binary,
and make dist builds it first, which is the step a plain
cargo build on a fresh clone has no way to do. That step needs
Node and npm on the machine as well as the Rust toolchain.
git clone https://github.com/mcpd/mcpd && cd mcpd && make dist
The binary is written to target/dist/mcpd, or
target/release/mcpd if you took the faster route. Use
make build while you are working on mcpd itself and
make dist for anything you intend to run.
Where the data goes
--data is the path to the SQLite file, and it defaults to
./mcpd.db in the working directory. Parent directories are
created for you.
Passing :memory: keeps everything in memory and writes nothing
to disk. That is the right choice for a test suite, for a demo on someone
else's machine, and for any run whose prompts you would rather not leave
behind. The store disappears with the process, which is the point.
Leave nothing behind
mcpd --data :memory: mcpd demo fills the store with a realistic failing run and then
serves, so the interface has something in it the first time you open it.
Check what it will do before you start it
mcpd config prints the configuration this invocation resolves
to, then exits. Flags, environment variables and defaults all end up in one
listing, so you can see which one won without starting anything.
$ mcpd configlisten 0.0.0.0:4317storage sqlite at ./mcpd.dbconfig file nonelog format textproject defaultprojects noneretention foreveringest auth openread auth openwrite auth openadmin auth openexecute auth openworker auth opensignup offruns sqlite at ./mcpd.runs.dbrun settings lease_ttl_ms 30000, reclaim_window_ms 30000, stall_window_ms 300000, idempotency_window_ms 86400000, park_ttl_ms 86400000, max_depth 8, max_step_repeats 16identity offclient ip peer addresssampling keep everythingquota unlimitedredaction onredact key built in, which is public; set --redact-keycors same-origin onlytimeout 30s per requestlimits 32 MiB per request, 10000 signals per batchderived duration and token metricsaspects offmonitors noneobjectives nonejudge offforwarding off Verify the install
/healthz is the answer to "is it actually running". It never
reads from storage, so it stays fast and stays honest about the process
rather than the disk.
$ curl -s localhost:4317/healthz{"status":"ok","version":"0.1.0","spec_version":"1.0.0","uptime_seconds":8,"storage":"sqlite"}$ curl -s -o /dev/null -w "%{http_code}\n" localhost:4317/readyz200 spec_version is the data-model version this build implements. A
client can read it and refuse to talk to a server it does not understand,
which is better than sending a batch that will be rejected field by field.
/readyz is the other half. It pings storage and answers
503 when the store is not reachable, so a load balancer stops
sending reads to an instance that cannot serve them. Use
/healthz for liveness and /readyz for readiness,
never the same one for both.
Then open localhost:4317 and you are looking at the interface.
Next
- Send it a trace from your own agent: Quickstart.
- Learn the words the rest of the documentation uses: Concepts.
- Every flag and environment variable in one table: Configuration.
- Before anyone else can reach the port: Docker and Kubernetes.