Vaultspec MCP serverLink to Vaultspec MCP server
The server exposes Vaultspec document and workflow tools to Model Context Protocol (MCP) clients using JSON-RPC over standard input and output.
For the workflow and document types, see workflow stages.
SetupLink to Setup
Vaultspec keeps provider-neutral MCP definitions in .vaultspec/mcps/*.json. Executable
definitions, including builtins, require host-local operator approval before
installation or sync can enroll them. After installing the workspace, run
vaultspec-core spec mcps trust at a terminal, review the complete commands, arguments,
environment, and provider targets, and confirm. Then run
vaultspec-core spec mcps sync. Use the same provider and --scope for approval and
sync when selecting a specific target.
Approval lives in ~/.vaultspec/mcp-trust.json and binds the workspace, source path,
definition digest, rendered launch, provider, scope, and native target. Edits, pulls,
and changes in install mode require fresh approval. Noninteractive runs and --json
cannot grant consent; --force only controls overwrite/adoption. Unapproved definitions
are skipped with warnings while other resources still sync.
vaultspec-core spec mcps trust --revoke withdraws this workspace’s grants for future
enrollment; use vaultspec-core spec mcps uninstall to remove previously deployed
entries.
When top-level vaultspec-core sync --target DIR reads definitions from the current
workspace and writes them into another workspace, approve that same source and
destination pair with vaultspec-core spec mcps trust --source-from-cwd --target DIR.
Approval for the destination’s own definitions does not authorize commands from another
workspace.
Installation and vaultspec-core spec mcps sync render those definitions into each
selected supported MCP provider’s native configuration:
Provider |
Project scope |
Broader scope |
|---|---|---|
Claude |
|
Local or user enrollment in |
Codex |
|
User enrollment in |
Antigravity |
|
Broader scopes are unsupported |
Project scope is the safe default. Select user or local scope explicitly. Native host
files contain only host-valid configuration; vaultspec records project and local
ownership under ~/.vaultspec/mcp-ownership/<workspace-hash>/mcp-ownership.json (using
the canonical workspace path) and user ownership in ~/.vaultspec/mcp-ownership.json,
so unrelated host entries remain external. Use vaultspec-core install --skip mcp if
you manage enrollment yourself.
Ownership records bind entries to canonical target paths. Repository ownership sidecars
and project legacy ownership markers are ignored as authority. The 0.3.2 environment
migration removes the obsolete repository sidecar during explicit upgrades or
vaultspec-core migrations run, preserving MCP configurations and existing host-local
ownership. It never imports repository fingerprints into trusted state. After upgrading,
review and approve definitions, then use vaultspec-core spec mcps sync --force to
explicitly adopt existing entries; ordinary sync preserves them until adoption.
Configure your client to launch the server with the project root as its working directory, or set an explicit workspace.
Keep the generated python -P -m vaultspec_core.mcp_server.app invocation. On Windows,
running the vaultspec-core-mcp console executable can keep it locked while the client
is connected, blocking package updates that replace it. -P keeps the working directory
off sys.path, so a file in the workspace cannot be imported in place of the server
package or of anything it imports.
In tool mode, the generated entry in Claude’s .mcp.json and Antigravity’s
.agents/mcp_config.json uses this configuration:
File contents
{
"mcpServers": {
"vaultspec-core": {
"command": "uvx",
"args": [
"--from",
"vaultspec-core",
"python",
"-P",
"-m",
"vaultspec_core.mcp_server.app"
]
}
}
}
Codex takes the same invocation as TOML, inside a marked region the installer owns:
File contents
# <vaultspec type="mcps">
[mcp_servers."vaultspec-core"]
args = ["--from", "vaultspec-core", "python", "-P", "-m", "vaultspec_core.mcp_server.app"]
command = "uvx"
# </vaultspec>
The launch shape follows the install mode rather than being fixed: a project that
carries vaultspec-core as a dependency gets uv run --no-sync with no --from, because
the environment already has the package. Compare against these when you manage
enrollment yourself, and read the markers as ownership boundaries: edit inside them and
the next sync declines to touch the entry rather than repairing it.
Serving a read-only surfaceLink to Setup, Serving a read-only surface
Use --read-only to limit the tools the server advertises:
Command
vaultspec-core-mcp --read-only
This exposes only status, find, search, crossref, check, and discover to
connected clients. In this mode, check has no fix parameter, and crossref judges
one ADR and cannot write links; any other argument to either is refused rather than
ignored. search and crossref change nothing on disk, but when a hosted-search key is
configured they send vault text to the TypeSafe API, so read-only mode still carries
that data flow. See search and crossref.
Install modesLink to Setup, Install modes
For Core’s generated MCP entry, the install mode determines how the server starts:
Tool:
uvxuses an isolated tool environment and may download dependencies or create that environment at launch. See uv’s tool environments.Dev or dependency:
uv run --no-syncskips project environment synchronization. Prepare that environment using your project’suv syncprocedure before connecting.
See install mode selection for precedence and dependency declarations.
Convergence on upgradeLink to Setup, Convergence on upgrade
Approved launch entries that vaultspec wrote converge to the current standard
automatically. A managed entry whose bytes still match the fingerprint recorded when
vaultspec last wrote it is provably untouched, so vaultspec-core sync,
vaultspec-core spec mcps sync, and vaultspec-core install --upgrade all refresh it
in place - no --force required - and print exactly what changed: the entry name, the
old launch command, the new launch command, and why. A registered migration applies the
same refresh, so a launch rendered before the --no-sync guard existed (a bare uv run
shape) converges whatever its provisioned version - but it converges when a converging
command runs, not on any contact with the CLI. Those commands are
vaultspec-core install --upgrade, vaultspec-core migrations run,
vaultspec-core vault repair, vaultspec-core vault add,
vaultspec-core vault feature index, and the MCP create tool. Everything else, reads
included, observes the workspace as it finds it and reports any pending migrations as a
warning.
Every refresh also requires approval for the current definition and rendered launch.
When either changes, approve it with vaultspec-core spec mcps trust before convergence
can resume; ownership fingerprints and migrations do not grant consent.
Two kinds of entry never converge automatically. An entry you edited by hand (its bytes
no longer match the recorded fingerprint) is skipped with a warning and requires an
explicit vaultspec-core spec mcps sync --force, which overwrites your edit. An entry
recorded by a release that predates fingerprinting cannot be verified as untouched and
keeps the same --force-only behavior. External entries vaultspec never wrote are never
adopted or modified without --force. To opt out of enrollment management entirely,
provision with vaultspec-core install --skip mcp; a workspace without recorded
enrollment is never touched by the convergence migration.
vaultspec-core spec doctor additionally warns - without failing - about two states
vaultspec cannot converge itself: canonical hooks that cannot be refreshed because
prek.toml is present (transplant the entries manually), and a package-bundled seed
definition still in a static pre-mode shape (re-run that package’s installer with
--upgrade).
Select a mode with vaultspec-core install --mode tool,
vaultspec-core install --mode dependency, or vaultspec-core install --mode dev.
vaultspec consumes the package, module, and optional tool requirement while rendering;
that metadata never reaches native host configuration.
The chosen mode is recorded per package in the workspace’s committed workspace.json,
which keys each provisioned package to its own mode and version floor. A workspace that
provisions both vaultspec-core and a companion package can therefore declare each in a
different mode - one as a dependency, the other as a tool - without either overwriting
the other’s choice.
A workspace set up before install modes existed carries no recorded choice. Running
vaultspec-core install --upgrade infers the mode from how the workspace already runs -
a uv run-shaped setup that lists vaultspec-core as a dependency stays in dependency
mode, and anything else moves to tool mode - and records it, so the launch command stays
stable from then on.
Point the server at a different workspaceLink to Setup, Point the server at a different workspace
When the client’s working directory isn’t the workspace you want - for example in a
standalone setup outside an editor - set VAULTSPEC_TARGET_DIR in the canonical MCP
definition’s env map, then run vaultspec-core spec mcps sync. The provider adapter
renders normalized command, args, and env fields into each selected native target.
VAULTSPEC_TARGET_DIR is the environment equivalent of the CLI’s --target flag.
Your first callLink to Setup, Your first call
Claude reads project enrollment from .mcp.json. Codex reads .codex/config.toml for a
trusted project. Antigravity uses .agents/mcp_config.json. Reload or reconnect the
provider as required by that host. vaultspec writes enrollment only: it does not start
the server, grant trust, or bypass provider approval.
Call the status tool with no arguments. It returns a rollup report:
tool_schema_version, the workspace’s features with document counts and lifecycle
status, any plans in flight with their completion and next open step, and
hosted_search, whether search has a key configured. A populated report
confirms the server found the workspace.
Call find with no arguments. It returns the feature listing, for example
[{"name": "auth", "doc_count": 4, "weight": 7}, ...]. A non-empty listing confirms the
vault is readable. Follow up with find(feature=...) to fetch a feature’s documents,
each with a blob_hash ready for a later edit call.
EnvironmentLink to Environment
The MCP server uses Core’s shared settings order: invocation, process environment,
project store .vaultspec/.env for persistable settings, workspace-root .env for an
eligible credential, persisted configuration, derived and external defaults, then the
shipped default. See Settings resolution for the full
order and vocabulary.
Variable |
Default |
Controls |
|---|---|---|
|
discovered |
The workspace root containing |
|
|
Root log level when nothing else names one; the server’s output is a log, not a terminal a person is watching. |
|
unset (armed) |
A false word disables the stdio lifetime watchdog; an unrecognised word warns and stays armed. See Server lifetime. |
|
unset |
The key that enables hosted vault search for |
Those are the four VAULTSPEC_ variables the MCP server reads directly. See the
CLI reference for the full VAULTSPEC_ variable
family, and for the workspace .env credential rule that also governs the key above
when the server runs from the workspace’s own environment.
VerificationLink to Verification
Command
vaultspec-core spec mcps status
vaultspec-core spec mcps status codex --scope project
vaultspec-core spec mcps status --json
Status reports aggregate and per-provider configuration health. It identifies managed,
missing, drifted, stale managed, and external entries, plus warnings. It does not start
or probe an MCP process. The command exits 0 only when aggregate status is "ok".
If status isn’t "ok", inspect the missing, drifted, stale_managed, and
warnings fields, run vaultspec-core spec mcps sync to reconcile enrollment, then
re-run the status check. Project scope is the default; user or local enrollment must be
requested explicitly, and unsupported provider/scope combinations fail.
Run vaultspec-core spec doctor --json for a broader workspace diagnosis.
ToolsLink to Tools
Most tools cover the everyday path: status orients you in the workspace, find
locates documents, search answers a question with the passage that answers it,
create scaffolds new ones, edit makes body-prose changes, check validates and
repairs the vault, plan_progress marks steps complete, plan_edit authors step
content, and log appends a step’s rows to the plan’s execution ledger. discover and
invoke form a gateway that reaches every remaining CLI verb.
The table below is generated from the running server: each tool’s purpose is its own
handler’s summary and each annotation column its declared MCP hints. Run
vaultspec-core spec reference generate to refresh it; do not hand-edit between the
markers.
The server exposes 12 tools.
Tool |
Purpose |
Annotations |
|---|---|---|
|
Find vault documents or list features. |
read-only, idempotent |
|
Scaffold one or more vault documents from templates. |
non-destructive, not idempotent |
|
Apply one or more body-prose edits to vault documents. |
destructive, not idempotent |
|
Orient in a vaultspec project, project-wide or targeted. |
read-only, idempotent |
|
Run the vault health-check suite, optionally repairing. |
non-destructive, idempotent |
|
Answer a question from the vault, quoting the passage that answers. |
read-only, idempotent |
|
Find the ADRs a decision should cross-reference, within fixed bounds. |
non-destructive, idempotent |
|
Mark plan steps closed or open by canonical identifier. |
non-destructive, idempotent |
|
Author plan steps: add, insert, edit, or remove. |
destructive, not idempotent |
|
Append one Step’s rows to its plan’s execution ledger. |
non-destructive, idempotent |
|
Search the long-tail verb catalog and return ranked schemas. |
read-only, idempotent |
|
Execute one cataloged verb. A verb that runs and fails is still a successful call; its exit code and stderr arrive in the error payload. |
destructive, not idempotent |
Surface provenanceLink to Tools, Surface provenance
Which of the tools above are absent from the latest published release at the time this
handbook was generated. Computed from the surface recorded from that release’s published
distribution, which is written after publication through
vaultspec-core spec reference snapshot --record and never by hand.
Measured against 0.3.5, the latest published release when this reference was
generated: every tool above is in it.
Two bands of operations sit outside the tool surface.
The gateway reaches some operations, but a CLI command carries them by default:
synchronizing generated surfaces (vaultspec-core sync,
vaultspec-core spec <resource> sync) and restructuring a plan above the step level
(vaultspec-core vault plan tier promote, tier demote, and the wave, phase, and
epic intent verbs). Every invoke call carries the destructive-operation annotation,
so a connected host still asks you to confirm each one; the CLI skips that round-trip.
Other operations have no MCP path at all. Regenerating a feature index runs through
vaultspec-core vault feature index. For MCP configuration, list, add, and remove
inspect or mutate canonical definitions; status inspects native enrollment and
ownership health; sync reconciles definitions into selected targets; and uninstall
removes vaultspec-owned enrollment while preserving canonical definitions and external
host entries. Removing vaultspec from a workspace runs through
vaultspec-core uninstall.
TermsLink to Tools, Terms
Canonical identifier: the stable
W##,P##, orS##label a plan container keeps for its entire life. A retired identifier is never reused, so gaps in the sequence are expected. The display path you see, such asP01.S02, is computed for reading; the canonical identifier is the durable handle underneath it.Body prose versus CLI-owned structure: frontmatter, filenames, and plan-table structure belong to the CLI and its owning verbs. Body prose in a scaffolded document is the surface you edit by hand.
Feature index: an auto-generated document at
.vault/index/<feature>.index.mdthat lists every document belonging to a feature. It regenerates automatically when acreatebatch succeeds.Destructive-operation annotation: the MCP
destructiveHintmarked on a tool. A connected host reads this hint and asks you to confirm before it runs a call carrying it.
findLink to Tools, find
Find vault documents or list features. Read-only, idempotent.
With no arguments, find lists features: each row gives a feature’s name, document
count, and graph weight. Pass any filter (feature, type, date, or text) and
find switches to search mode, returning matching documents instead.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string or null |
|
Feature filter, without the |
|
list of record types or null |
|
Document-type filter: any of |
|
string or null |
|
Exact ISO-8601 date filter. Passing it switches |
|
string or null |
|
Case-insensitive substring over the document stem and feature tag. Composes with the other filters, and matches identifiers rather than body prose. Passing it switches |
|
|
|
In search mode, how much document text to inline in each row. This is an enum, not a boolean: passing |
|
boolean |
|
In feature-listing mode, enrich each row with |
|
integer, 1 to 100 |
|
Maximum number of rows to return. |
Behavior notes:
Feature-listing rows report
name,doc_count, andweight(a graph-ranking score).weightis0when the dependency graph can’t be built, and the row notes that the ranking is unavailable.Document-search rows report
name(the file stem),type,feature,date,path(relative to the vault),blob_hash(the git blob object ID (OID) of the document’s current bytes, ornullif the file can’t be read),resource_uri(afile://locator), andbodywhen you setbodytoexcerptorfull.resource_uriis a path for your host to read directly. The server registers no MCP resources, so there is nothing toresources/readand the URI is not a protocolresource_link. Setbodywhen you want the text in the response instead.In search mode,
limitis a global cap applied across all matched types combined, not a per-type cap. If an early type fills the cap, later types can be crowded out entirely. Callfindonce per type when you need a fair spread across types.findnever raises an error for filters that match nothing; an empty result comes back as an empty list. A type that is not a record type, such asadrs, is refused by schema validation instead of matching nothing.findandsearchshare theirfeature,date, andtypefilters, so each argument selects the same records on both tools.Both
findmodes enforce a 1 MiB per-file limit and a 128 MiB budget across document reads, including graph construction and repeated reads. Oversized documents fail the call before being read in full. The serialized response is capped at 128 KiB, including JSON escaping; reducelimitor useexcerptwhen full bodies exceed it. Limits raise a protocol error rather than returning partial documents or hashes of incomplete content.
Example feature-listing response:
File contents
[
{"name": "auth", "doc_count": 4, "weight": 7},
{"name": "search-api", "doc_count": 2, "weight": 3}
]
Example document-search response row:
File contents
{
"name": "2026-07-11-search-api-research",
"type": "research",
"feature": "search-api",
"date": "2026-07-11",
"path": ".vault/research/2026-07-11-search-api-research.md",
"blob_hash": "<git blob OID>",
"resource_uri": "file:///.../2026-07-11-search-api-research.md"
}
searchLink to Tools, search
Answer a question from the vault, quoting the passage that answers it. Read-only, idempotent, open-world.
search ranks vault records against a question in plain language and returns the best
matches first. Each hit quotes the record’s own text, the block judged to answer, with
the file lines it came from, so you can cite it or open the file at that span. The reply
also says whether anything in the vault answers the question at all.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string, 1 to 2,000 characters |
required |
The question. |
|
list of record types or null |
|
Record-type filter. With no |
|
string or null |
|
Feature filter, without the |
|
string or null |
|
Exact date filter, |
|
integer, 1 to 11 |
|
Maximum number of hits to return. |
The filters are applied in code before anything is sent, so no record they exclude
reaches the model. An empty or blank query, a query over 2,000 characters, a limit
outside 1 to 11, an unknown type, or index fails the whole call with a protocol error.
Enabling hosted search. Set VAULTSPEC_CORE_TYPESAFE_API_KEY in the server’s
environment, or provision it with
vaultspec-core install --env VAULTSPEC_CORE_TYPESAFE_API_KEY (add --upgrade for an
existing installation). Process values override the protected .vaultspec/.env store,
which works in all install modes; a blank value is unset in either and falls through.
The resolver refreshes local file changes without changing the server’s inherited
environment. If neither source supplies the variable, the server reads that one variable
from the workspace-root .env instead, but only when both hold: the server runs from
the workspace’s own environment (its Python interpreter lives inside the workspace, as a
project virtual environment does), and vaultspec-core’s resolved install mode for the
workspace is dependency or dev. An invocation through uvx or a globally installed
vaultspec-core (such as a uv tool, a pipx install, or a release binary) never reads the
workspace .env, so a cloned repository cannot supply the key. No other variable
enables search: TYPESAFE_API_KEY and vaultspec-rag’s own variables do not. With a key
set, every search sends the question and vault text to the TypeSafe API at
api.typesafe.ai. Setting the key is the consent to that data flow, and it applies in
read-only mode too. Without a key, nothing leaves the machine. The key never appears in
a response, a log, or an error. status reports only whether a key is configured and
where it was found.
Outcomes. The status field is one of three values:
ok: the vault was judged.answeredsays whether any record was judged to answer the question, andverdictwords it:answered,nothing_answers, ornone_read_answerswhen the provider would not read some records in full.answeredis a verdict on the whole vault only whenusage.unscoredis0: a record the provider would not read in full was not judged.hitsis empty only when every record was read.not_configured: no key is set and nothing was sent.unavailable: a key is set but the search failed.reasonnames the failure:credential_rejected,content_rejected,rate_limited,transport,deadline,invalid_response, orrequest_too_large.content_rejectedmeans the provider refused to read the question itself, or every record the ranking needed. No partial ranking is returned.
Next step. A not_configured or unavailable reply carries next_step, the search
to run instead, and remediation, one sentence giving the reason and that step.
next_step.kind is rag_search when the workspace provisions vaultspec-rag, and
command is a vaultspec-rag vault search over the requested record types. Otherwise
kind is listing, and command is vaultspec-core vault list, with the type when
one was requested; run it or find, then grep .vault/ for the passage. types lists
the record types the step covers, and <intent> in command stands for your question.
When the request carried feature or date, command keeps them as --feature and
--date. Provisioning is read from the workspace configuration, not checked for
liveness. The server never calls vaultspec-rag itself; you or your agent run the step.
search covers the vault only: use vaultspec-rag for code.
Hits. Each hit reports path, type, feature, date, title, score (the
ranking score), answers (the probability that the record states the answer),
premise_conflict (the probability that the record contradicts an assumption in the
question, reported on its own and never folded into score), blob_hash, and up to two
excerpts. excerpt is the block judged to answer, and supporting is a second block
when the answer spans two. A hit with no excerpt was ranked, but no single block was
chosen. Unlike find, a hit carries no resource_uri: path is relative to the
workspace, and blob_hash names the file version.
Excerpts. Each excerpt has these fields:
section: the heading path, outermost first, each heading separated by" > ".line_startandline_end: the lines oftextin the file version thatblob_hashnames.text: the file’s own text, never text a model wrote.truncated: whether the block goes on pastline_end.
An excerpt’s text is at most 700 bytes of UTF-8, or 300 for supporting, and the
section’s headings are bounded the same way. A longer block keeps its opening whole
lines, line_end is the last line kept, and truncated is true; the rest of the
block starts at line_end + 1. The one exception is a block whose first line alone is
over the limit: text is then the start of that line, and line_end equals
line_start.
Paging. A ranked reply carries returned, total, and truncated. The ranking is
computed fresh for each request, so there is no offset to resume from. When truncated
is true, raise limit (up to 11) or narrow the filters. usage reports the model
version, the request count, input tokens, elapsed milliseconds, and unscored, the
number of records the provider would not read in full: its content filter refused them,
or one of their passages exceeded the request size. A record refused only in part is
still ranked, on the text that was read, and is counted too. usage is absent when
nothing was sent. The reply never echoes the question back.
Example response:
File contents
{
"status": "ok",
"answered": true,
"verdict": "answered",
"hits": [
{
"path": ".vault/adr/2026-07-12-search-api-adr.md",
"type": "adr",
"feature": "search-api",
"date": "2026-07-12",
"title": "`search-api` adr: `page with opaque cursors`",
"score": 0.912,
"answers": 0.877,
"premise_conflict": 0.012,
"blob_hash": "<git blob OID>",
"excerpt": {
"section": "Decision",
"line_start": 41,
"line_end": 46,
"text": "Pages are addressed by an opaque cursor, never an offset...",
"truncated": false
}
}
],
"returned": 1,
"total": 6,
"truncated": true,
"usage": {
"model": "<API-reported model>",
"requests": 6,
"input_tokens": 41250,
"elapsed_ms": 1234,
"unscored": 0
}
}
Example response with no key configured, for a request with type ["adr"] in a
workspace that provisions vaultspec-rag:
File contents
{
"status": "not_configured",
"answered": false,
"hits": [],
"next_step": {
"kind": "rag_search",
"types": ["adr"],
"command": "vaultspec-rag search \"<intent>\" --type vault --doc-type adr"
},
"remediation": "Hosted vault search is not configured: set VAULTSPEC_CORE_TYPESAFE_API_KEY to enable it, or run `vaultspec-rag search \"<intent>\" --type vault --doc-type adr`."
}
crossrefLink to Tools, crossref
Find the ADRs a decision should cross-reference, within fixed bounds. Not read-only (it can write links), non-destructive, idempotent, open-world. On a read-only server it is read-only and judges one ADR.
crossref ranks every other ADR locally, judges a bounded shortlist, and returns
suggested links and declared links judged weak. It is the same backend as
vaultspec-core vault adr crossref, and it uses the hosted-search key and data flow
described under search.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
list of ADR references |
|
ADRs to judge: stem, filename, path or |
|
string or null |
|
Sweep this feature’s ADRs. |
|
boolean |
|
Sweep only ADRs that link no other ADR. |
|
boolean |
|
Sweep non-retired ADRs, including proposals; superseded, rejected, and deprecated sources require naming. |
|
string or null |
|
Resume a sweep after this ADR, the |
|
integer, 1 to 50 |
|
Most ADRs one sweep judges. |
|
boolean |
|
Write each new |
|
string or null |
|
Proposed body prose for one ADR, judged without changing it; no sweep or apply. |
A call needs refs, feature, isolated, or all_adrs. feature and isolated
narrow together; refs and all_adrs each stand alone, and combining either with
another selector is refused. The read-only server takes ref, the ADR to judge, and
optional body prose. Body prose omits frontmatter and allows amendments to be checked
before changing accepted text; the source result is marked draft.
Bounds. Every other ADR is ranked by code alone, the best 192 are put to the model
as Choice questions of at most 32 options, and the best 32, plus up to 8 declared links
outside them, are judged in pairs. A source has at most 46 requests and a 15-second
deadline, a sweep takes at most 50 sources, or 52 when it must settle a refusal, and 300
seconds, and a vault may hold at most 5,000 ADRs. Sweeps run in stem order and store no
state. An ADR the provider refuses to read is held open while the sweep judges on, past
its source limit by up to two more ADRs if it must: a later ADR the provider reads shows
the refusal was that ADR’s own, and the sweep moves past both. A refusal no read
settles, three refusals in a row, or any other failure stops the sweep with stopped
set and the cursor before the first refusal still open, so resuming retries from there.
Verdicts. Each row has stem, kind, score, relation, status, declared,
and applied when this call wrote it. link means the source should link the ADR;
weak is a declared link judged below the threshold, listed for a reader and never
removed. relation (supersedes, refines, depends_on, conflicts,
shared_artifact, topic_only, unrelated) is advisory: it says which pairs to read
in full, not what to decide.
Reply. sources holds one entry per source, with its status (ok,
not_configured, or unavailable with its reason), its verdicts, and the counts
links, written, and verdicts_total; truncated marks a source whose rows were
cut, since a reply carries at most 80 verdict rows. A source also carries
unjudged_declared when declared links were beyond the judged ceiling, write_failed
for links apply could not write, and, when it was not judged, next_step and
remediation: the search to run instead, which is the vaultspec-rag search when rag
is provisioned and the ADR listing otherwise. The totals judged, links, and
written follow, then remaining, next_after when sources remain and one was
processed (without it, a resume starts from the beginning), stopped when a sweep ended
early, and usage (requests, input tokens, and unscored, the requests the provider
refused to read) when anything was sent. A source whose every pair was refused is
unavailable with content_rejected, never a source with no links.
Judged sources include coverage: corpus, pool, judged, source_truncated, and
candidates_truncated. Returned candidates with clipped decision text carry
input_truncated. This is distinct from reply-row truncated. The 6,000-character
input budget is shared across sections, with decisions and constraints first. Read
relevant full records when coverage is incomplete; ok does not prove no conflicts.
The batch envelopeLink to Tools, The batch envelope
create, edit, plan_progress, and plan_edit take a list and return one envelope
describing the whole call:
Field |
Type |
Meaning |
|---|---|---|
|
|
|
|
list |
Item rows. See the sampling rule below. |
|
object |
How many items landed in each status. Exact regardless of sampling. |
|
integer |
How many items were in the request. |
|
integer |
Uneventful successes left out of |
items is a sample, not the full list. It carries every failure and every item that
produced a warning, plus some plain successes; the rest are counted in items_omitted.
A client that tallies items to learn what happened will undercount on a large batch.
Read counts for the outcome and items for the detail.
A partially failed batch is still a successful call. It returns status: "mixed" and
reports each item’s outcome rather than raising a protocol error. What fails the call
itself is a malformed request and, for create only, a schema convergence that could
not be applied before the first write; see create.
createLink to Tools, create
Scaffold one or more vault documents from templates. Mutates the vault; not idempotent.
create takes a single parameter, documents, a list of document specifications with
at least one entry.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
string |
none |
Required. Feature tag, in kebab-case. A leading |
|
string or null |
|
Document type: |
|
string or null |
|
Rendered into the document’s heading. |
|
string or null |
today’s date (UTC) |
ISO-8601 date. |
|
string or null |
|
Seed prose, appended under a |
|
list of strings or null |
|
Documents to link in the |
|
list of strings or null |
|
Only the document’s required directory and feature tags. Duplicates are ignored; other tags fail the item before writing. Omit for ordinary creation. |
|
string or null |
|
Plan complexity tier, one of |
|
string or null |
|
Kebab-case filename infix that distinguishes a second document of the same type for one feature ( |
Behavior notes:
createreads the template at.vaultspec/templates/{type}.md, fills its placeholders, and writes the document to.vault/{type}/{date}-{feature}-{type}.md; you don’t set the filename directly.Items in a batch apply in order, and one item’s failure doesn’t abort the rest of the batch. Because each item validates against a vault that already includes the batch’s earlier writes, a single call can create a full dependency chain, such as a research document followed by the ADR and plan that depend on it.
On success,
createregenerates the feature index for every feature that received at least one successfully created document.An empty
documentslist is a whole-call error, distinct from a per-item failure.So is a failed schema convergence.
createdecides where it writes from the current schema, so it applies any pending schema migrations before the first item. If that fails, the whole call fails with a protocol error naming the cause and nothing is written - a client that submitted three specs gets one call-level failure rather than three item rows. Refusing is deliberate: writing part of the batch into a layout the schema no longer describes leaves a split brain that is harder to recover from than a clean refusal. Resolve it withvaultspec-core migrations statusandvaultspec-core migrations run, then retry the call.
create can fail an item for these reasons:
an invalid feature tag, extra tag, tier, or document type
a request for type
index(index documents are auto-generated; use the feature-index verb instead)an unresolvable
relatedreferencea lifecycle-dependency error (for example, requesting an
execdocument before its plan exists)a missing template
a file that already exists
Example item, from the items list:
File contents
{
"index": 0,
"target": "adr:search-api",
"status": "created",
"path": ".vault/adr/2026-07-12-search-api-adr.md",
"blob_hash": "<oid>",
"error": null,
"warnings": []
}
Example failure item, in a batch whose overall status is "mixed":
File contents
{
"index": 1,
"target": "exec:search-api",
"status": "failed",
"path": null,
"blob_hash": null,
"error": {"message": "... requires a plan ..."},
"warnings": []
}
editLink to Tools, edit
Apply one or more body-prose edits to existing vault documents. Mutates the vault; not
idempotent, and set_body and replace_section overwrite existing prose.
edit takes a single parameter, operations, a list of edit operations with at least
one entry.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
string |
none |
Required. The document to edit: a stem, filename, path, or |
|
string |
none |
Required. One of |
|
string |
none |
Required. For |
|
string or null |
|
Exact heading-line text, for example |
|
string or null |
|
Optimistic-concurrency guard. If the document’s current blob hash doesn’t match, the item fails as a conflict and |
edit changes body prose only: it strips frontmatter before composing the new body,
never touches frontmatter, and never renames files. set_body replaces the whole body
while preserving frontmatter. append_section inserts content after the matched
section; replace_section keeps the heading and swaps the prose beneath it. Section
matching is exact against the heading-line text; a heading that doesn’t match fails the
item with "section_not_found": true.
Batches apply sequentially, and one item’s failure doesn’t abort the rest. Multiple
operations against the same document chain correctly: only the first operation needs
expected_blob_hash, and each later operation validates against what the prior
operation just wrote, so a blob_hash returned by one call can guard a later call.
edit can fail an item for these reasons:
an unknown operation
a missing
sectionfield on a section operationan unresolvable target (reported as
"Cannot resolve document: '...'")a blob-hash conflict (reported with
"conflict": true, and the file left untouched)
An empty operations list is a whole-call error, distinct from a per-item failure.
Example success item:
File contents
{
"index": 0,
"target": "2026-07-12-search-api-adr",
"status": "updated",
"path": ".vault/adr/2026-07-12-search-api-adr.md",
"blob_hash": "<post-write oid>",
"error": null,
"warnings": []
}
Example conflict item:
File contents
{
"index": 0,
"target": "...",
"status": "failed",
"blob_hash": null,
"error": {"message": "...", "conflict": true},
"warnings": []
}
statusLink to Tools, status
Orient in a vaultspec project, project-wide or targeted at one feature or plan. Read-only, idempotent.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string or null |
|
A feature tag or a plan stem/path. Omit it for a project-wide rollup. |
Call status without a target to get a rollup: the features in the vault (name,
document count, latest activity, whether a plan exists, lifecycle status, plan tier, and
plan completion percent), the plans currently in flight (stem, feature, tier, open and
closed step counts, completion percent, and the next open step), vault-wide totals, and
hosted_search. That field reports whether search has a key (configured)
and, when it does, where the key was found (source: environment, local_env, or
dotenv). It describes configuration, not whether the key works. Next to it,
companion reports whether the vaultspec-rag companion is provisioned, which decides
the next step a declined search names (package, signal, mode, version,
floor, health_authority; the key is absent when the probe failed). It reports
configuration, not liveness: a provisioned companion can still be down. Both fields come
from the same backend call as vaultspec-core status --json. Every response carries a
tool_schema_version field so a client can detect a server upgrade.
Pass a target to trace one plan or feature instead. The response then reports each
plan’s steps in full detail (canonical ID, display path, checked state, the ledger stem
when rows exist, the row count, and the last verify: result), the grounding documents
behind the plan (grouped by document type), and any exec documents that name no step. A
target that resolves to no plan or feature fails the whole call with a protocol error.
status returns no hashes; get a blob_hash from find to guard an edit.
Rollup example, captured against this project’s own vault and cut to one feature and one plan of the eleven and four it returned:
File contents
{
"tool_schema_version": "0.1.73",
"kind": "rollup",
"features": [
{
"name": "docs-site",
"doc_count": 115,
"latest_activity": "2026-09-02",
"has_plan": true,
"status": "In Progress",
"plan_tier": "L3",
"plan_completion_percent": 99.1
}
],
"features_total": 11,
"plans_in_flight": [
{
"stem": "2026-08-28-docs-site-plan",
"feature": "docs-site",
"tier": "L3",
"open_steps": 1,
"closed_steps": 110,
"total_steps": 111,
"completion_percent": 99.1,
"next_open_step": "W09.P18.S109"
}
],
"totals": {
"total_docs": 394,
"total_features": 11,
"counts_by_type": { "adr": 14, "audit": 15, "plan": 11, "exec": 327 },
"orphaned_count": 0,
"dangling_link_count": 0
},
"plans": [],
"hosted_search": { "configured": false }
}
status on a feature is written for a person - In Progress, Completed - rather than
as a slug, so match it exactly rather than assuming a lowercase hyphenated form.
counts_by_type above is cut to four of its seven keys.
Trace example:
File contents
{
"kind": "trace",
"trace_kind": "feature",
"target": "search-api",
"plans": [
{
"stem": "2026-07-12-search-api-plan",
"steps": [
{ "canonical_id": "S01", "display_path": "S01", "checked": true, "record_stem": null }
]
}
]
}
checkLink to Tools, check
Run the vault health-check suite and, when asked, apply safe repairs. Not read-only, not destructive, idempotent - a repair never overwrites authored prose, so running it again converges on the same clean state.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string or null |
|
Restrict the check to one feature tag, without the |
|
boolean |
|
Apply safe auto-corrections instead of only reporting them. |
The response reports an overall status: "ok" when the total error count is zero,
"failed" otherwise. Warnings alone don’t fail a check. Alongside status, the
response carries fixed (whether a repair ran), total_errors, total_warnings,
total_fixed, a checks array with one summary row per validator (error, warning,
info, and fixed counts, plus a clean flag), and a findings array listing every error
and warning (informational diagnostics are dropped). Each finding names the check that
raised it, the affected path, a message, its severity, and whether it’s fixable.
Set fix to true to apply safe corrections in the same call. The response then
reports fixed: true, and if every issue found was auto-fixable, status returns to
"ok".
Clean example:
File contents
{
"status": "ok",
"fixed": false,
"total_errors": 0,
"total_warnings": 0,
"total_fixed": 0,
"checks": [{ "check": "dangling", "error_count": 0, "warning_count": 0, "info_count": 0, "fixed_count": 0, "clean": true }],
"findings": []
}
Finding example:
File contents
{
"check": "dangling",
"path": ".vault/adr/2026-07-01-search-api-adr.md",
"message": "wiki-link target not found",
"severity": "error",
"fixable": false
}
plan_progressLink to Tools, plan_progress
Mark plan steps checked or unchecked by canonical identifier. There’s no toggle operation - each step change states its target explicitly, which keeps the tool idempotent. Not read-only, not destructive, idempotent.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
- (required) |
A feature tag or a plan stem (optionally ending in |
|
list of step-state changes |
- (required, at least one entry) |
Each entry is |
Plan resolution follows one rule across every tool that accepts a plan parameter: an
exact stem match wins first; failing that, a feature tag resolves only when it matches
exactly one plan. A feature with several plans refuses the call and lists the candidate
stems rather than guessing. An unresolvable or ambiguous plan fails the whole call with
a protocol error, as does an empty steps list.
MCP plan mutation tools and gateway plan verbs accept stems and feature handles only.
They reject filesystem paths and require the resolved plan to be a regular file beneath
the workspace’s .vault/plan, including after resolving filesystem links. Literal plan
paths remain available through the trusted local CLI.
Each step change reports its own outcome:
"updated"- the step changed to the target state"unchanged"- the step was already at the target state, which counts as a success"failed"- an unknown step, an ambiguous ID, or an invalid state, with anerror.message
A failure in one step change doesn’t abort the batch - the aggregate status becomes
"ok", "mixed", or "failed" depending on the outcomes. The plan file is written
once, at the end of the batch, and only if a step changed. That write refreshes the
plan’s modified stamp.
Every response ends with the plan’s post-batch state: total_steps, steps_completed,
completion_percent, and next_open_step (a display path, or null when every step is
closed).
File contents
{
"status": "ok",
"plan": "2026-07-12-search-api-plan",
"items": [{ "step_id": "S01", "state": "checked", "status": "updated" }],
"total_steps": 2,
"steps_completed": 1,
"completion_percent": 50.0,
"next_open_step": "S02"
}
plan_editLink to Tools, plan_edit
Author plan steps: add, insert, edit, or remove step rows. Not read-only, destructive - removing a step retires its ID permanently - and not idempotent.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
- (required) |
A feature tag or a plan stem, resolved the same way as in |
|
list of plan-edit operations |
- (required, at least one entry) |
See the operation fields below. |
Each operation has these fields. Omit a field that does not apply rather than sending
null:
Field |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
One of |
|
string or null |
|
The step’s imperative statement. Required for |
|
string or null |
|
The file or area the step touches. Required for |
|
string or null |
|
The |
|
string or null |
|
The |
|
string or null |
|
The |
|
string or null |
|
The target step for |
Operations apply in order against a single parsed plan, so an earlier add in the same
call is visible to a later edit or remove. A failure in one operation doesn’t abort
the batch; the aggregate status can come back "mixed". An empty operations list
fails the whole call with a protocol error.
Identifier guarantees belong to the plan core, not this tool:
addallocates the next free ID in sequence.insertalways allocates a new ID, even when inserting before an existing step. Nothing is ever renumbered.removeretires an ID permanently. Lateraddoperations skip past retired IDs rather than reusing them.
A write guard refuses any write that would retire identifiers the operation didn’t intend to touch.
plan_edit covers step rows only. Restructuring above the step level - tiers, waves,
phases, or the epic frame - is gateway-or-CLI territory and isn’t a first-class
operation here.
Add example:
File contents
{
"status": "ok",
"plan": "2026-07-12-search-api-plan",
"items": [{ "operation": "add", "status": "created", "step_id": "S03" }],
"total_steps": 3,
"steps_completed": 1,
"next_open_step": "S02"
}
logLink to Tools, log
Append one step’s evidence to its plan’s execution ledger. Not read-only, not
destructive (append-only), idempotent: re-logging a row changes nothing. The ledger is
one document per plan, created on the first call; it is the only execution artifact, and
this tool and vaultspec-core vault exec log share one writer.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
- (required) |
Feature tag, with or without |
|
string |
- (required) |
The parent plan’s stem. |
|
string |
- (required) |
Canonical step id ( |
|
list of strings or null |
|
One |
|
list of strings or null |
|
One |
|
string or null |
|
The persona that closed the step. |
|
list of strings or null |
|
Exception notes only (data loss, skipped work, a scaffold left in code, a persistent failure). |
A malformed row or verify spec, or a feature, plan, or step that does not resolve, fails the whole call with a protocol error and writes nothing.
File contents
{
"path": ".vault/exec/2026-07-12-search-api/2026-07-12-search-api-ledger.md",
"step": "S01",
"rows": 3,
"notes": 0,
"changed": true,
"created": true
}
discover and invokeLink to Tools, discover and invoke
discover and invoke are the gateway pair for everything else. The ten hot tools
cover the verbs a client reaches for constantly; discover and invoke reach the rest
of the CLI - the long tail of verbs that don’t earn a dedicated tool but still need to
run. discover searches that catalog and returns ranked schemas. invoke runs one of
the verbs the catalog names.
discover: search the verb catalogLink to discover and invoke, discover: search the verb catalog
Read-only, idempotent.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
Verb words or an intent phrase to rank the catalog against. |
|
integer |
|
Maximum number of ranked verbs to return. |
It returns the query echoed back, a count, and a list of ranked verb schemas.
Each verb schema carries:
verb- the space-joined verb path, for example"vault list"description- the verb’s help textscore- a relevance score; results are ranked by relevance, highest firstsupports_json- whether the verb accepts a--jsonflagflags- the verb’s flags, each with a name, whether it takes a value, and its help textarguments- the verb’s positional arguments, each with a name, whether it’s required, and whether it’s variadic
A discover call looks like this:
File contents
{
"query": "list vault documents"
}
And the response:
File contents
{
"query": "list vault documents",
"count": 1,
"verbs": [
{
"verb": "vault list",
"description": "List or filter vault documents.",
"score": 7.5,
"supports_json": true,
"flags": [
{ "name": "--feature", "takes_value": true, "help": "Filter by feature tag." }
],
"arguments": []
}
]
}
invoke: run a cataloged verbLink to discover and invoke, invoke: run a cataloged verb
Not read-only. Destructive, because the catalog reaches mutating verbs. Idempotency depends on the verb invoked.
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
The space-joined verb path returned by |
|
object or null |
|
The verb’s flags as a mapping. A list value repeats the flag; a boolean passes |
|
list of strings or null |
|
The verb’s positional operands, in CLI order. |
|
number |
|
Positive, finite wall-clock seconds; values above 120 are clamped to 120. |
Three flags are reserved for the server: --target, --json, and --help. Do not pass
them in arguments. The server sets --target to the resolved workspace and adds
--json automatically when the verb supports it, and supplying a reserved flag fails
the call before any process starts.
--editor, --from-file, --body-file, and --template are restricted to the local
CLI. They are omitted from discovery and rejected by invoke before a process starts.
MCP-launched CLI processes also refuse file imports. To add rules, skills, or agents
through MCP, pass their content in body.
At the user level, invoke runs the named verb as a subprocess of the installed
vaultspec-core binary against the resolved workspace. When the verb supports --json,
invoke parses the output into the response’s data field; otherwise it returns the
raw standard output as text. Because the catalog mixes mutating and read-only verbs,
invoke carries the destructive-operation annotation unconditionally: the host prompts
for confirmation on every call, not just the ones that change state.
A successful call looks like this:
File contents
{
"verb": "vault add",
"positionals": ["research"],
"arguments": { "feature": "search-api" }
}
File contents
{
"verb": "vault add",
"ok": true,
"exit_code": 0,
"format": "json",
"data": { "path": ".vault/research/2026-07-11-search-api-research.md" },
"stdout": null,
"error": null,
"command": ["vaultspec-core", "--target", "<root>", "vault", "add", "research", "--feature", "search-api", "--json"]
}
Two kinds of failureLink to discover and invoke, Two kinds of failure
invoke distinguishes a failed call from a failed verb. Three checks run before any
process spawns and raise a protocol error: an unknown verb path, a denylisted verb, or
an invalid argument (a reserved or undeclared flag, or a malformed positional). Nothing
runs when one of these fires.
Once the verb runs, its outcome is normally a per-call result. The response’s ok field
is true only when the process exits zero and its output parses successfully. A verb
that runs and exits non-zero returns ok: false with a populated error, and the call
itself still succeeds:
File contents
{
"verb": "vault plan status"
}
File contents
{
"verb": "vault plan status",
"ok": false,
"exit_code": 2,
"format": "text",
"data": null,
"stdout": null,
"error": {
"kind": "nonzero_exit",
"exit_code": 2,
"stderr": "Error: missing argument 'PLAN'",
"message": "verb exited with status 2"
},
"command": ["vaultspec-core", "--target", "<root>", "vault", "plan", "status", "--json"]
}
error.kind takes one of four values: nonzero_exit (the process exited with a
non-zero status), json_parse (the process exited zero, but its declared JSON output
didn’t parse), timeout (the process didn’t finish within the timeout budget), or
output_limit (combined stdout and stderr exceeded 1 MiB; the child was stopped and its
output discarded).
The serialized response, including JSON escaping and command metadata, is capped at 128 KiB. A larger response raises a protocol error after the verb has run; this does not undo its effects. Check the workspace state before retrying a mutating verb.
The denylistLink to discover and invoke, The denylist
A verb never reaches a subprocess if it’s on the denylist. discover excludes
denylisted verbs from its results, and invoke rejects them before spawning. The
denylist covers:
uninstall- tears down the frameworkvaultspec-core spec mcps add,vaultspec-core spec mcps remove,vaultspec-core spec mcps sync, andvaultspec-core spec mcps uninstall- MCP definition or enrollment mutation, owned by thevaultspec-core spec mcpslifecycle (the read-onlyvaultspec-core spec mcps listandvaultspec-core spec mcps statusstay available)vaultspec-core vault feature index- index documents stay uncreatable through the MCP surfacevaultspec-core install,vaultspec-core sync,vaultspec-core spec hooks sync,vaultspec-core spec hooks trust,vaultspec-core spec triggers add,vaultspec-core spec triggers run, andvaultspec-core spec triggers trust- a hook or trigger file declares a shell command, and these are the verbs that write one, approve one, render one into an agent’s config, or fire the lifecycle event that runs them. Every other gateway defence is about argv hygiene and cannot help here, because a well-formed invocation of these verbs is exactly the dangerous one, and because a model’s context can contain text from a cloned repository. Approving either is an operator decision taken at a terminal, so no tool call can stand in for one. The deprecatedvaultspec-core spec hooks addandvaultspec-core spec hooks runaliases delegate to the trigger verbs and carry the same authority, so they are denied too, and leave the denylist when the aliases do. The read-onlylist,show, andstatusverbs of both groups stay available, so an agent can still read and explain what a workspace declares.
Flags the gateway does not passLink to discover and invoke, Flags the gateway does not pass
A verb can be perfectly safe to run and still declare one option that is not safe to
accept from a tool call. --editor is the example: it names a command for the CLI to
execute, which is the point of the flag at a terminal and has no meaning at all through
invoke, where no terminal is attached.
The gateway’s other screens do not catch it. The positional guard sees no operand, and
the flag-name guard sees an option the verb genuinely declares; neither looks at the
value. So --editor is refused by name for every gateway call, whichever verb declares
it, and it is withheld from the schemas discover returns.
Separately, every subprocess invoke spawns is marked as non-interactive in its
environment, and the CLI declines to open an editor when it sees that mark - from the
flag, from .vaultspec/config.toml, from VAULTSPEC_EDITOR, VISUAL or EDITOR, or
from the built-in fallback. The mark is added to the environment the gateway itself
composes, which no tool argument reaches, so a caller can neither set nor clear it. The
edit verbs stay reachable for everything else they do; only the interactive launch is
off.
Server lifetimeLink to Server lifetime
The server’s lifetime is its client connection. It exits when stdin reaches EOF, and a lifetime watchdog backstops the cases where EOF never arrives - on Windows, a client that restarts or dies can leave the server’s stdin pipe held open by inherited handles, which would otherwise leave orphaned server processes running indefinitely.
The watchdog anchors shutdown to the client process itself. On Windows it identifies the
process that created the server’s stdin pipe and exits the moment that process
terminates; when stdin is not a client-created pipe (a console launch, for example) it
watches the server’s ancestor process chain instead, ignoring short-lived launcher
processes. On other platforms a coarse poll exits the server if it is ever orphaned.
When the watchdog fires, one JSON event line ("event": "stdio_watchdog_exit") is
written to stderr before the server exits cleanly. Its reason field says why:
watched_process_exit when the client died, unanchored_orphan when the server reaped
itself as described below.
On Windows the watchdog can also lose track of the client entirely - every launcher in
the chain exits before it looks, and stdin is not a pipe it can trace. Rather than give
up for the rest of the run, it keeps looking on an interval and re-anchors as soon as a
live ancestor appears. Only if repeated checks agree that no live process is left to
serve does it write a "stdio_watchdog_disarmed" event and then exit as an orphan.
Two knobs control it:
VAULTSPEC_STDIO_WATCHDOG- a false word (0,false,off,no) disables the watchdog entirely; the server then exits only on stdin EOF. Unset, blank, or an unrecognised word leaves it armed.--parent-pid <PID>- names an explicit client process to watch in addition to the detected one. Useful for hosts that spawn the server through wrappers the detection cannot see through.
A watchdog that cannot start never prevents the server from serving, and an ambiguous reading is never treated as a dead client: the server exits only when a check confirms the client is gone, never when a check is inconclusive.
LoggingLink to Logging
All server logs go to stderr. Stdout is reserved exclusively for the JSON-RPC protocol stream, so nothing but protocol data can be written there without breaking the client connection.
Getting helpLink to Getting help
If the server misbehaves, start with the commands in the Verification section - they diagnose the most common configuration problems without leaving the terminal. For anything they don’t explain, file bugs and questions on the vaultspec-core issue tracker.