Quickstart
Five steps: connect the MCP server, import a rule set, ask what holds and why, look at deviations, then supersede a rule and ask the same question again — the old rule is now visibly superseded, not gone.
Every REST example below uses two placeholders: $COMPASS_URL for the Core's
base URL (the contract's default is http://<host>:8080/api/v1) and
$COMPASS_KEY for an X-Compass-Key value. Substitute your own.
1. Connect the MCP server
Locally (stdio — Claude Code, Cursor), add an entry to .mcp.json (Claude
Code) or .cursor/mcp.json (Cursor):
{
"mcpServers": {
"compass": {
"command": "/path/to/mcp/.venv/bin/python",
"args": ["/path/to/mcp/server.py"],
"env": {
"COMPASS_CORE_URL": "http://localhost:18080",
"COMPASS_KEY_RO": "<read-only key>",
"COMPASS_KEY_RW": "<read-write key>"
}
}
}
}
Against a running container (streamable-http), point at the MCP endpoint
instead and send the key as a header — the process itself carries no key in
this mode:
{
"mcpServers": {
"compass": {
"url": "http://localhost:18082/mcp",
"transport": "http",
"headers": { "X-Compass-Key": "<key for this tenant>" }
}
}
}
See Connect Claude Code, Cursor, and Codex for the full picture, including Codex's TOML config and what changes under each transport.
2. Import a rule set
POST /import/claude-md is a deterministic, model-free scanner: a heading followed by a prose sentence that
starts with an imperative marker (Always, Never, Must, Immer, Kein,
Muss, …), or a bullet/numbered item whose text starts with one — each
becomes one proposed Sache. Nothing else does, and nothing is written yet:
curl -sS -X POST "$COMPASS_URL/import/claude-md" \
-H "X-Compass-Key: $COMPASS_KEY" \
-F 'text=### R1 — No secret in the log
**Enforcement:** binding.
Never log or report a secret.
'
{
"neu": [
{"sache": "never-log-or-report-a-secret",
"titel": "Never log or report a secret",
"frage": "Was gilt für 'Never log or report a secret'?",
"regel_text": "Never log or report a secret.", "quelle_zeile": 5}
],
"unveraendert": [],
"weicht_ab": [],
"unklar": [],
"art": "claude-md"
}
Note that only the sentence starting with the marker Never became a
proposal — the heading text (R1 — No secret in the log) and the
**Enforcement:** line above it did not, by themselves. Review the
proposal, then write it for real with POST /import/claude-md/apply — this
writes the Sache and a regel Aussage from regel_text in one call:
curl -sS -X POST "$COMPASS_URL/import/claude-md/apply" \
-H "X-Compass-Key: $COMPASS_KEY" -H "Content-Type: application/json" \
-d '{"items": [{"sache": "never-log-or-report-a-secret",
"titel": "Never log or report a secret",
"frage": "Was gilt für '"'"'Never log or report a secret'"'"'?",
"regel_text": "Never log or report a secret.", "quelle_zeile": 5}]}'
{"geschrieben": [{"sache": "never-log-or-report-a-secret", "aussage_id": 42}],
"unveraendert": [], "abgeloest": [], "konflikt": [], "quelle_ref_widerspruch": []}
Both the Sache and its regel Aussage exist now — keep the aussage_id
(42 here), step 5 needs it to supersede this rule. See
Import a rule set for the exact two patterns
the scanner recognizes and its honestly-named limits — it is much narrower
than full free-form documentation, and gives rule-numbered headings in the
### R<n> format no special treatment of their own.
3. Ask what holds — and why
Step 2 already wrote a rule for this Sache. Ask what holds:
curl -sS "$COMPASS_URL/gilt/never-log-or-report-a-secret" -H "X-Compass-Key: $COMPASS_KEY"
{"id": 42, "text": "Never log or report a secret.",
"gilt_seit": "2026-09-18T10:00:00Z", "ort": null, "pruefweg": null,
"beleg": [], "quelle": "CLAUDE.md:5", "persona_slug": null}
And why it holds:
curl -sS "$COMPASS_URL/warum/never-log-or-report-a-secret" -H "X-Compass-Key: $COMPASS_KEY"
GET /warum/{sache} returns the same rule together with its decision text,
history, and the last check result, if one is attached. The MCP tools
gilt(slug) and warum(slug) call exactly these two routes.
4. Look at deviations
A check (Prüfweg) can be attached to a rule and run periodically; a run
that doesn't match what the rule expects becomes an Abweichung
(deviation) with a determined cause — a real violation, a missing tool, or
unclear. List what currently deviates across the tenant:
curl -sS "$COMPASS_URL/abweichungen" -H "X-Compass-Key: $COMPASS_KEY"
Nothing shows up until a check has actually run and disagreed with its rule
— see Attach a check to wire one up. The MCP
tool is abweichungen(seit?).
5. Supersede the rule, ask again
Write a new statement with ersetzt pointing at the old Aussage's id
(42 from step 2) — the old one is automatically marked abgeloest
in the same request:
curl -sS -X POST "$COMPASS_URL/aussagen" \
-H "X-Compass-Key: $COMPASS_KEY" -H "Content-Type: application/json" \
-d '{"sache": "never-log-or-report-a-secret", "art": "regel",
"text": "Secret lengths may be named, values never.",
"herkunft": "entschieden", "angelegt_von": "quickstart", "ersetzt": 42}'
Ask the same question again:
curl -sS "$COMPASS_URL/gilt/never-log-or-report-a-secret" -H "X-Compass-Key: $COMPASS_KEY"
The text has changed. Fetch the Sache itself to see both statements at
once — the old one is not gone, it is filed under abgeloest:
curl -sS "$COMPASS_URL/sachen/never-log-or-report-a-secret" -H "X-Compass-Key: $COMPASS_KEY"
{
"slug": "never-log-or-report-a-secret", "...": "...",
"aussagen": {
"gilt": [{"id": 43, "text": "Secret lengths may be named, values never."}],
"abgeloest": [{"id": 42, "text": "Never log or report a secret.", "gilt_bis": "2026-09-18T10:05:00Z"}],
"offen": [], "hypothesen": []
}
}
Nothing was deleted, and trying to write a second regel for this Sache
without ersetzt would have failed with 409 — the database allows exactly
one current rule per Sache and subject. See
Set and supersede a rule for the
full mechanics, including what happens when a rule needs to end with no
successor at all.