Skip to content
Conduit

mcp server

ossuary

hosom/ossuarycommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "ossuary": {
      "command": "uvx",
      "args": [
        "ossuary"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 21, 2026

OSSUARY

Ossuary reads local LLM agent session transcripts, finds health issues in them, and builds clusters of those issues across the corpus, outputting an HTML report of the findings.

discover sessions
      ↓
adapters → normalized events (+ shape records)   [deterministic]
      ↓
outline, corpus-wide tool stats, redaction        [deterministic]
      ↓
── MCP ───────────────────────────────────────────────────────
      ↓
your agent reads, notices, records                [Claude Code / Copilot CLI]
      ↓
── MCP ───────────────────────────────────────────────────────
      ↓
issues + clusters, reconciled against the taxonomy
      ↓
Jinja2 → single-file HTML → open browser

Ossuary supplies everything except for the coding agent. To make sure that it functions in as many environments as possible, Ossuary runs as a plugin for Claude Code and Copilot with a paired local MCP.

Install

uv venv && uv pip install -e .
source .venv/bin/activate    # the bare `ossuary ...` commands below need this

Then install the plugin for whichever agent you use:

/plugin marketplace add hosom/ossuary          # Claude Code
/plugin install ossuary@ossuary
copilot plugin install hosom/ossuary:plugins/copilot/ossuary
export OSSUARY_PROJECT="$(pwd)"   # absolute path to this checkout
copilot

There is no API key to set. Ossuary runs no inference of its own — it hands your transcripts to the agent you are already talking to, which is already authenticated. See docs/plugins.md.

Copilot copies the plugin subdirectory without the Python project. Keep OSSUARY_PROJECT set when launching Copilot, and restart existing sessions after updating the plugin. For development, copilot --plugin-dir ./plugins/copilot/ossuary uses the surrounding checkout without the override.

Use

Ask your agent to look at your sessions:

Have a look at my recent Claude Code sessions and tell me what went wrong.

It reads the outline, follows what looks odd, checks the corpus statistics before calling anything abnormal, and records what it finds. When you want the findings written down it calls ossuary_write_run, and then:

ossuary report                   # renders .ossuary/run.json to a single HTML file

The Claude Code plugin ships the same two steps as commands:

/ossuary:investigate             # audit what is on disk, one subagent per session
/ossuary:report                  # render the findings that were written down

Nothing reaches disk until ossuary_write_run runs, so an investigation you abandon leaves no artifacts behind. Writing a run archives the previous one under .ossuary/runs/ rather than replacing it.

The same property has a sharp edge: findings live in the MCP server's memory and nowhere else, so a server that restarts mid-investigation comes back with an empty buffer and no way to know it ever held anything. Writing an empty run over one that found something is therefore refused — it would replace a finished investigation with a blank. Pass allow_empty to record a run that genuinely found nothing.

The CLI is the deterministic surface around that — nothing here calls a model:

ossuary sources [PATHS...] [--source claude-code|codex|copilot|pi]
ossuary outline <session-id|path>          # one session, by hand
ossuary report [--open/--no-open] [--out report.html]
ossuary taxonomy [--show/--clear]
ossuary export --out issues.jsonl
ossuary-mcp                                # the MCP server, for wiring up by hand

Tests

python -m pytest

Golden-file tests run over fixtures that include deliberately malformed lines, out-of-order tool pairing, an orphaned result, a payload capped at exactly 30000 bytes, an empty result with a 30-second duration, and a session that was rewound so that some of what is on disk is on no conversational path at all.

Taxonomy

Named clusters persist to .ossuary/taxonomy.json. Later runs assign to existing clusters where they fit and only propose genuinely new ones — which stops reports reshuffling between runs and makes "new issue types this run" a real signal rather than an artifact of the model choosing different words.

Issue discovery is the model's job, not a regex's. There are no heuristic detectors that look for tracebacks or known failure strings and call those "issues". A deterministic detector can only find what its author already knew about, which makes the tool a fancy grep. Deterministic code here measures and indexes; the agent interprets. The investigating agent is given no taxonomy of issue types for the same reason — a menu of expected failure modes turns discovery into recognition.

Nothing is truncated silently. Every path that shortens a payload routes through elide.py and inserts [[ossuary:elided N of M bytes]]. The invariant the agent relies on is the contrapositive: a payload with no marker ended that way on disk. Unlabelled truncation would manufacture the exact artifact the tool exists to detect. This is a correctness requirement, and tests/test_elide.py treats it as one.

The outline comes before anything else. The agent navigates from there with tools, but it has seen every event at low resolution before it chooses where to look. Recall does not depend on the model's curiosity. The outline is also where most anomalies actually surface — as rows rather than as text buried in a payload — which is why the plugin prompts insist on reading it in full first.

One session per context window. Auditing several sessions spawns one session-investigator per transcript rather than sweeping the corpus in a single context. Two outlines in one window means the second session is read by an agent already holding opinions about the first, and findings stop being independent. The per-session agent is granted the read tools and report_issue and nothing else — no way to reach a second transcript, no way to end the run — and tests/test_plugins.py asserts that, so widening it fails the suite instead of quietly costing recall. That check normalises the mcp__<server>__ prefix away before comparing, so it constrains which tools an agent may hold but not whether the names resolve under the namespace the host actually uses: a plugin server the host exposes as mcp__plugin_ossuary_ossuary__* will not match an agent asking for mcp__ossuary__*, and the suite passes either way while every spawn gets an empty toolset. Copilot's grants are additionally checked for its ossuary/<tool-name> namespace, not just for the underlying tool names. See docs/plugins.md.

Adapters parse like archaeologists, not validators. No line is ever rejected. A malformed line becomes a degraded event carrying its raw text and the parse error, and parsing continues. Whatever is on disk is the only record that will ever exist of that session. See docs/formats.md.

Ossuary does not bring its own model. The hard part here was never calling one: it was turning five incompatible transcript formats into a single event model, measuring payloads without editorialising, computing the corpus-wide statistics no single session can show, and never shortening anything without saying so. A coding agent already has a model, a context window and a turn loop. It needs the transcripts laid out properly, not a second agent bolted on beside it. So the whole inference layer is somebody else's — which is also why there is no API key, no provider SDK, and no question about whose subscription is being spent. See docs/plugins.md.

The method lives in the plugin prompts, not in Python. What to look for, how to read the instrumentation, and what counts as an issue are in SKILL.md and *.agent.md, where they are readable and editable by the person running the investigation. Nothing in the code decides what a problem looks like.

Shape records

Every tool_result carries measurements, not verdicts:

FieldWhy
byte_lengthRepeated identical lengths across calls are visible in a column
is_round_numberExactly 30000 bytes is a cap, not a coincidence
terminates_cleanlyEnds at a boundary, or was cut mid-flow
duration_ms + duration_sourceWhether the CLI reported it or we derived it
exit_codeOnly when genuinely recorded — never synthesised from an error flag
content_hashByte-identical repeated results
is_empty, has_error_field

Exit code 0, an empty body, and a 30-second duration together are a timeout swallowed by the harness. No amount of reading the payload shows that as clearly as those three fields side by side.

Corpus-wide statistics do the part no single session can: a tool that returns exactly 30000 bytes on 40% of calls is unremarkable once and damning across two hundred sessions. Nothing inside one session shows it, so ossuary_tool_stats computes it across the corpus and hands it over on request.

Privacy

Transcripts contain source code, file contents, and not infrequently credentials. A redaction pass runs before anything leaves the MCP boundary — common secret shapes, key-shaped strings, and the literal values of your own credential-ish environment variables. Placeholders are padded toward the original length so shape records stay honest.

The host agent is a model too, and is treated as one. OSSUARY_NO_REDACT disables the pass, and then transcripts reach it verbatim.

Nothing else leaves the machine: Ossuary opens no sockets and makes no API calls. Whatever the transcripts are shown to is whatever you have already chosen to run.

The report

ossuary report renders one self-contained HTML file — inline CSS and JS, no CDN links, no network — so it survives being attached to a ticket. Sections: run summary, corpus trace, new issue types this run, clusters (severity-filtered, expandable to member issues and verbatim evidence), distribution by phase and tool, full tool statistics, and every session examined.

Brand

The mark is a niche — the arched recess a set of remains is filed into — holding three strips of sorted bone. The strips carry the meaning: cream for the record, verdigris for what held, rust for what failed. The arch springs from a circle of radius 18 centred at (32,27) on a 64-unit grid, drawn in a single 2.4-unit stroke, and reads down to 16px. Clear space equals the plinth height on all four sides.

ColourHexUsed for
Void#12100EEvery canvas. Never used for type.
Bone#E6DFD1Primary type, the mark, rules of consequence
Verdigris#4E8F7CPassed, held, resolved. Section numerals.
Rust#9C4A3CFailure, severity. Used sparingly.
Ash#5A554DSecondary type, skipped sessions, inert data
Brass#C8A24AWarnings — measurements that want a second look

Cinzel for display (inscriptional caps only, never a paragraph), Spectral for body, JetBrains Mono for every measurement, label, ID and code fragment. The report requests all three and falls back down stacks that keep their character, because a file that must work offline cannot fetch a webfont.

The recurring device is the dot strip: a run of circles where each dot is one session, one tool call, one attempt. It is the interior of the mark and it is the corpus trace in the report. Where a strip stops being one dot per unit it says so rather than quietly standing for the wrong number — the same rule as [[ossuary:elided]], applied to pixels.

Findings are stated flatly, past tense, with the number attached. The funerary register lives in the naming and the surfaces, never in the diagnosis: the engineer reading this is debugging at 6pm and wants the cause of death and the line number, not a metaphor.

Not in v1

No regex/heuristic issue detectors. No embeddings or vector store. No web service, daemon, or TUI. No database — JSON and JSONL on disk are sufficient at this scale. No model SDK, no API client, and no inference of Ossuary's own.

↑↓ navigate openesc close