willow-mcpCAUTION
Agent-neutral MCP server with persistent memory (SOIL + Postgres KB) and a sandboxed task queue. Manifest-based ACL; works with any stdio MCP client.
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
[](https://pypi.org/project/willow-mcp/) [](LICENSE) [](https://modelcontextprotocol.io)
Agent-neutral MCP server with persistent memory and task execution. Works with any MCP client: Claude Code, Claude Desktop, Cursor, or any custom agent that speaks stdio MCP.
Corpus memory: the whole Willow constellation — the code, human, and collaboration corpora assembled and queryable — lives in the sovereign `willow-compose` repo. The hub calls it; it doesn't live here (keeping the hub lean).
Three storage backends in one server:
- SOIL store — SQLite-backed local key/value store with full-text search and soft delete
- Postgres knowledge base — multi-keyword searchable knowledge graph
- Kart task queue — sandboxed task executor for shell commands and scripts
Every tool call is authorized via a filesystem-based manifest ACL — no ACL database, no external auth service. See Authorization.
Install
pip install willow-mcp
Requires Python 3.11+. Postgres is optional — SOIL store works standalone.
willow-mcp-init # scaffold $WILLOW_HOME (idempotent) willow-mcp-compile --force # compile manifests (use product venv — see below) willow-mcp-sign-seed hanuman # ratify home seed + detach-sign (operator terminal only) willow-mcp-compile-persona hanuman # seed → personas/hanuman.md (AS-7)
Local sandbox (one command)
To take a fresh clone to a working stdio server — venv, editable install, scaffolded $WILLOW_HOME, compiled manifests, and (best-effort) a local Postgres with every table created — run:
bash scripts/sandbox-bootstrap.sh # idempotent; ends with a live diagnostic_summary
On a bootstrapped sandbox the schema mappings for the tables the scri
ab0db57d3726OBSERVED · 2026-10-11Connect
Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.
claude mcp add willow-mcp --env BINDING_FIXTURE_SECRET=${BINDING_FIXTURE_SECRET} --env BRAVE_API_KEY=${BRAVE_API_KEY} --env GITHUB_APP_PRIVATE_KEY_PATH=${GITHUB_APP_PRIVATE_KEY_PATH} --env WILLOW_KEYRING=${WILLOW_KEYRING} -- uvx willow-mcp{
"mcpServers": {
"willow-mcp": {
"command": "uvx",
"args": [
"willow-mcp"
],
"env": {
"BINDING_FIXTURE_SECRET": "${BINDING_FIXTURE_SECRET}",
"BRAVE_API_KEY": "${BRAVE_API_KEY}",
"GITHUB_APP_PRIVATE_KEY_PATH": "${GITHUB_APP_PRIVATE_KEY_PATH}",
"WILLOW_KEYRING": "${WILLOW_KEYRING}"
}
}
}
}Exposed tools (39)
34 read · 5 write · 0 destructive.
| Tool | Risk | Description |
|---|---|---|
corpus_hits | read | return { |
corpus_search | read | return { |
diagnostic_summary | write | Self-check: is this willow-mcp install wired correctly? Reports the SOIL |
echo | read | observed[ |
grove_ack | read | |
grove_agents | read | |
grove_bus_receive | read | |
grove_bus_send | write | |
grove_flag | read | |
grove_flagged | read | |
grove_fleet_status | read | |
grove_get_history | read | |
grove_get_identity | read | |
grove_get_thread | read | |
grove_heartbeat | read | |
grove_human_required | read | |
grove_inbox | read | |
grove_list_channels | read | List all active Grove channels (name, type, description). Requires |
grove_reply | read | |
grove_search | read | |
grove_send_message | write | |
grove_unflag | read | |
grove_watch | read | |
grove_watch_all | read | |
mai_call_macro | read | Call a named macro in a document. Requires markdownai_read (#161). |
mai_execute_directive | write | |
mai_get_constraints | read | Get all @constraint declarations from a MarkdownAI document, sorted by severity. |
mai_get_env | read | Get an environment variable value. |
mai_invalidate_cache | read | Invalidate the directive cache. Pass directive to invalidate a specific entry. |
mai_list_phases | read | List all phases in a MarkdownAI document. Requires markdownai_read (#161). |
mai_next_phase | read | Get the next phase after current_phase. Requires markdownai_read (#161). |
mai_read_file | read | |
mai_resolve_phase | read | Resolve a named phase in a document — returns its content. Requires markdownai_read (#161). |
mai_write_file | write | |
probe | read | What this fixture saw — asserted by the test after the client acts. |
session_bind | read | observed[ |
session_reconcile | read | observed[ |
suspicious | read | return |
whoami | read | Report who you are and what you may do: your app_id, role, the permission |
Trust audit
CAUTIONgrade F · trust 49/100 Install with care. The audit found things worth knowing before you trust its output.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | WARN |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (5 observation(s))
- Network
- declared (4 observation(s))
- Shell
- declared (4 observation(s))
- Dependencies
- pinned
- Secrets in source
- found
Findings (25)
m = importlib.import_module(mod)
return getattr(importlib.import_module(modpath), attr)
module = importlib.import_module(module_name)
DEFAULT_DAEMON = "http://127.0.0.1:11434"
leaky.write_text("total due\naws_key=AKIAABCDEFGHIJKLMNOP\n") # ...but leaks a secretpdf.write_bytes(b"%PDF-1.4\n\xff\xfe\x00\x01 AKIAABCDEFGHIJKLMNOP \xff\xfe\n")
leaky_name = drop / "invoice-AKIAABCDEFGHIJKLMNOP.md"
held = next(h for h in result["held"] if h["filename"] == "invoice-AKIAABCDEFGHIJKLMNOP.md")
big.write_bytes(b"aws_key=AKIAABCDEFGHIJKLMNOP\n" + padding)
journal_text = "2026-09-27T10:00:00 DSN=postgresql://willow:p@[email protected]/willow tail\n"
secret = "ghp_supersecret12345"
"-----BEGIN PRIVATE KEY-----\nQQ==\n-----END PRIVATE KEY-----\n"
key.write_text("-----BEGIN PRIVATE KEY-----\n...")key.write_text("-----BEGIN PRIVATE KEY-----\n...")f.write_text("-----BEGIN PRIVATE KEY-----\nx\n-----END PRIVATE KEY-----\n")"-----BEGIN OPENSSH PRIVATE KEY-----\n"
SLACK_TOKEN = "xoxb-123456789012-abcdefABCDEF"
.mcp.json.example
.pre-commit-config.yaml
.release-please-manifest.json
__import__(top_level)
mod = importlib.import_module(f"willow_mcp.{module}")return "itm-" + hashlib.sha1(src.encode(), usedforsecurity=False).hexdigest()[:10]
return hashlib.md5(
("ssh_key_path", "cat ~/.ssh/id_ed25519", "id_ed25519"),Gates applied: no_behavioural_pass.
ab0db57d3726full audit observations/trust-audit/mcp-server/willow-memory__willow-mcp.json · Report an issue / request a re-scanAudit history
Every audit this server has had. A grade with a past is a grade somebody is still checking.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-11 | ab0db57d3726 | CAUTION | F | 49 | first audit |
Questions
What is the willow-mcp MCP server?
Agent-neutral MCP server with persistent memory (SOIL + Postgres KB) and a sandboxed task queue. Manifest-based ACL; works with any stdio MCP client.
What tools does willow-mcp expose?
39 in total: 34 read-only, 5 that write, and 0 that can delete or overwrite. Every one is listed on this page with its risk.
Is willow-mcp safe to connect to an agent?
With care. The audit graded it F (49/100) and found 25 things worth knowing before you trust this server, listed below with the exact line each was found on.
What credentials does willow-mcp need?
It reads BINDING_FIXTURE_SECRET, BRAVE_API_KEY, GITHUB_APP_PRIVATE_KEY_PATH, WILLOW_KEYRING, WILLOW_MCP_AUTHORITY_CHECK, WILLOW_MCP_EGRESS_PUBLIC_KEY and WILLOW_MCP_EGRESS_SIGNING_KEY from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.
How does willow-mcp run?
It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on PyPI as willow-mcp.
How current is this page?
The grade is for one exact copy of the source (ab0db57d3726), read on 2026-10-11. The repository is watched and re-audited when it changes.