Skip to main content

MCP / coding-agent integration

horologium-mcp is a thin, stateless MCP adapter: every MCP tool maps 1:1 onto exactly one Product Truth read endpoint. It doesn't infer, cache, join, or resolve anything itself — every fact an agent sees through it (canonical ID, derivation, an Unresolved reason, an exact evidence citation) is precisely what the web dashboard sees, because both read the same API. It is read-only — there is no MCP tool for submitting proposals or writing anything to Product Truth.

Tool map

Fourteen tools, one per read endpoint:

MCP toolAPI call
get_workspaceGET /v1/workspace
list_entitiesGET /v1/entities
get_entityGET /v1/entities/{id}
get_entity_claimsGET /v1/entities/{id}/claims
get_entity_relationshipsGET /v1/entities/{id}/relationships
get_entity_evidenceGET /v1/entities/{id}/evidence
list_findingsGET /v1/findings
get_findingGET /v1/findings/{id}
get_evidenceGET /v1/evidence/{id}
list_deployment_targetsGET /v1/deployment-targets
get_deployment_target_stateGET /v1/deployment-targets/{key}/state
get_capability_timelineGET /v1/capabilities/{id}/timeline
list_property_defsGET /v1/property-defs
get_versionGET /v1/version

get_entity already bundles a capability's evidence, open findings, and per-target deployment reality in one response — that's the tool to reach for first when an agent needs "what do we know about this capability, and can we trust it?" Every entity-scoped tool accepts an at (RFC3339) parameter for asking what was true at a point in time, rather than needing a separate history tool for each one — see Product Shape and temporal history.

get_workspace (GET /v1/workspace, ADR-0016 D4) tells an agent what it's actually connected to: the workspace/product identity and a data_class field. data_class: "synthetic" means every fact returned by every other tool this session describes fictional demo data — call it once at the start of a session, especially the hosted-demo session below, to confirm you're not looking at anything real before you quote it back to anyone.

Configuration

Env varDefaultPurpose
HOROLOGIUM_API_BASE_URLhttp://127.0.0.1:8080Base URL of a running horologium-server

The adapter runs over stdio — the transport Claude Code and other MCP-compatible coding agents use to launch a local MCP server.

Adding it to a coding agent

Claude Code — the copy/pasteable path

The one-line setup, from the repo root, against a horologium-server you already have running at http://127.0.0.1:8080:

claude mcp add horologium -e HOROLOGIUM_API_BASE_URL=http://127.0.0.1:8080 \
-- uv run --project python/horologium-mcp horologium-mcp

claude mcp add <name> -e KEY=VALUE -- <command> [args...] registers a stdio server — the transport this adapter speaks — and writes the entry into your local Claude Code MCP config (-s local, the default; pass -s project to check the entry into .mcp.json for a team instead). Verify the connection came up before asking anything:

claude mcp list
# horologium: uv run --project python/horologium-mcp horologium-mcp - ✓ Connected

Removing it later: claude mcp remove horologium.

The equivalent .mcp.json (for a team, or a non-Claude-Code client)

If you'd rather check the config into the repo (claude mcp add -s project does this for you), or you're wiring up a different MCP-capable client, this is the same server as a raw config block — add it to your project's .mcp.json (create one at the repo root if you don't already have one — don't overwrite an existing one):

{
"mcpServers": {
"horologium": {
"command": "uv",
"args": ["run", "--project", "python/horologium-mcp", "horologium-mcp"],
"env": {
"HOROLOGIUM_API_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}

Against a self-hosted instance running under Docker Compose

{
"mcpServers": {
"horologium": {
"command": "docker",
"args": ["compose", "run", "--rm", "-T", "horologium-mcp"]
}
}
}

This connects to the same self-host instance over the internal Compose network — the same Product Truth the dashboard shows, no separate data path.

Against the hosted demo

Safe, synthetic-only configuration:

The hosted demo (workspace demo, product acme-store, data_class: "synthetic" — ADR-0016 D7) is a fictional company's Product Truth: no real repository, customer, or credential is ever behind it. Point either setup above at its origin instead of a local server, and nothing else changes — the MCP adapter has no separate "demo mode", it's the same stateless HTTP client pointed at a different HOROLOGIUM_API_BASE_URL:

claude mcp add horologium-demo \
-e HOROLOGIUM_API_BASE_URL=https://horologium-demo-551243130607.us-central1.run.app \
-- uv run --project python/horologium-mcp horologium-mcp

or the .mcp.json equivalent:

{
"mcpServers": {
"horologium-demo": {
"command": "uv",
"args": ["run", "--project", "python/horologium-mcp", "horologium-mcp"],
"env": {
"HOROLOGIUM_API_BASE_URL": "https://horologium-demo-551243130607.us-central1.run.app"
}
}
}
}

No credentials are required — the demo has none of its own (see Security, privacy and data boundaries), and it is read-only (HOROLOGIUM_READ_ONLY=true, ADR-0016 D7), so nothing an agent does through this server can write anything back. Ask get_workspace first — it should report "data_class": "synthetic" — to confirm the agent knows it's looking at demo data before it quotes anything back to you. This is the fastest way to try MCP integration before setting up a self-host instance — see the hosted demo walkthrough for what data it exposes.

Full public-hosted verification (a fresh machine, zero prior setup, connecting to the public URL above with nothing self-hosted) depends on the Alpha hosted deployment itself landing (HORO-524) and the distribution policy it depends on (HORO-540, deliberately still open) — the steps above are exactly what that flow will look like once both land. Every step in this document is verified today against a local horologium-server instance seeded with the same golden fixture the hosted demo serves; see "Verifying it end to end" below.

Portability to other MCP-capable clients

horologium-mcp speaks standard MCP over stdio with no Claude-specific behavior — the server, its tool schemas, and its transport are identical regardless of which client launches it. Any MCP-capable client that can launch a local stdio server and pass it environment variables (Codex CLI, Cursor, and other MCP-compatible IDEs generally support this) should be able to run it, using that client's own config format with the same command (uv), args (run --project python/horologium-mcp horologium-mcp), and env (HOROLOGIUM_API_BASE_URL) values shown above — consult that client's own MCP documentation for its exact config file shape, since that varies by client and this repository only verifies Claude Code directly (see "Verifying it end to end" below). Clients that only speak HTTP/SSE transports are not supported today — this adapter ships stdio only (see python/horologium-mcp/src/horologium_mcp/server.py's main()); that is a genuine constraint, not a documentation gap, and is unaffected by this ticket.

What to ask

Once connected, these are the high-value questions this integration exists to answer — each maps to a small tool sequence, never a single opaque "summarize everything" call:

  • "What's actually running in production right now?"list_deployment_targets, then get_deployment_target_state for the target with is_primary_production: true. Against the golden fixture this reports production on 1.8.0 while beta is already on 2.0.0main != production is a fact this tool surfaces directly, not something the agent has to infer from a git log.
  • "What does Product Truth say about Refund?"list_entities with q="Refund" to resolve the name to a canonical entity_id, then get_entity for its claims, evidence, open findings, and per-target deployment reality in one response.
  • "Which sources conflict?"list_findings (optionally filtered kind="documentation_conflict" or kind="claim_conflict"), then get_finding for one finding's basis_ids (the disagreeing claims — ontology §6). Match those claim IDs against get_entity_claims's result to get each claim's own evidence_ids, then get_evidence per ID to show the two disagreeing citations side by side.
  • "Show deployment/history/evidence for this feature."get_capability_timeline for the transition events (not sampled snapshots), get_entity_evidence for its citations, and get_deployment_target_state per target for current reality — three separate tool calls an agent composes itself; the MCP layer does not join them into one response (ADR-0009).

A fuller example: "What's the current refund-window capability's status, and is it live on production?" The agent calls list_entities to resolve the capability, then get_entity for its evidence, open findings, and per-target deployment reality — the same documentation_conflict and released_not_live findings described in Integrity states and the evidence model, each with a citation the agent (or you) can follow to the exact source.

Verifying it end to end

Two layers of automated verification exist in the repository, both proving the same invariant — an agent sees exactly what Web/API see, never a second, MCP-specific interpretation:

  • python/horologium-mcp/tests/contract/test_rest_parity.py (HORO-466): every tool, called through its real MCP tool-dispatch path, is asserted byte-for-byte equal to the matching REST call, against a real horologium-server seeded with the golden scenario (crates/horologium-fixtures's hyphenated seed-golden-fixture — see docs/testing.md for why that binary and not horologium-api's own underscored seed_golden_fixture). Covers the exact beta=2.0.0/production=1.8.0 "released but not live" scenario above.
  • python/horologium-mcp/tests/contract/test_high_value_queries.py (HORO-527): the four questions above, each encoded as its own test against the same live fixture server, asserting the specific facts an agent must see — production pinned below beta, the Refund capability's conflicting claims, a documentation-conflict finding's two citations, and a non-empty timeline plus evidence for the same capability — and a get_workspace test asserting the reported data_class matches the server's own configuration.

Run both with (see python/horologium-mcp/README.md's "Contract tests" section for the full Postgres/binary setup):

uv run pytest python/horologium-mcp/tests/contract

If you're verifying your own setup by hand instead: connect a client with claude mcp add as shown above, ask an agent a Product Truth question through MCP, then confirm the same answer (same canonical IDs, same citations) comes back from curl-ing the equivalent /v1/* endpoint directly.

What this proves, and what it doesn't: every command above runs against a horologium-server bound to 127.0.0.1 — a fresh Claude Code client connecting locally to that instance, exactly as documented above, is verified end to end. Connecting to the public hosted URL from a genuinely fresh machine with nothing self-hosted is not yet verified this way, because the public hosted deployment depends on HORO-524 (and the still-open HORO-540 distribution decision) landing first — see the note under "Against the hosted demo" above.