MCP server for .fafm — the Permanent Memory Layer (PML). Wraps claude-fafm-sdk via fastmcp. IANA-registered, cross-vendor, offline-first.
Five tools with clear verb-noun naming (etch, recall, list_facts, save_soul, load_soul). All have descriptions (avg 150 chars, within 10-1024 baseline). Input schemas present with typed parameters and descriptions. Output schemas documented via return type hints. Key strengths: deterministic filtering, idempotent operations, persistence guarantees. Gaps: no enums for priority/min_priority (free-form strings invite hallucination), no pagination for list_facts (could return unbounded results), error handling in load_soul returns bare dict instead of structured error with recovery guidance.
Write a durable fact to .fafm memory and persist it to disk immediately. If ``id`` matches an existing fact, it's updated in place (O(1) dedup); otherwise appended. Survives process exit — writes $FAF_SOUL_PATH (default ./soul.fafm).
List ALL facts in the current soul (no filter, no rank).
Load a .fafm soul from disk, replacing the current in-memory soul.
Recall facts — deterministic filter (substring on text, type equality, tag intersection, priority floor) ranked by priority then recency.
Persist the current soul to a .fafm file.
Priority and min_priority parameters accept free-form strings instead of enums. LLMs may hallucinate invalid values like 'urgent' or 'max' instead of the documented set ('ephemeral', 'standard', 'high', 'critical'). Enums are self-documenting and prevent invalid input.
list_facts() has no pagination (limit, offset, cursor) and no result count cap. Returning all facts unbounded risks context window exhaustion if the soul grows large. Baseline: cap at 20-50 items and offer pagination.
load_soul() error response returns bare dict {'error': '...'} instead of structured error with recovery guidance. LLM cannot determine if error is retryable or user-fixable. Should return {'status': 'error', 'code': 'file_not_found', 'message': '...', 'recovery': 'Check FAF_SOUL_PATH or provide explicit path parameter'}.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 60 | 2026-07-28+ | v2 |
No tool supports dry-run or confirmation for destructive operations (etch with id=existing_fact overwrites, load_soul replaces in-memory soul). Agents make mistakes, confirmation pattern prevents data loss.
recall() limit parameter defaults to 20 but accepts null for 'all'. Unbounded results risk context explosion. Baseline: enforce a hard cap (e.g. 100) and document it in description.