Atlas / MCP servers / willow-memory / willow-mcp

willow-mcpCAUTION

mcp/willow-memory/willow-mcp

Agent-neutral MCP server with persistent memory (SOIL + Postgres KB) and a sandboxed task queue. Manifest-based ACL; works with any stdio MCP client.

Verdict
CAUTION
Grade
F
Trust score
49 /100
Exposed tools
39 34r · 5w · 0d
Transport
stdio · streamable-http
License
Apache-2.0
Stars
3
01

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

Read from source at commit ab0db57d3726OBSERVED · 2026-10-11
02

Connect

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-code
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
claude-desktop
{
  "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}"
      }
    }
  }
}
03

Exposed tools (39)

34 read · 5 write · 0 destructive.

ToolRiskDescription
corpus_hitsreadreturn {
corpus_searchreadreturn {
diagnostic_summarywriteSelf-check: is this willow-mcp install wired correctly? Reports the SOIL
echoreadobserved[
grove_ackread
grove_agentsread
grove_bus_receiveread
grove_bus_sendwrite
grove_flagread
grove_flaggedread
grove_fleet_statusread
grove_get_historyread
grove_get_identityread
grove_get_threadread
grove_heartbeatread
grove_human_requiredread
grove_inboxread
grove_list_channelsreadList all active Grove channels (name, type, description). Requires
grove_replyread
grove_searchread
grove_send_messagewrite
grove_unflagread
grove_watchread
grove_watch_allread
mai_call_macroreadCall a named macro in a document. Requires markdownai_read (#161).
mai_execute_directivewrite
mai_get_constraintsreadGet all @constraint declarations from a MarkdownAI document, sorted by severity.
mai_get_envreadGet an environment variable value.
mai_invalidate_cachereadInvalidate the directive cache. Pass directive to invalidate a specific entry.
mai_list_phasesreadList all phases in a MarkdownAI document. Requires markdownai_read (#161).
mai_next_phasereadGet the next phase after current_phase. Requires markdownai_read (#161).
mai_read_fileread
mai_resolve_phasereadResolve a named phase in a document — returns its content. Requires markdownai_read (#161).
mai_write_filewrite
probereadWhat this fixture saw — asserted by the test after the client acts.
session_bindreadobserved[
session_reconcilereadobserved[
suspiciousreadreturn
whoamireadReport who you are and what you may do: your app_id, role, the permission
04

Trust audit

CAUTIONgrade F · trust 49/100 Install with care. The audit found things worth knowing before you trust its output.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeWARN
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (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)

MEDIUMCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
scripts/fleet-standup.sh:112
m = importlib.import_module(mod)
MEDIUMCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
src/willow_mcp/hook_runner.py:57
return getattr(importlib.import_module(modpath), attr)
MEDIUMCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
src/willow_mcp/voice/daemon.py:65
module = importlib.import_module(module_name)
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
src/willow_mcp/model_pull_executor.py:57
DEFAULT_DAEMON = "http://127.0.0.1:11434"
MEDIUMHard-coded secrets · secret.aws · CWE-798, CWE-321
tests/test_nest_autointake.py:62
leaky.write_text("total due\naws_key=AKIAABCDEFGHIJKLMNOP\n")  # ...but leaks a secret
MEDIUMHard-coded secrets · secret.aws · CWE-798, CWE-321
tests/test_nest_autointake.py:119
pdf.write_bytes(b"%PDF-1.4\n\xff\xfe\x00\x01 AKIAABCDEFGHIJKLMNOP \xff\xfe\n")
MEDIUMHard-coded secrets · secret.aws · CWE-798, CWE-321
tests/test_nest_autointake.py:136
leaky_name = drop / "invoice-AKIAABCDEFGHIJKLMNOP.md"
MEDIUMHard-coded secrets · secret.aws · CWE-798, CWE-321
tests/test_nest_autointake.py:143
held = next(h for h in result["held"] if h["filename"] == "invoice-AKIAABCDEFGHIJKLMNOP.md")
MEDIUMHard-coded secrets · secret.aws · CWE-798, CWE-321
tests/test_nest_autointake.py:209
big.write_bytes(b"aws_key=AKIAABCDEFGHIJKLMNOP\n" + padding)
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
tests/test_unit_status.py:466
journal_text = "2026-09-27T10:00:00 DSN=postgresql://willow:p@[email protected]/willow tail\n"
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
tests/test_integrations.py:175
secret = "ghp_supersecret12345"
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
tests/test_github_app_credentials.py:27
"-----BEGIN PRIVATE KEY-----\nQQ==\n-----END PRIVATE KEY-----\n"
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
tests/test_lease.py:385
key.write_text("-----BEGIN PRIVATE KEY-----\n...")
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
tests/test_lease.py:395
key.write_text("-----BEGIN PRIVATE KEY-----\n...")
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
tests/test_nest_intake.py:61
f.write_text("-----BEGIN PRIVATE KEY-----\nx\n-----END PRIVATE KEY-----\n")
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
tests/test_secret_scan.py:19
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
MEDIUMHard-coded secrets · secret.slack · CWE-798, CWE-321
tests/test_secret_scan.py:14
SLACK_TOKEN = "xoxb-123456789012-abcdefABCDEF"
LOWInventory / provenance · inv.hidden_file · CWE-1104
.mcp.json.example
.mcp.json.example
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.pre-commit-config.yaml
.pre-commit-config.yaml
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.release-please-manifest.json
.release-please-manifest.json
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_package_upgrade.py:1599
__import__(top_level)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_unit_install.py:1593
mod = importlib.import_module(f"willow_mcp.{module}")
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
src/willow_mcp/nest/intake.py:76
return "itm-" + hashlib.sha1(src.encode(), usedforsecurity=False).hexdigest()[:10]
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
src/willow_mcp/nest/intake.py:165
return hashlib.md5(
LOWFilesystem / path · fs.credential_store · CWE-22, CWE-59
tests/test_node9_shadow.py:321
("ssh_key_path", "cat ~/.ssh/id_ed25519", "id_ed25519"),
Why it matters. touches a credential store

Gates applied: no_behavioural_pass.

Audited 2026-10-11 · audit v0.4.1 · source sha ab0db57d3726full audit observations/trust-audit/mcp-server/willow-memory__willow-mcp.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-11ab0db57d3726CAUTIONF49first audit
06

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.

Advertisement