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 beforesuggest_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.