{"slug": "i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local", "title": "I gave Claude Code a memory for my corrections (a 200-line MCP server on local Postgres)", "summary": "A developer built feedback-memory, a roughly 200-line MCP server backed by local PGlite Postgres with pgvector that stores past approvals, edits and rejections along with the reasons behind them, then returns the most similar past decisions before an agent drafts new text. The server exposes five tools (recall_corrections, record_decision, add_rule, list_memory, forget) and ships as a Claude Code plugin with hooks that trigger recall and recording automatically, since the developer found that agents often ignore tool descriptions alone. In a test run, the agent stored a correction about avoiding hype and exclamation marks and produced the release note 'v3: offline mode added.'", "body_md": "My coding agents kept making mistakes I had already corrected. Not big ones: an exclamation mark in a release note, a \"blazing fast\" in a changelog, a reply that promised a shipping time we don't have. I'd fix it, the session would end, and the next session would do it again, because the correction lived in a chat nobody reads twice.\n\n`CLAUDE.md` helps with rules that always apply. It's bad at the other kind: corrections that only matter in a similar situation. Put all of those in `CLAUDE.md` and it turns into a wall of special cases the agent skims.\n\nSo I wrote a small MCP server that does one thing: it remembers what I approved, edited or rejected, and why, and hands the closest past decisions back before the next draft.\n\nFive tools:\n\n`recall_corrections(task)`: call before drafting. Returns standing rules, then my past decisions on similar tasks, most similar first.`record_decision(task, decision, corrected_text?, reason?)`: call after I approve, edit or reject.` add_rule(rule)`: for preferences that always apply.` list_memory()` and `forget(id)`: see what's stored, delete what no longer applies.\nInstall in Claude Code as a plugin (more on why below):\n\n```\n/plugin marketplace add https://github.com/ssap-pa/self-learning-agent-setup.git\n/plugin install feedback-memory@ssap-pa\n```\n\nOr just the MCP server:\n\n```\nclaude mcp add feedback-memory -- npx -y github:ssap-pa/self-learning-agent-setup\n```\n\nIn Claude Desktop it's one click: download [`feedback-memory.mcpb`](https://github.com/ssap-pa/self-learning-agent-setup/releases/latest/download/feedback-memory.mcpb) and open it.\n\nEverything lives in a local Postgres in `~/.feedback-memory`. It's PGlite (Postgres compiled to WebAssembly) with pgvector, so there's no Docker and nothing to host.\n\nAn MCP tool the agent never calls is dead code. Two things made the difference.\n\n**The tool descriptions say when, not just what.** `recall_corrections` starts with \"Call this BEFORE you draft anything the user will review\", and `record_decision` with \"Call this right AFTER the user approves, edits or rejects something you drafted\". It also says the reason is what makes the next draft better.\n\n**Two lines in `CLAUDE.md`:**\n\n```\nBefore drafting anything I'll review, call recall_corrections with the task and follow what comes back.\nWhen I approve, edit or reject your draft, call record_decision with my reason.\n```\n\nBoth of those are still requests. The agent has to remember to call the tool, and that's the weak spot. So feedback-memory is also a Claude Code plugin with two hooks that don't ask:\n\n`/clear` or compaction.\nThe hook can't open the database while the server has it open (PGlite is single-process), so the server keeps a plain JSON snapshot next to the database and rewrites it after every change.\n\nIn a real run with the plugin, Claude made no tool calls at all and still answered a new \"how long does shipping to Canada take?\" comment with \"Thanks for asking. Shipping to Canada usually takes 5-7 business days for the lavender set.\": the shipping time from a past edit (reason: don't promise speed) and no exclamation marks, from a stored rule.\n\nI tested it with a fresh database and one prompt: \"Last time you drafted 'v2 is here!!! Blazing fast sync!!!' and I edited it to 'v2: sync is faster, fewer settings.' because I want short notes with no hype. Record that decision. Then draft a one-line release note for v3, which adds offline mode.\"\n\nClaude Code called `record_decision` and stored the edit with the reason \"I want short notes with no hype\". The part I didn't expect: it stored the task as \"Draft a one-line release note for a product version\" instead of something about v2 specifically, which is exactly what you want for matching future release notes. The v3 draft came back as \"v3: offline mode added.\" No exclamation marks.\n\n`OPENAI_API_KEY` and it matches by meaning (text-embedding-3-small). If you add the key later, it re-embeds what's already stored so old and new vectors stay comparable.`FEEDBACK_MEMORY_DIR`.\nIt's small on purpose. There's no UI beyond `list_memory`, word hashing misses paraphrases (the plugin's prompt hook always matches by shared words, even when the server has an OpenAI key), and it can't tell whether the agent actually followed what it recalled. That last one is the part I'd look at next.\n\n`mcp/`, `npm test` runs an end-to-end test over stdio):", "url": "https://wpnews.pro/news/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local", "canonical_source": "https://dev.to/ssapable/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local-postgres-3f76", "published_at": "2026-10-02 08:59:23+00:00", "updated_at": "2026-10-02 09:07:30.456382+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "agent-protocols", "developer-tools", "mlops"], "entities": ["Claude Code", "Claude Desktop", "MCP", "PGlite", "pgvector", "Postgres", "OpenAI", "feedback-memory"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local", "markdown": "https://wpnews.pro/news/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local.md", "text": "https://wpnews.pro/news/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local.txt", "jsonld": "https://wpnews.pro/news/i-gave-claude-code-a-memory-for-my-corrections-a-200-line-mcp-server-on-local.jsonld"}}