A local MCP server that gives Claude persistent memory of your DSA / HLD / LLD / Behavioral interview practice — backed by plain files on your disk that you can open and review anytime.
It knows a built-in catalog of ~230 problems (149 DSA, 30 HLD, 25 LLD, 29 Behavioral), tracks what you've attempted and when, and can recommend what to practice next: new problems, or ones due for revision, biased toward your flagged weak areas.
Session/tracking data, under ~/interview-prep/ by default:
revision.md— human-readable chronological log, one section per session. This is the file you review.index.json— compact machine index: sessions, aggregated weak areas, competency scores (Behavioral), and a per-problem tracker (times practiced, last verdict, last attempt date, absolute path to the linked solution/doc). Keeps Claude's context small.custom_catalog.json— problems you've added yourself (e.g. from a mock interview), merged with the built-incatalog.jsonshipped next toserver.py.
Per-problem solutions/docs live in four independent, separately configurable directories — each defaults to a folder under ~/interview-prep/docs/ but is normally pointed at wherever your real practice work lives:
DSA_SOLUTIONS_DIR(e.g.~/code/DSA_mock) — organized as<Topic_Folder>/<problem>.py, one real Python file per problem grouped by topic, matching a typical personal LeetCode-practice layout. DSA is code, not markdown — seesave_dsa_solutionbelow.HLD_SOLUTIONS_DIR(e.g.~/code/HLD) — one markdown file per HLD problem (e.g.design-rate-limiter.md).LLD_SOLUTIONS_DIR(e.g.~/code/LLD) — holds two separate things: the reference corpus of your own designs, one self-contained.pyper problem grouped into category folders (1_state_machine/vending_machine.py), plus flatdesign-parking-lot.mdwrite-ups fromsave_practice_doc; andMock Solutions/, the mock-interview loop where you attempt a problem and Claude grades it. See LLD mock interviews below.BEHAVIORAL_SOLUTIONS_DIR(e.g.~/code/Behavioral) — holds a singlecandidate_context.md: your reusable background + STAR story bank, with a "Current Focus" section you refresh per company/role. Seesave_candidate_contextbelow.
| Tool | When Claude uses it |
|---|---|
get_progress_summary |
Start of every session — session table, weak areas, competency scores, catalog coverage per type |
get_catalog |
Browse the full problem pool for DSA/HLD/LLD/Behavioral, filterable by topic/difficulty/status, to find a problem's exact id |
suggest_next_problems |
"What should I practice next?" — ranks due-for-revision and weak-area-matching problems above fresh new ones |
add_custom_problem |
Add a problem not in the built-in catalog (company-specific, from a mock interview, a real behavioral question you were asked, etc.) |
log_session |
End of every session, all types — appends to revision.md, updates weak areas and the per-problem tracker; optionally records per-competency scores (1-5) and improvement suggestions |
save_practice_doc |
HLD/LLD only — after a design discussion, writes its own markdown file for quick revision |
get_practice_doc |
Fetch one saved HLD/LLD doc in full, e.g. to revise a specific past solution |
list_practice_docs |
List all saved HLD/LLD docs (title, topic, last updated) as a revision menu |
save_candidate_context |
Behavioral only — persist your reusable candidate profile + STAR story bank as one markdown file, overwriting the previous version |
get_candidate_context |
Fetch the saved candidate context in full, e.g. at the start of a behavioral mock session |
scan_dsa_directory |
Read DSA_SOLUTIONS_DIR — folder/filename/problem-statement-snippet/last-modified for every .py file, flagging which are already tracked |
import_solved_dsa_problem(s) |
Backfill the tracker from an existing file on disk (matched to a catalog id) without touching the file — uses its mtime as "last practiced" |
save_dsa_solution |
After solving a NEW DSA problem — writes a .py file into the right topic folder in DSA_SOLUTIONS_DIR; refuses to overwrite an existing file unless told to |
scan_lld_directory |
Read LLD_SOLUTIONS_DIR — folder/filename/kind/snippet/last-modified for every file in the reference corpus, flagging which are already tracked. Only kind=solution rows are per-problem designs; Mock Solutions/ is excluded |
read_lld_solution |
Open ONE LLD file in full — a past design, a corpus doc, or a mock attempt you're about to grade |
import_solved_lld_problem(s) |
Backfill the tracker from an LLD design already on disk, without touching the file. Refuses anything that isn't kind=solution |
save_lld_solution |
Write a NEW design into the reference corpus, in the right category folder; refuses to overwrite unless told to |
start_mock_attempt |
Pose an LLD problem — creates Mock Solutions/<id>/ with problem.md and an attempt.py stub for you to fill in. Never overwrites an existing attempt; re-posing opens the next round |
list_mock_attempts |
Every mock problem and round: which files exist, what's still awaiting grading, scores and verdicts |
save_mock_evaluation |
Grade an attempt against the fixed 7-dimension rubric — writes evaluation.md, logs the session, and regenerates feedback.md |
save_ideal_solution |
Write the interviewer's reference design as ideal.py, beside your attempt. Can only ever write ideal*.py, so it cannot touch your own file |
save_simple_solution |
Write a pared-back version of that design as simple.py — what a strong candidate could realistically finish in the time box. Can only ever write simple*.py |
get_lld_feedback |
Rubric averages, weakest dimensions, and attempt history — call this before suggest_next_problems("LLD") to aim the next question |
get_session_detail |
Revisit the full log entry for one past session |
resolve_weak_area |
Remove a weak area once you've demonstrably improved at it |
-
Keep this folder wherever you cloned it, e.g.
/Users/YOUR_USERNAME/code/file_mcp/.server.pyandcatalog.jsonmust stay next to each other — the catalog is loaded relative to the script. -
Install the MCP SDK (any Python ≥3.10):
pip3 install mcp
(If you use
uv:uv pip install mcp, and adjust the command below to your venv's python.) -
Open Claude Desktop → Settings → Developer → Edit Config. This opens
~/Library/Application Support/Claude/claude_desktop_config.json. Add:{ "mcpServers": { "interview-memory": { "command": "python3", "args": ["/Users/YOUR_USERNAME/code/file_mcp/server.py"], "env": { "INTERVIEW_PREP_DIR": "/Users/YOUR_USERNAME/interview-prep", "DSA_SOLUTIONS_DIR": "/Users/YOUR_USERNAME/code/DSA_mock", "HLD_SOLUTIONS_DIR": "/Users/YOUR_USERNAME/code/HLD", "LLD_SOLUTIONS_DIR": "/Users/YOUR_USERNAME/code/LLD", "BEHAVIORAL_SOLUTIONS_DIR": "/Users/YOUR_USERNAME/code/Behavioral" } } } }Use absolute paths. If
python3on your PATH isn't the one withmcpinstalled, use the full path (check withwhich python3). Omit any of the four*_SOLUTIONS_DIRvars you don't need — each independently falls back to a folder underINTERVIEW_PREP_DIR. -
Fully quit Claude Desktop (Cmd+Q) and reopen it. You should see the server's tools under the connectors/tools indicator in the chat input.
Note: ~/code/HLD only ever receives what this server writes, so there's nothing to import there. ~/code/LLD is different — it already holds a real corpus of hand-written .py designs in category folders, so start with the first-time LLD import below. Older LLD work outside LLD_SOLUTIONS_DIR (e.g. ~/code/Practice LLD/chess/main.py) is not reachable by these tools; move it under ~/code/LLD if you want it tracked.
If DSA_SOLUTIONS_DIR already has solutions in it (like an existing Arrays_and_Hashing/, Graphs/, etc. layout), the tracker starts out blank until you import — the files existing on disk isn't the same as the MCP knowing about them. Ask Claude:
Scan my DSA directory and import everything you can confidently match to the catalog.
Claude will call scan_dsa_directory(), read each file's problem-statement snippet, match filenames like island.py → number-of-islands or top_k.py → top-k-frequent-elements using its own judgment (filenames rarely match catalog ids exactly), and call import_solved_dsa_problems in bulk. Anything it can't confidently match (scratch files, company-specific problems) it should flag for you to decide — add those via add_custom_problem first if you want them tracked. This step only reads files and writes to index.json; it never modifies anything under DSA_SOLUTIONS_DIR.
LLD_SOLUTIONS_DIR (~/code/LLD) already holds ~31 hand-written designs in numbered category folders, none of which the tracker knows about. Ask Claude:
Scan my LLD directory and import everything you can confidently match to the catalog.
scan_lld_directory() returns every file with a Kind column — solution (a per-problem design), category-doc (a folder README), aggregate-doc (INDEX.md, QUICK_REFERENCE.md and friends, which span many problems), practice-doc (a markdown write-up this server wrote via save_practice_doc), or other. Only solution rows are importable; import_solved_lld_problem(s) refuses the rest, so an index file can never get linked to a single problem. Claude matches filenames to catalog ids by judgment (8_lru_cache.py → design-lru-cache-oop), and roughly half the corpus isn't in the 25-entry built-in LLD catalog at all (order_lifecycle.py, whatsapp_messaging.py, audit_trail.py, …) — those need add_custom_problem first. Nothing under ~/code/LLD is modified; only index.json is written.
The loop that turns practice into targeted practice. You write the design yourself; Claude grades it as the interviewer and remembers where you're weak.
~/code/LLD/Mock Solutions/
feedback.md # running scorecard, regenerated after each grading
design-parking-lot/
problem.md # the prompt, as Claude posed it
attempt.py # YOUR work — no tool in this server ever writes over it
evaluation.md # Claude's scored critique
ideal.py # Claude's reference design
simple.py # the same design pared back to interview scope
Re-attempting a problem later opens the next round (attempt_2.py, evaluation_2.md, ideal_2.py, simple_2.py), so you keep the earlier one to diff against.
The flow. Say:
Let's do an LLD mock interview.
- Claude calls
get_lld_feedback()andsuggest_next_problems("LLD")to pick a problem aimed at your weakest dimensions, thenstart_mock_attempt(...)— which writes the prompt and an emptyattempt.py. - You write your design in
attempt.py, on your own. Say when you're done. - Claude calls
read_lld_solution("Mock Solutions/design-parking-lot/attempt.py"), thensave_mock_evaluation(...)with rubric scores and a written critique, thensave_ideal_solution(...)and optionallysave_simple_solution(...). - Read
evaluation.mdand diffattempt.pyagainstideal.py— and againstsimple.pyfor the version that actually fits the clock.
The rubric is a fixed seven-dimension vocabulary, scored 1-5. It's fixed on purpose: free-form labels would never aggregate, and the aggregate is exactly what makes later sessions pick different problems.
| Dimension | What it measures |
|---|---|
requirements-and-scope |
Clarifying questions, scoping, stated assumptions |
class-decomposition |
Entity/responsibility split, cohesion |
design-patterns |
Pattern choice, and whether it was justified |
solid-and-extensibility |
SOLID, open/closed, how the design absorbs change |
concurrency-and-edge-cases |
Thread safety, races, boundary conditions |
code-quality |
Naming, structure, idiomatic Python |
tradeoff-communication |
Articulating and defending alternatives |
Scores accumulate in index.json under lld:-prefixed keys (kept apart from Behavioral competency scores, which share that store), and surface in three places: feedback.md for you to read, get_lld_feedback() and get_progress_summary() for Claude, and a weakest-dimensions note appended to suggest_next_problems("LLD"). That last one matters — the generic weak-area ranking matches against catalog topics, and every LLD topic starts with "OOP Design", so without the rubric the feedback would never actually change what gets asked.
Safety. attempt.py is yours: start_mock_attempt refuses to overwrite one, save_ideal_solution can only ever write a file named ideal*.py, and save_simple_solution only simple*.py. Scans and imports exclude Mock Solutions/ entirely, so Claude's own output can never be backfilled into the tracker as your finished work.
Say this at the start of a chat (or bake it into a Claude Desktop project/skill):
At the start of every practice session, call
get_progress_summary, thensuggest_next_problemsfor the type we're practicing (DSA/HLD/LLD) and let me pick from the top few. For LLD also callget_lld_feedbackfirst and aim the pick at my weakest rubric dimensions. After we solve/discuss it, calllog_session(withproblem_idset). Then for HLD/LLD callsave_practice_docwith the full write-up (HLD: requirements, capacity estimate, architecture, API/data model, trade-offs; LLD: class design, patterns used, key decisions). For DSA, if the problem isn't already on disk, callsave_dsa_solutionwith the final code and a short explanation.If I say I want to attempt an LLD problem myself rather than discuss it, run the mock loop instead:
start_mock_attempt, wait for me to writeattempt.py, thenread_lld_solution,save_mock_evaluation(score honestly — inflated scores break the feedback loop),save_ideal_solution, andsave_simple_solutionfor the cut-down version.
Example flow (HLD):
- "What should I practice today?" → Claude calls
get_progress_summary, thensuggest_next_problems("HLD"), and proposes 2-3 options (mixing anything due for revision with new ones matching your weak areas). - You work through the problem together.
- Claude calls
log_session(..., problem_id="design-rate-limiter")to record the verdict/gaps, thensave_practice_doc("HLD", "Design a Rate Limiter", <full writeup>, problem_id="design-rate-limiter"), creatingHLD_SOLUTIONS_DIR/design-rate-limiter.md. - Next time you ask to revise,
list_practice_docs()shows everything documented so far, andget_practice_docpulls up any specific one in full.
Example flow (DSA):
suggest_next_problems("DSA")→ you pick "Course Schedule".- You solve it together.
log_session(..., problem_id="course-schedule"), thensave_dsa_solution("course-schedule", "Course Schedule", "Graphs", <code>, explanation="topological sort via Kahn's algorithm")→ createsDSA_SOLUTIONS_DIR/Graphs/course_schedule.py.
Example flow (Behavioral):
- Before your first mock session, tell Claude about your background and stories; it calls
save_candidate_context(...)to writeBEHAVIORAL_SOLUTIONS_DIR/candidate_context.md(a "Core Profile & Story Bank" section, plus a "Current Focus" section for whichever company/role you're currently prepping for). - "Let's do a behavioral mock for CompanyX." → Claude calls
get_candidate_context()to see your stories, thensuggest_next_problems("Behavioral")and proposes a few questions (built-in catalog of 29 common questions across competencies like Leadership, Conflict & Disagreement, Ownership — seecatalog.json), or you can just ask about a real question you were asked and it'll useadd_custom_problem("Behavioral", ...)to track it. - You answer with a STAR story together.
- Claude calls
log_session(..., interview_type="Behavioral", problem_id=..., competency_scores={"leadership": 4, "communication": 3}, improvements="Add a concrete metric to the result step.")— this appends the verdict, scores, and improvement suggestions torevision.md, and updates the per-competency score averages shown inget_progress_summary. - Prepping for a different company next week? Call
save_candidate_context(...)again with an updated "Current Focus" section — the Core Profile & Story Bank stays intact since you write the whole file each time.
By default a problem is considered "due for revision" 21 days after its last attempt (or immediately, regardless of days, if it matches a currently flagged weak area). Change it via the INTERVIEW_PREP_STALE_DAYS env var in the Claude Desktop config's env block.
catalog.json (next to server.py) is the shared, built-in catalog — feel free to hand-edit it, or just ask Claude to call add_custom_problem, which writes to custom_catalog.json inside your INTERVIEW_PREP_DIR instead (keeps your personal additions separate from the shipped list).
A stdio-transport MCP server (what this is) isn't a background daemon you start once and leave running — Claude Desktop spawns it fresh as a subprocess every time it needs it, using whatever command/args/env is in mcpServers.interview-memory in claude_desktop_config.json. So there's no separate "autostart" toggle: being registered in that config is autostart — Desktop manages the process lifecycle automatically from then on. That's already wired up (see Setup above); you just need to fully quit (Cmd+Q, not just close the window) and reopen Claude Desktop for it to pick up the config.
Four ways to check it's actually working, in increasing order of realism:
- Does it even boot?
python3 server.pyfrom this folder — should start and hang silently (that's correct; it's waiting for a client on stdin). Ctrl+C to stop. - Does it speak MCP correctly?
python3 test_server.py— spawns the server as a real MCP client would, does the protocol handshake, lists all 26 tools, and callsget_progress_summaryfor real. Read-only, safe to run anytime. A clean "All checks passed" means the server itself is solid, independent of Claude Desktop. - Do the LLD tools actually behave?
python3 test_lld_tools.py— builds a synthetic design repo in a temp directory and exercises every LLD tool against it: path guards, kind classification, rubric validation, the full mock loop, and above all thatattempt.pycomes out byte-identical to what was written. Hermetic — it never touches~/code/LLDor your realindex.json. - Is Claude Desktop actually using it?
- Open a chat and look at the tools/connectors icon near the input box —
interview-memoryshould be listed with its tool count. ps aux | grep server.py— while Desktop is open, you should see a livepython3 .../server.pyprocess (Desktop spawns it once you open a chat that uses it, or at startup depending on version).- Logs:
~/Library/Logs/Claude/mcp-server-interview-memory.log. - Interactive debugging (optional):
mcp dev server.pylaunches the MCP Inspector, a local web UI where you can call any tool by hand and see raw responses — useful for poking at edge cases outside a real chat.
- Open a chat and look at the tools/connectors icon near the input box —
This server only ever runs over stdio (mcp.run(transport="stdio")), the same as the original. Stdio means Claude Desktop spawns it as a local subprocess and talks to it over stdin/stdout pipes — it opens no network port, so there is nothing for another machine, process, or website to connect to. Don't change the transport to sse/http unless you also add authentication and bind to localhost; as shipped, "outside access" isn't a meaningful attack surface because there's no listener at all.
Within the local filesystem, a few guardrails are worth knowing about since Claude drives these tools somewhat autonomously:
- Path sanitization. Every value used to build a filename (
problem_id, problem titles) is passed through a slugifier that strips everything excepta-z 0-9 -before it ever touches a path. A value like../../etc/passwdbecomesetc-passwd, not a traversal — verified with an adversarial test during development. - Directory containment.
save_practice_doccan only write insideHLD_SOLUTIONS_DIRorLLD_SOLUTIONS_DIR(whichever matches the call);save_dsa_solutioncan only write insideDSA_SOLUTIONS_DIR;save_candidate_context/get_candidate_contextare confined toBEHAVIORAL_SOLUTIONS_DIR;import_solved_dsa_problem(s)can only link to files that already live insideDSA_SOLUTIONS_DIR(it refuses anything outside it, e.g.~/.ssh/,/etc/);get_practice_docre-checks that whateverindex.jsonpoints at is still inside the allowed directory for that problem type before reading it. - No code execution. The server only reads and writes text files. It never
execs,evals, or runs the DSA solutions it stores —code/content_markdownarguments are treated purely as bytes to persist. - Overwrite protection.
save_dsa_solutionrefuses to replace an existing file unlessoverwrite=Trueis passed explicitly, sinceDSA_SOLUTIONS_DIRis assumed to hold real, hand-written work. (save_practice_docfor HLD/LLD, andsave_candidate_contextfor Behavioral, do overwrite by design — they hold Claude's latest write-up/profile, and those directories are managed entirely by this server.) - Env-var-scoped roots.
INTERVIEW_PREP_DIR,DSA_SOLUTIONS_DIR,HLD_SOLUTIONS_DIR,LLD_SOLUTIONS_DIR, andBEHAVIORAL_SOLUTIONS_DIRare only ever set by you, in your own Claude Desktop config — they aren't something a chat message can override.
- Server not appearing: check JSON syntax in the Claude Desktop config (one bad comma disables everything), confirm paths are absolute, then look at logs in
~/Library/Logs/Claude/mcp-server-interview-memory.log. - Test the server manually:
python3 server.pyshould start and wait silently (Ctrl+C to exit). ModuleNotFoundError: No module named 'mcp': runpip3 install mcpwith the samepython3referenced in the config.save_dsa_solutionrefusing to write: it never overwrites an existing file withoutoverwrite=True— this is deliberate, sinceDSA_SOLUTIONS_DIRis treated as containing real, hand-written solutions.