Skip to content

MCP tools

selvedge-server exposes eight MCP tools. Seven are read-only and idempotent; one (log_change) is the writer. Every tool ships with full per-parameter descriptions, an output schema, and tool-level annotations — so MCP-aware agents and directories can gate, surface, and pick between them appropriately.

ToolPurposeRead/WriteIdempotent
log_changeRecord a change eventWrite (append)No (each call mints a new event)
diffHistory for an entity / prefixReadYes
blameMost recent change + contextReadYes
historyFiltered history across entitiesReadYes
changesetAll events under a slugReadYes
searchFull-text searchReadYes
prior_attemptsPrior attempts on an entity + inferred outcomeReadYes
stale_decisionsDated decisions that are due for a revisit and still activeReadYes

None are openWorldHint: true. None touch the network.


Record a change event. The only writer.

NameTypeDescription
entity_pathstring (required)The thing that changed: users.email, src/auth.py::login, env/STRIPE_SECRET_KEY, deps/stripe. See entity paths.
entity_typestringcolumn / table / file / function / class / endpoint / dependency / env_var / index / schema / config / other. Coerced to "other" if unrecognized.
change_typestring (required)add / remove / modify / rename / retype / create / delete / index_add / index_remove / migrate. Validated against the enum — typos are rejected.
diffstringThe diff text. Optional but recommended.
reasoningstringThe why. Captured from the agent’s own context. Run through the quality validator — empty / too-short / generic-placeholder values produce warnings (advisory).
agentstringThe calling agent name (claude-code, cursor, copilot, etc.).
session_idstringOptional session correlation ID.
git_commitstringCommit hash. Usually unset at log time and backfilled by the post-commit hook.
projectstringProject name. Defaults to the project root’s basename.
changeset_idstringA slug grouping related changes under one feature/task. Indexed.
rename_fromstringThe entity’s previous path, for renames. Set it with change_type="rename" and put the new path in entity_path. Selvedge then writes the dual-event pattern — a rename on the old path and a create on the new path with metadata.renamed_from set — so blame / diff / prior_attempts on the new path keep the history. A rename_from without change_type="rename" is rejected. Rename is a parameter, not a separate tool.
revisit_afterstringA revisit date for the decision — an ISO-8601 date (2026-12-01) or a relative offset from the event’s timestamp (90d, 6mo), normalized with the same grammar as --since. Consumed by stale_decisions / selvedge stale. New in v0.3.8.

entity_path is canonicalized on write (strip leading ./, collapse //, normalize separators to /, trim — case preserved on purpose) at a single storage chokepoint shared by the MCP and CLI write paths, so src/auth.py::login and ./src/auth.py::login resolve to the same entity. entity_path is also shape-checked per entity_type (e.g. a function path without ::) — a soft warning in the warnings array, never a rejection.

{
"id": "uuid",
"timestamp": "2026-04-22T15:31:02Z",
"status": "ok" | "error",
"error": "",
"warnings": ["reasoning too short", "..."]
}

Every key is always populated — empty string / empty list when not applicable. This keeps the outputSchema clean and lets agents type-check without branching.


History for an entity or entity prefix.

NameTypeDescription
entity_pathstring (required)Exact match (users.email) or prefix (users returns all users.*).
limitintDefault 20.
[
{ "id": "...", "timestamp": "...", "entity_path": "...", "change_type": "...",
"diff": "...", "reasoning": "...", "agent": "...", "git_commit": "...",
"project": "...", "changeset_id": "..." },
...
]

Newest-first (abbreviated — each event also carries entity_type, session_id). LIKE queries properly escape _, %, and \.


Most recent change + context for an exact entity. The query everyone runs first.

NameTypeDescription
entity_pathstring (required)Exact match only — no prefix expansion (use diff for that).
{
"id": "...",
"timestamp": "...",
"entity_path": "...",
"entity_type": "...",
"change_type": "...",
"diff": "...",
"reasoning": "...",
"agent": "...",
"session_id": "...",
"git_commit": "...",
"project": "...",
"changeset_id": "...",
"metadata": {},
"revisit_after": "",
"expires_when": "",
"error": ""
}

On miss (no history found), every field is empty and error carries the “no history found” message. The protocol-level isError is false — empty history isn’t a protocol failure; the in-payload error key is the documented signal.


Filtered history across all entities.

NameTypeDescription
sincestring15m / 24h / 7d / 5mo / 1y / ISO 8601. Unparseable → error.
entity_pathstringExact or prefix.
projectstringExact match.
changeset_idstringExact match.
limitintDefault 50.

Array of event objects, newest-first. Same shape as diff (abbreviated above — each event also carries entity_type, session_id).


All events grouped under a named feature/task slug.

NameTypeDescription
changeset_idstring (required)The slug. Returns events oldest-first to reconstruct chronology.

Array of event objects, oldest-first. Empty array if the changeset has no events (returned with error: "..." per the same convention as blame).


Full-text search across reasoning + diff + entity_path.

NameTypeDescription
querystring (required)Free-text query. Wildcards _ and % are escaped — they match literally, not as SQL LIKE metacharacters.
limitintDefault 20.

Array of event objects, newest-first. Match score is implicit (newest first within matches); a future version may add --score ranking.


Prior change attempts on an entity, each with an inferred outcome. Call this before editing an entity — if the same change was tried before and reverted, you get the prior reasoning and why it was rejected, so you can change your plan instead of repeating it. New in v0.3.7; this is Selvedge’s wedge.

NameTypeDescription
entity_pathstringThe entity you’re about to change. Exact + prefix match (users also covers users.email). Provide this or description.
descriptionstringFree-text description, when you don’t have an exact path. Matched as a substring against prior reasoning / diffs / paths. entity_path wins if both are given.
min_confidencestringproximity_high (default) returns only the clear “tried then reverted” cases; proximity_low widens to the noisy tail (still-active changes, far-apart reverts).
window_minutesintProximity window for the add→remove revert heuristic. Within it ⇒ proximity_high, beyond ⇒ proximity_low. Default 10080 (7 days).
limitintDefault 20.

Array of event objects (newest-first), each with three extra fields:

[
{ "...event fields...": "...",
"outcome": "reverted" | "active",
"confidence": "proximity_high" | "proximity_low",
"outcome_reasoning": "why it was reverted, or \"\" while still active" }
]

Outcome is inferred from add→remove proximity — v0.3.7 has no explicit reject / revert change types yet (those arrive in v0.3.11). The output is templated and deterministic — no LLM call — and the tool is pull-only: it never writes and never pushes; you decide when to ask.

Conservative by design. min_confidence defaults to proximity_high, so an empty list — nothing clearly tried-and-rejected — is the normal, preferred answer over a speculative false positive. You get one shot at the agent’s trust budget.


Dated decisions that have come due for a revisit — but only the ones whose entity is still in active use. A decision logged with a revisit_after surfaces here once that date has passed and the entity is still live, so an old-but-correct decision nobody touches never nags. New in v0.3.8; the date-based half of active memory.

NameTypeDescription
entity_pathstringFilter to one entity (exact + prefix match). Omit to scan all dated decisions.
projectstringExact match.
agentstringExact match.
limitintDefault 20. Results are ordered most-overdue-first.

Array of event objects (most-overdue-first), each with four extra fields:

[
{ "...event fields...": "...",
"revisit_due": "2026-...Z",
"days_overdue": 12,
"active_use_signals": ["queried"],
"stale_reason": "past its revisit date and still active — the entity was queried (blame/diff/prior_attempts) after the decision." }
]

Active-use weighting — pure age never surfaces. A decision only comes back if its entity is still live: it was queried (blame / diff / prior_attempts) at or after the decision was logged, or its changeset_id saw later sibling activity. active_use_signals lists which signals fired. A dated decision nobody has touched is filtered out — that’s the noise defense against old-but-correct decisions. The output is templated and deterministic — no LLM call — and the tool is read-only.


Every tool advertises:

log_change readOnly=false destructive=false idempotent=false openWorld=false
diff readOnly=true destructive=false idempotent=true openWorld=false
blame readOnly=true destructive=false idempotent=true openWorld=false
history readOnly=true destructive=false idempotent=true openWorld=false
changeset readOnly=true destructive=false idempotent=true openWorld=false
search readOnly=true destructive=false idempotent=true openWorld=false
prior_attempts readOnly=true destructive=false idempotent=true openWorld=false
stale_decisions readOnly=true destructive=false idempotent=true openWorld=false

log_change is append-onlydestructive: false even though it writes — but not idempotent, since each call mints a new event with a fresh UUID and timestamp.


Each parameter on every tool ships with a description in inputSchema.properties, populated via Annotated[T, Field(description=...)] on the function signature. Agents picking which tool to call read these directly at tool-call time, so they’re a DX surface, not just directory metadata.

This was a major v0.3.3 fix — earlier versions left the rich descriptions in the function body where agents couldn’t see them. Coverage is 100% — every parameter on all eight tools.

  • Entity paths → — what counts as an entity, how prefix matching works.
  • CLI reference → — the same data, queried from the command line.
  • Comparison → — Selvedge’s MCP-tools approach vs. the git-hook + LLM-inference approach.