Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
WILLOW_PG_DBNoPostgres database namewillow
SAP_SAFE_ROOTNoSAFE folder root~/.sap/Applications
WILLOW_PG_USERNoPostgres user (Unix socket auth)$USER
WILLOW_STORE_ROOTNoSQLite store directory~/.willow/store
SAP_PGP_FINGERPRINTNoPinned GPG fingerprint

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
store_putA

Write a JSON record to a named SOIL collection (created on first write). Pass record_id to pin a stable ID — re-putting the same ID overwrites the record; omit it to auto-generate one. deviation (radians) declares how far this write departs from established pattern: >= 0.785 is recorded as 'flag',

= 1.571 as 'stop', otherwise 'work_quiet'. Returns {id, action}. Confined to the collections in your manifest's store_scope.

store_getA

Read one record from a SOIL collection by its exact ID. Returns the stored record plus _id/_created/_updated/_deviation/_action metadata, or {error: not_found} if the ID is absent or soft-deleted. Read-only; confined to your store_scope.

store_listA

Return every live record in one SOIL collection, oldest first, each with _id/_created/_updated metadata. Unfiltered and unpaginated — prefer store_search once a collection grows large, and store_collections to discover collection names. Read-only; confined to your store_scope.

store_updateA

Replace an existing record's data in-place — same ID, updated_at bumped, audit metadata retained. Never creates: an unknown ID returns {error: not_found} (use store_put to create). deviation declares pattern-departure exactly as in store_put. Returns {id} on success. Confined to your store_scope.

store_searchA

Full-text search one SOIL collection: the query is split on whitespace and EVERY token must appear somewhere in a record's JSON (AND logic, substring match). Returns matching records with _id metadata; an empty query returns []. Read-only; confined to your store_scope.

store_deleteA

Soft-delete one record by ID: it disappears from get/list/search but is retained on disk for audit and recovery (hard removal is an operator act, deliberately outside the tool surface). Returns {deleted: true|false} — false means the ID wasn't found. Confined to your store_scope.

store_purge_collectionA

Soft-delete EVERY record in a collection at once — a bulk store_delete, for clearing out a whole collection (e.g. leftover test/scratch data). Archive, not drop: records are retained (deleted=1) and stay recoverable, they just fall out of get/list/search, matching the store's soft-delete model. The collection's store.db is never removed — hard removal is an operator/filesystem act, deliberately outside the tool surface. Guarded against fat-finger accidents: pass confirm= to proceed. Confined to your store_scope like every other store_* tool.

store_search_allA

Token-AND search across every SOIL collection you can see — all of them, or only your manifest's store_scope if one is set. Each hit carries a _collection field naming where it was found. Use when you don't know which collection holds the data; prefer store_search when you do. Read-only.

store_collectionsA

List the SOIL collections you can see — every collection under the store, narrowed to your store_scope if your manifest sets one. Answers "what's in the store" without running a search (store_list needs a collection name; this is how you learn the names). Returns the collection names, a count, and the scope that was applied (null = unrestricted within this store).

store_statsA

Per-collection live-record counts for the collections you can see (narrowed to your store_scope) — the numeric companion to store_collections. Counts only live records (soft-deleted ones don't show). Returns each collection with its count (largest first), plus store-wide totals — handy for spotting a polluted or runaway collection before deciding what to purge.

lineage_recordA

Record a provenance atom — the "story of this willow" as memory an agent can query later. Answers the questions agents keep asking: where did this come from, why is it this way, what was here before. rationale (the WHY) and at least one evidence citation (a PR / commit / file / session) are REQUIRED — an atom that can't cite is lore, not memory, and is refused.

Relationships to other atoms are typed EDGES (stored in lineage_edges, the same {from,to,relation,context} shape willow's own knowledge graph uses), and direction is QUERIED, not stored twice:

  • supersedes — atoms this REPLACES (the old ones become non-current)

  • derived_from — atoms this CAME FROM but did NOT retire (both stay valid)

  • motivated_by — the friction/decision behind it (may be a gap id or an external node, not only another atom)

Corrections re-record the same id in place; edges persist independently. Confined to your store_scope like every store write.

subject_id (guardian-consent seam): a provenance atom that makes a person-shaped claim about a non-owner names that subject here. This is the highest bar in the seam — person_inference — corpus-lens's quarantined PERSON_CLAIM_TYPES: making the claim at all requires a verified grant. Opaque, never stored on the atom. Leave empty for the owner or for non-person lineage.

lineage_linkA

Add one provenance edge without (re)writing a node — e.g. mark an atom motivated_by a gap discovered after the fact, or derived_from a source you only now connected. relation is free-form; the why verb reads supersedes / derived_from / motivated_by. Idempotent per (from, relation, to).

lineage_whyA

Answer "why does X exist / where did X come from" from recorded lineage. Give a slug id or free text; returns the matching atom's rationale, origin, authority, and evidence, PLUS its typed edges — what it supersedes (and whether it is itself still current), what it was derived_from, and what motivated it — the lineage, not a blob. A plain-language answer synthesizes it. This is the verb a curious agent runs before acting on something it didn't build.

lineage_listA

List recorded lineage atoms (id, title, whether current, tags) — the index of "what parts of this willow have a recorded story". Pass current_only=True to hide atoms that a later supersedes edge has retired.

friction_scanA

Scan a transcript window for the mirror failure mode: the agent has stopped being other and is reflecting the user back, smoothed, WHILE the user is escalating. Model-free and deterministic — no LLM, no egress; it NEVER blocks, it only flags. When a window of agent turns sits below the friction floor during escalation it raises (and persists, deduped) a loud human-facing flag naming where the agent stopped disagreeing.

turns: [{"role": "user"|"agent", "text": str, "ts"?: number}, …] — the recent window, in order. It is a SIGNAL, not a verdict (false-positives happen; a clever mirror can duck it); its value is observability. It MUST be driven from OUTSIDE the watched model (a harness/monitor) — a mirror cannot audit itself; an agent scanning its own turns is theater.

friction_flags_listB

List recent friction flags recorded by friction_scan — the durable trace of when the relationship watcher tripped (most recent first).

nestor_tool_routeA

Route a natural-language intent to the willow-mcp verb that serves it. Returns status='served' with the resolved tool ONLY when a human-sealed phrasing clears the match threshold; otherwise status='queued' (the intent is logged for a human to seal) — it never guesses a verb. status='unavailable' when the optional Nestor engine isn't installed. Read-oriented, but every route leaves a hash-chained ledger trail like any other served answer. Backed by a signed catalog that is integrity-checked before it is trusted.

nestor_tool_sealA

Teach the oracle: sanction that natural-language surface maps to willow verb tool. This is a GOVERNANCE WRITE — it seals a verified pair (signed, ledgered) that nestor_tool_route will then serve, so the tool_oracle_seal group is meant for human/attested seats only: sealing a phrasing mints invocation power for it. The counter-verb to a queued route; your app_id is recorded as the verifier.

nestor_tool_pendingA

The teach-queue: natural-language intents nestor_tool_route couldn't serve, newest first, deduped by phrasing — what a human should seal (or reject) so the oracle learns them. Empty when every routed intent has a sealed home.

session_bindA

Open a cryptographically-bound session (check-in). Provide a 13-field HMAC-signed header whose agent_id equals your app_id; the server verifies it against your operator-registered secret and caps the claimed trust_level at your registered ceiling ("Elder is not a text field anyone can type"). Returns {session_id, agent_id, trust_level, tier}, or {error} on refusal.

Once bound, the tier is LOGGED on your subsequent calls (receipt bind_observed). With WILLOW_MCP_ENFORCE_BINDING on it is also ENFORCED — each call must carry a valid per-call signature and clear the tier ceiling (Phase 3). Registration/rotation of the secret is operator/CLI-only (willow-mcp register-agent); no MCP tool can mint one — the sudo invariant.

session_reconcileA

Close a bound session and reconcile what you DECLARE you did against what the receipt log shows you actually did (check-out; willow-gate seam Phase 4 / H3).

exit_declaration is the reconciled subset of the entry header — tools (the willow-gate CLASSES you exercised: read/write/execute/admin), plus your self-scored pass_count/fail_count/drift/state_hash. The server sources the ground truth from ReceiptLog (every gated call that actually ran since your check-in — you cannot feed it), classifies those tools, and diffs:

  • claimed_not_done — a class you claim you used that NO receipt backs;

  • beyond_entry / done_not_claimed — a privileged class the receipts show you used that you did not pre-declare / did not report. clean is false if any privileged discrepancy is found; read-level over/under- reporting is surfaced but never fails the session. The session is DROPPED after this call (its per-call nonce set is freed), so verify_call for it then fails.

Requires a live session bound to your app_id (call session_bind first); returns {error} if there is none. This RECONCILES and records — it never blocks a handoff, so run session_handoff_write / handoff_write_v4 as usual alongside it.

knowledge_ingestA

Add a knowledge atom to the Postgres knowledge base. Check for duplicates first.

subject_id (guardian-consent seam): name the person this atom is about when it is not the owner. Opaque and local — never written to the KB, only checked. Naming a non-owner subject requires a verified kb_promotion consent grant (see docs/design/guardian-consent-seam.md); leave it empty for the owner's own data. The successful write is logged to that subject's disclosure chain.

nest_scanA

Walk a drop folder, extract + classify its files, and write a canonical SQLite Nest DB. Returns structure only (counts by source status and fragment type) — never file content.

dry_run=True (default): classify and report counts WITHOUT writing the DB — inspect what a dump would become before committing it. dry_run=False writes. use_embed uses an Ollama embedding model when present (falls back to regex offline); use_llm escalates the uncertain tail to a text/vision model.

Inference stays on this machine by default. It is NOT unconditional: the seams post to $OLLAMA_HOST, and if that points off-box this tool requires the operator's standing consent.cloud_llm and denies without it. Classification sends document bodies, so where that host points is a privacy decision, not a performance one. (This docstring used to promise "nothing leaves the machine" flatly, which was true of the default and false of the variable.)

nest_statusB

Counts for a seeded Nest DB — sources by status, fragments by type, topical categories by size. Structure only; no content.

nest_digestA

A one-page Markdown map of a seeded Nest DB — the WALLED view: category breakdown, discovered clusters, secret kinds, and how files were read. Person names, the date timeline, and source filenames are suppressed (they are content, not structure). The full unwalled digest is a local-CLI affordance only; it is never returned over MCP.

nest_promoteA

Promote a Nest's STRUCTURE into the knowledge base. Reads structure-only atoms from bridge.build_bridge — counts, curated category names, and redacted secret kinds, never fragment content, filenames, or person names — and ingests each through the same core write knowledge_ingest uses.

dry_run=True: return the atoms that WOULD be promoted (safe to inspect — they are structure only) without writing. dry_run=False ingests them.

subject_id (guardian-consent seam): when the Nest is a non-owner's life-dump (a co-parent, a child, an ex-partner — the case the seam exists for), name that subject. Even the structure-only bridge crossing into the shared KB then requires a verified kb_promotion grant for them; leave empty for the owner's own dump. Opaque, never written to the KB. A committed promotion is logged to the subject's disclosure chain.

nest_intake_scanA

Scan drop zone(s), classify new files by filename into tracks, and stage a review queue. Idempotent (a file already staged is not re-staged) and non-destructive — nothing is moved until nest_intake_file. folder overrides the default drop dirs (~/Desktop/Nest and $WILLOW_HOME/nest/inbox).

nest_intake_queueA

List the pending review queue — files staged by nest_intake_scan awaiting a confirm/override/skip decision, with the track the classifier predicted.

nest_intake_fileA

File a staged item: MOVE the file to its predicted track's destination, or to override_dest if you're correcting the classifier. An override (the outcome track differs from the prediction) feeds the correction counter and, at threshold, opens a rule-delta flag.

nest_intake_skipA

Skip a staged item — leave the file where it is and record the skip (removes it from the pending queue; the decision is logged as feedback).

nest_intake_flagsA

List open rule-delta flags — patterns the classifier got wrong often enough (CORRECTION_FLAG_THRESHOLD overrides) that it proposes a rules change. The classifier never rewrites its own rules; a human ratifies the delta.

gap_logA

Log or bump a "we don't know this yet" entry. Repeated asks of the same topic+question increment asked_count instead of duplicating — asked_count is the backlog's own priority signal for what to fill in next. Returns {id, status, asked_count}.

gap_listA

List backlog gaps, most-asked first — the fleet's shared "what we don't know yet" queue. Filter by topic and/or status (open | resolved | promoted); asked_count shows demand for each answer. Read-only.

gap_resolveA

Mark a gap as being worked or answered — bookkeeping only, does not write to the knowledge base. Use gap_promote to actually land a verified answer and close the gap out.

gap_deleteA

Soft-delete a single gap by id — for clearing junk or test entries from the backlog without disturbing its real gaps. Archive, not drop: the gap is retained (deleted=1) and just stops appearing in gap_list. Returns {deleted, id}, or {error: not_found}.

gap_purge_topicA

Soft-delete every gap under an exact topic in ONE call — bulk cleanup of a junk/test namespace without hitting gap_delete's per-call rate limit. Promoted gaps (which point at a landed knowledge atom) are left intact. Archive, not drop: purged gaps are retained (deleted=1), just removed from gap_list. Pass confirm= to proceed. Returns {purged, skipped_promoted, topic}.

Note: gaps are a FLEET-SHARED backlog, not store_scoped — this purges every app's gaps under the topic, so it's gated on its own gap_purge permission (not the everyday gap_write) rather than handed out broadly.

gap_promoteA

Turn a gap into trusted knowledge. Requires an answer, at least one source, and who's vouching for it (confirmed_by) — a human name, an agent id, whatever this fleet uses as an identity, but never empty. Writes through the SAME schema-confirmation gate as a direct knowledge_ingest call: if the 'knowledge' table mapping for this app_id hasn't been confirmed via schema_confirm_mapping, this refuses with unconfirmed_schema exactly like knowledge_ingest would. Requires Postgres — gap_log/list/resolve work SOIL-only, but promotion targets the durable, searchable knowledge base. Gated as its own permission (gap_promote), separate from gap_write, the same way schema_admin is kept separate from knowledge_write — landing something as trusted knowledge is a more consequential act than logging or resolving a gap.

knowledge_searchA

Search the fleet Postgres knowledge base by content: the query is whitespace-split and ALL tokens must appear in an atom (AND logic). domain narrows to one domain (e.g. 'journal', 'continuity'). Returns up to limit atoms. Read-only; use kb_at to fetch a known atom by ID.

task_submitA

Submit a task to the Kart sandboxed execution queue. Returns task_id for polling.

Tasks run network-isolated by default. Egress needs the task_net capability, standing consent.internet, a live operator lease, and an operator-signed per-task envelope passed as network_authorization. The envelope binds the submitter, task id, agent, normalized task hash, network scope, expiry, and nonce. The signed task id is the queue primary key, preventing the envelope from authorizing a second row. # allow_net and # allow_localhost remain requests, never authority.

Local Postgres access (socket mount + PG/POSTGRES env) requires allow_db=True and the separate task_db manifest capability — not granted by task_queue or full_access. # allow_db in task text is a request, never authority. When the operator sets WILLOW_MCP_ENFORCE_DB_PERIMETER (off by default), allow_db additionally requires an operator-signed per-task db_authorization envelope (db scope) from willow-mcp sign-db-task — mirroring the network perimeter, so a task_db holder can no longer submit arbitrary psql.

The Kartikeya executor verifies all host gates and the signed envelope again immediately before shell launch. Missing attribution or envelope, an invalid signature, expiry, replay, task mutation, unavailable verifier, or a strict trust-root failure denies network. Signing is available only through the local interactive willow-mcp sign-net-task CLI; no MCP tool can mint authority.

Task text is security-scanned at SUBMIT time (defense-in-depth): a task the Kart scanner would refuse — destructive, exfiltration, secret access, obfusc- ation, or resource-exhaustion (fork bomb / spin / disk-fill) — is rejected here before it ever occupies a queue slot, not only when the worker later picks it up. The worker re-scans at execution regardless; this just denies earlier and keeps a bomb from sitting pending.

task_statusA

Poll one task submitted via task_submit. Reads the fleet Postgres queue and returns the task row — status, result, submitted_by, timestamps — or {error: not_found} for an unknown task_id. Read-only; poll until the worker marks the task complete.

task_listA

List tasks still waiting in the Kart queue for one worker agent (default 'kart'), oldest first, task text truncated to 80 chars. Answers "what is queued to run" — use task_status for one task's full detail. Requires the fleet Postgres. Read-only.

kb_atA

Fetch one knowledge-base atom by its exact ID from the fleet Postgres — content, domain, source, and tags. Returns {error: not_found} for an unknown ID. Read-only; the direct-address companion to knowledge_search.

knowledge_flagA

Attach a visible integrity flag to an existing KB atom (tags only).

Idempotent: re-flagging replaces the prior flag metadata. Original content is unchanged. Requires knowledge_curate and a confirmed schema with a mapped tags column.

knowledge_retractA

Tombstone a KB atom in place — hidden from default search, fetchable by id.

Adds kb:retracted tags; kb_at still returns the atom with retracted: true. Requires knowledge_curate and mapped tags.

kb_promoteA

Move an existing knowledge atom to a different domain — e.g. lift a 'journal' entry into a topical domain once it proves durable. Updates the atom in place; content and ID are unchanged. Returns {id, domain} or {error: not_found}. Requires a confirmed 'knowledge' schema mapping.

kb_journalA

Append a free-text journal entry to the knowledge base: a new atom in domain 'journal', tagged 'journal' plus any tags you pass, with optional source attribution. Lighter than knowledge_ingest — no domain choice or sensitivity tiering. Returns {id, domain}. Requires a confirmed 'knowledge' schema mapping.

kb_ingestA

Promote a ratified agent_seed slice to Postgres KB (source_type: agent_seed).

Requires ratified + trusted seed at $WILLOW_HOME/seeds/{agent_id}.json. slice: voice_only | work_context | full (omit to use exposure.json default for kb_ingest). Never promotes persona.cast or context.personal_note.

subject_id (guardian-consent seam): if a seed slice carries a non-owner subject's data across into the shared KB, name them — the crossing then needs a verified kb_promotion grant. Opaque, never written. Empty for the owner's own agent seed (the usual case). A committed promotion is logged to the subject's disclosure chain.

schema_confirm_mappingA

Confirm a table's schema mapping, unlocking write tools for it (knowledge_ingest, kb_journal, kb_promote today). overrides lets you correct individual canonical-field -> real-column assignments before confirming, e.g. {"source": "origin_ref"} or {"tags": null} to explicitly mark a field unmapped. Gated separately from knowledge_write — confirming a mapping is a more consequential act than a single write.

preview=True: dry-run — return the proposed mapping AND a rendered sample row (what each canonical field actually resolves to) WITHOUT confirming or writing. Review the sample first: a column that name-matches but holds the wrong data — e.g. a content column that is really a provenance blob, with the real text in title/summary — reveals itself in the sample where a name match cannot. preview=False (default): confirm, and include the sample in the response so the confirmation is never blind.

kb_startup_continuityA

Fetch the knowledge atoms marked for session-startup continuity — domain 'continuity' or tagged "continuity" — newest first, up to limit. Call early in a session to recover standing decisions and in-flight state. The result always includes _continuity_filter showing exactly what was searched, so an empty list is legible. Read-only.

agent_routeA

Record a routing decision — "this task goes to that agent" — in the fleet's routing_decisions ledger and return a routing_id for correlating the eventual outcome. Does NOT start or notify the target agent: pair with dispatch_send to actually deliver the work, and report completion via agent_dispatch_result.

agent_dispatch_resultA

Close the loop on an agent_route call: attach the outcome — result text plus status (e.g. 'done', 'failed') — to the routing_decisions row named by routing_id. Returns {routing_id, status}, or {error: not_found} for an unknown routing_id.

dispatch_sendA

Create a dispatch packet assigning work to another agent: writes meta.json + assignment.md with status 'pending' under $WILLOW_HOME/dispatch/. assignment_md is the full brief the specialist will read; reply_to names who verifies the handoff. The packet advances pending → working → complete via dispatch_accept and handoff_write_v4. Filesystem-backed unless an authority envelope governs verb "dispatch" for this caller (#333) — then citing that envelope (and its quota) gates the write, Postgres-backed for that one check. Returns the new packet's metadata including its dispatch_id.

dispatch_readA

Read one dispatch packet by ID: its meta (from/to app, role, phase, priority), current status, and the full assignment.md body. Read-only — how a specialist sees its brief before calling dispatch_accept. Returns {error: not_found} for an unknown dispatch_id, or {error: not_party_to_dispatch} if app_id is neither from_app, to_app, reply_to, nor the orchestrator (B-54, issue #242 -- dispatch_read permission alone used to let any holder read any dispatch_id's full content).

dispatch_listA

List dispatch packets newest-first, filtered by any combination of to_app / from_app / status ('' = no filter). Returns packet metadata only, no assignment bodies — use dispatch_read for one packet's brief. Read-only. Returns {dispatches, total, unverified, unverified_total} -- dispatches only carries packets whose meta.json HMAC signature verified (B-52, issue #241); a packet with no signature (legacy, pre-dates signing) or a wrong one (tampered/forged) is never mixed into it, instead appearing in unverified with unverified: true and a signature_status of legacy_unsigned or invalid.

dispatch_acceptA

Accept a dispatch packet addressed to you: flips its status pending → working and records your session_id against it. Refuses if the packet is addressed to a different app (wrong_recipient) or is not currently pending (invalid_transition). Read the brief with dispatch_read first; close out with handoff_write_v4 when the work is done.

handoff_write_v4A

Close out a dispatch you accepted: writes handoff.json (the structured findings list) plus closeout.md (the narrative) into the packet and flips its status to complete. checklist_resolved and envelope_clean are your declarations that the assignment checklist is finished and no authority envelope was left open — the orchestrator checks both in verify_handoff before releasing you via agent_clear.

handoff_readA

Read the closeout of a completed dispatch: the structured handoff.json findings plus the closeout.md narrative. What the orchestrator reads before verify_handoff, and what a successor agent reads to pick up the thread. Read-only. Returns {error: not_party_to_dispatch} if app_id is neither from_app, to_app, reply_to, nor the orchestrator (B-54, issue #242 -- same packet-party check as dispatch_read).

verify_handoffA

Orchestrator-side check of a completed dispatch's handoff: confirms the closeout exists and its declarations hold — checklist resolved, envelope clean, findings present. Run this before agent_clear releases the specialist for its next packet. Read-only.

agent_clearA

Release a specialist after its handoff has been verified: clears target_app's live assignment state for dispatch_id, leaving the agent ready for the next packet. The final step of the dispatch lifecycle (send → accept → handoff → verify → clear); orchestrator-side.

session_readA

Read the thin per-session state file for an app/session pair — entry mode, bound dispatch, project/workspace — as written by session_enter. Returns {error: not_found} for an unknown session. Read-only.

session_enterA

FIRST CALL of any session. Registers the app/session pair, resolves the entry mode (human seat vs dispatched specialist — pass dispatch_id when entering to work an assigned packet), and returns orientation: project info, ORIENT.md presence, standing records (stack, portfolio, milestones, commitments, governance flags), the latest project handoff, collection aliases, and FRANK ledger presence. Close the session later with session_handoff_write (human path) or handoff_write_v4 (dispatch path).

session_handoff_writeA

Close a human-entry session with a project-scoped markdown closeout: narrative (what happened), summary, structured findings, and next_bite — the suggested next step for whoever resumes. The human-path sibling of handoff_write_v4, which closes dispatched work and requires a dispatch_id; this one needs only the session.

specialist_listA

List the specialist registry (config/specialists.json): every agent the orchestrator can route work to, with role metadata and — when include_permissions=true — each one's permission set. Returns {registry, specialists, total}. Read-only; use specialist_get for one agent's full row and persona.

specialist_getA

Fetch one specialist's registry row by agent_id, including its compiled persona text and persona file path when available. Returns {error: not_found} for an unknown agent. Read-only — how an orchestrator inspects a specialist before dispatching work to it.

exposure_config_getA

Read the standing exposure defaults from $WILLOW_HOME/config/ exposure.json — which preset governs each seed destination when exposure_slice is called without an explicit one. Returns {format, path, exists, config}; exists=false means no config file, so built-in defaults apply. Read-only (AS-8).

exposure_sliceB

Resolve and apply an exposure preset for a seed destination (AS-8).

destination: session_enter | kb_ingest | agent_seed_mirror | grove | cloud_llm | dispatch. preset: override standing default. fields: custom dotted paths (checkbox IDs).

agent_seed_mirrorA

Mirror a ratified home seed into SOIL collection willow_agents_seeds (AS-5).

Requires ratified status; when WILLOW_PGP_FINGERPRINT is set the detached .sig must verify. slice: full | voice_only | work_context (omit for exposure.json default).

fleet_statusA

Read the canonical fleet roster (fleet.json) — every registered agent and its declared state — annotated with drift diagnostics wherever the fleet Postgres disagrees with the file. Read-only; requires Postgres. Use fleet_health for liveness signals rather than roster membership.

frank_readA

Read recent entries from the FRANK governance ledger — the fleet's hash-chained, Postgres-backed audit chain — newest first, optionally filtered to one project. Each entry carries its prev_hash/hash links; use frank_verify to check chain integrity rather than eyeballing them. limit must be 1–500. Read-only.

frank_verifyA

Re-hash the entire FRANK governance chain and verify every prev_hash → hash link, detecting tampering, edits, or gaps. Returns the verification verdict, including where the chain breaks if it does.

Also checks the chain's head against the externally-held anchor at $WILLOW_HOME/constitutional/frank_head_anchor.json when one exists (#280): a chain can be internally consistent (every link valid) and still not be the same chain it was yesterday — that's what an edit-then-rechain() relink looks like from outside the database. anchor_status reports which case applied: "anchored" (compared; valid reflects both internal consistency AND the head match), "unanchored" (no anchor file — most installs, opted out), "untrusted" (anchor file failed the ownership/permission trust check), or "unreadable" (missing/malformed). Only "anchored" means the head was actually compared; the other three are reported explicitly rather than silently treated as a pass. Use willow-mcp frank-anchor (CLI-only — never an MCP tool, so an agent cannot mint its own anchor) to create or refresh one. Read-only; may take a moment on a long ledger.

frank_appendA

Append one event to the shared FRANK governance ledger: project + event_type + an object content, hash-chained onto the previous entry. Append-only — entries can never be edited or deleted afterwards, so write settled facts, not drafts. Returns {id, project, event_type}.

envelope_applyA

Check an authority envelope — an operator-granted, scoped permission — BEFORE acting under it: verifies envelope_id is active and covers verb with the given call_args, then appends a citation to the FRANK ledger recording the use against project/session. Refuses with the reason when the grant is missing, expired, or out of scope.

#333 double-charging note: verb in VERB_LEVEL_ENFORCED_VERBS (currently just "dispatch") now cites itself from inside its own tool handler (dispatch_send) — that handler is authoritative for the act. Calling envelope_apply first for one of those verbs and then performing the act would charge the grant's quota twice for one real act, so for those verbs this tool is advisory-only: it still runs the full check() (so a caller can preflight "would this be granted?" without side effects) but does NOT write a citation — cited_before_act comes back False and note says so. The verb handler cites exactly once, at the act, regardless of whether envelope_apply was called first. Every other verb is unchanged: still checks AND cites here, since no other verb has a handler-side citation path yet.

fleet_healthA

Fleet health — task queue counts by status, plus live worker heartbeats.

A queue depth is only half the picture: pending tasks with no live worker are not "queued", they are stranded. workers reports every process publishing a heartbeat, and stranded is true when there is pending work and nothing running to drain it.

context_saveA

Save ephemeral working state under key, optionally expiring after ttl_seconds (0 = never). Overwrites an existing key. Per-identity — scoped to your app_id. Backed by the SOIL store, so no Postgres is required.

context_getA

Read a saved context by key. Returns {error: not_found} if absent, or {error: expired} (and purges it) if its TTL has passed.

context_listA

List your saved context keys with save/expiry times (values omitted — use context_get). Expired entries are skipped and purged.

context_expireA

Delete one of your saved contexts immediately, ahead of any TTL. Returns {expired: true} if a record was removed, false if the key didn't exist. Scoped to your app_id — you cannot expire another app's context.

integration_listA

List every integration adapter — live and declared stubs — with status, credential source (env/vault, never the value), and, for stubs, what is missing and what earns the implementation.

integration_statusA

Offline readiness readout for one integration: live or stub, credential presence, and whether the three-key egress gate would pass for this app right now. Makes no network call — ask this before asking for a lease.

integration_callA

Call an external API through a registered integration adapter.

Egress needs THREE keys, all held at once: the 'integration_net' capability in this app's manifest (own line — NOT granted by task_net, integration_call, or full_access), the operator's standing consent.internet, and an unexpired egress lease issued via willow-mcp grant-net. Declared stubs refuse with what is missing and what earns their implementation.

federation_discoverA

Shadow-IT scan: .mcp.json files under root (default: this host's home directory, or $WILLOW_MCP_FEDERATION_SCAN_ROOT) that the ratified registry does not yet own. Read-only — never parses an entry into a connectable spec and never ratifies anything. "Which MCP servers exist that willow-mcp does not know about" is the first question an orchestrator must answer before it can federate at all.

federation_list_serversA

List every operator-ratified downstream MCP server: id, name, launch command/args, the environment-variable NAMES it receives (never values), who ratified it and when. Never includes a discovered-but-unratified server — connecting requires ratification first (see federation_discover).

federation_callA

Call one tool on one ratified downstream MCP server.

Authorized only at the intersection of two ceilings (docs/design/ federated-mcp-gating.md Decision 2): this app's manifest must grant BOTH the 'mcp_federation' capability (own line — spawning a server at all) AND the namespaced mcp:<server_id>:<tool> permission (this specific tool on this specific server) — plus the operator's standing consent.federation and an unexpired egress lease, same as every other egress lane. The server itself must be in the operator-ratified registry regardless of what this app's manifest grants; a manifest grant alone can never make an unratified server reachable.

The downstream tool's result is scanned by external-guard and sandwich-wrapped if flagged (untrusted output, same treatment willow_web_fetch gives fetched pages) before it comes back.

receipts_tailA

Return your own most-recent tool-call receipts, newest first: ts, tool, outcome (ok/denied/rate_limited/error), detail. Scoped to your app_id — a self-audit trail, never another identity's calls.

whoamiA

Report who you are and what you may do: your app_id, role, the permission groups your manifest grants, the resolved set of tools you can actually call (group expansion minus any deny_tools), your store_scope, and whether you're a human-only seat. Read-only and self-scoped — your own manifest, never another identity's: in serve mode the app_id comes from your OAuth binding, and in stdio under enforcement you must prove you own the app_id you pass (a valid per-call credential), so whoami can't enumerate another bound agent's config. Ungated otherwise, like diagnostic_summary, so it still answers when your manifest is empty or missing (it says exactly that).

diagnostic_summaryA

Self-check: is this willow-mcp install wired correctly? Reports the SOIL store (path/writable/collections), Postgres (reachable + which database + whether willow-mcp's tables are present), schema-mapping confirmation state, your app_id's manifest + resolved permissions, identity bindings, whether a task worker is alive to drain the queue, your egress lease and which of the keys authorizing it this process could forge, whether this install is still wired to a fleet it claims to be severed from, and the config-bearing environment — then a verdict (ok/degraded/broken) with named problems and fixes. Ungated on purpose: it must answer even when your manifest or database is misconfigured. Reveals only your own config, never fleet rows or vault secrets.

Severance is asserted, never assumed: name a fleet with WILLOW_MCP_FLEET_HOME and WILLOW_MCP_FLEET_PG_DB and every shared surface becomes a named problem. Name none and the check reports not_asserted and changes nothing.

commitment_ingestA

Ingest calendar events into the operator's commitment ledger and persist the result (SOIL collection willow/commitments). Read-only reconcile: a new event becomes an ACTIVE commitment, a cancellation a WITHDRAWN state, a moved event keeps its old time in history — nothing is deleted (states-not-deletions). Only the FACT is stored (title/when/who/state); the event body/notes/location are read to derive the fact then DROPPED, never persisted (receipt-not-recording).

events: a list of {uid, title, start, end?, attendees?, body?, cancelled?} — the operator/integration push path (start/end are ISO-8601). Omit it to pull from the live calendar source; that transport (gcal OAuth) is a home-box step, so until then an omitted events returns transport_unwired rather than inventing data.

This tool NEVER writes the calendar back — a cancel/reschedule is a proposal routed through the SAFE gate, which is deliberately not exposed over MCP (no new authority).

commitment_acknowledgeA

Mark a commitment change as seen by the operator — the split-stick halves match again. Recorded as an 'acknowledged' history entry (never erased) and it stops surfacing as a 'mismatch'. Returns unknown_uid if there is no such commitment.

commitment_surfaceA

The dew-rule view: what — if anything — deserves the operator's attention right now. Silent by default; it speaks only when the split-stick halves disagree — a commitment imminent (starting within lead_minutes), two active commitments in conflict, or a change not yet acknowledged (a 'mismatch'). Each surfacing carries title + time only, never the event body. Read-only.

now: ISO-8601 instant to evaluate against (default: current UTC).

commitment_listA

List the operator's persisted commitments as FACTS only (uid/title/when/who/ state/acknowledged/history length) — never the event body. Read-only. state filters to ACTIVE or WITHDRAWN (case-insensitive); omit for all.

code_graph_indexA

Index a repository's Python + JS/TS files into a local SQLite symbol graph (symbols, import/inherit edges, per-file stats). Run once before the read tools. Pure stdlib ast — no network, no Postgres, no external CLI.

repo_root (required): the directory to index. db_path: override the graph DB location (default $WILLOW_HOME/code_graph/graph.db, or WILLOW_CODE_GRAPH_DB). force: reserved (the indexer upserts, so re-running is already idempotent).

code_graph_searchA

Fuzzy symbol search over the graph: exact → prefix → contains → camelCase/snake_case token split. kinds filters by symbol type (module|class|function|method); omit for all. Read-only.

code_graph_explainA

Explain a symbol: signature, file location, callers (inbound edges), and callees (outbound). symbol is a name or fully-qualified name (e.g. 'permitted' or 'willow_mcp.gate.permitted'). Read-only.

code_graph_walkA

BFS from an anchor symbol along import + inherit edges, collecting context until a token budget is hit. Deterministic (alphabetical within each hop). anchor: symbol name or fqn. Read-only.

code_graph_suggestA

Suggest the files most relevant to a task description, ranked by keyword overlap with symbol names + file paths. No embeddings, no LLM. Read-only.

code_graph_impactA

Blast radius: which files/symbols import from the given files? file_paths are repo-relative (e.g. ['src/willow_mcp/gate.py']). Read-only.

willow_web_searchA

Open web search via DuckDuckGo HTML (no API key). Returns title, url, snippet, source, and hostname per result. Use for current events, tech news, and queries that need the live open web.

trusted_only: keep verified institutional domain suffixes only. include_handoffs: prepend OpenStreetMap/Google Maps links for navigational queries. Requires web_net + consent.internet + a live egress lease.

willow_institutional_searchA

Search ~60 named institutional and academic collections — arXiv, PubMed, Crossref, OpenAlex, Library of Congress, Europeana, CourtListener, the Smithsonian — and return citable results.

Every hit carries confidence: "institutional" because a named collection was actually queried, not because its hostname looked reputable. Use this when a claim needs backing; use willow_web_search for the open web.

Read ok before hits: ok true with no hits means the collections had nothing, ok false means no source completed a look. sources_queried, failed, skipped and timed_out say which is which. sources narrows the fan-out to specific registered ids.

max_results caps the returned hits; total is the count before that cap, so a caller can tell whether there was more. limit_per_source is jeles' own knob and is per collection, not overall — with ~60 sources the default of 3 can produce far more hits than max_results keeps.

Requires web_net + consent.internet + a live egress lease.

willow_web_fetchA

Fetch a URL through external-guard (not native WebFetch).

Returns text with sandwich defense when wrap=True. Blocks private, loopback and other non-public destinations — resolving names, not just matching them — and re-checks every redirect hop rather than the first URL alone; the chain taken comes back in redirects. Use willow_web_search to discover URLs first. Requires web_net + consent.internet + a live egress lease.

fork_createA

Open a fork — a named, bounded unit of work (feature branch + PR tracking) recorded in the SOIL store. Snapshots selected environment variables so env_check can later spot drift between where the fork was opened and where it's being worked. fork_id pins an explicit ID; omit it to auto-generate. Log changes with fork_log; close with fork_merge or fork_delete.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rudi193-cmd/willow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server