A Socratic Model Context Protocol tutor for new coders. It teaches instead of dumping solutions — the whole point is that learners do the thinking, and the tutor hands them the right-sized nudge at the right moment.
Large language models are very good at writing code you can paste and ship. That is the problem this project exists to push back on. When a learner's first move is "paste my error into a chat and copy whatever comes out", they trade short-term wins for long-term understanding — and the skill they are actually there to build never lands.
So this tutor is deliberately annoying in specific ways:
- Checkpoints before answers. Every tool attaches 2-3 questions the learner should answer in their own words before escalating.
- A 4-level hint ladder. Level 1 is a definition; level 2 is a demo outline; level 3 is scaffolded pseudocode with
// TODOblanks; level 4 is a full conceptual walkthrough. Levels 3 and 4 are gated — asking for them without evidence of effort quietly downgrades the response and says why. - Rubrics, not rewrites.
review_submissionwalks each rubric criterion and gives coaching questions per item. It never rewrites the learner's code. - Debugging as a thinking tool.
debug_helpreturns ranked hypotheses and a verification checklist — not a fix. - A central policy layer.
applyTutorPolicyinspects every tool response and rewrites it into a guardrail redirect if it looks like a solution dump (multiple code fences + many code-like lines).
None of this prevents a motivated learner from getting a direct answer elsewhere. It just makes the default interaction shape one that teaches.
npm installThere are two transports. Pick the one you want.
npm run dev
# server: http://127.0.0.1:3333
# health: http://127.0.0.1:3333/health
# dispatch: POST http://127.0.0.1:3333/mcpnpm run mcp:stdio # via ts-node
# or, after `npm run build`:
npm run mcp:stdio:build # runs dist/mcp/stdioEntry.jsStdio mode speaks JSON-RPC 2.0 on stdin/stdout. Diagnostics go to stderr so they never corrupt the RPC channel.
Register the tutor with any MCP-aware client by pointing it at the built bin. Example claude_desktop_config.json:
{
"mcpServers": {
"noob-tutor": {
"command": "node",
"args": ["/absolute/path/to/mcp-noob-tutor/dist/mcp/stdioEntry.js"]
}
}
}Cursor and Zed use the same shape in their respective settings files. Run npm run build once before the client spawns the subprocess so dist/mcp/stdioEntry.js exists.
| Name | What it does | When the LLM should call it |
|---|---|---|
explain_concept |
Returns a curated definition, common mistakes, and a mini exercise. Supports a 4-rung hint ladder. | Learner asks "what is X" or is fuzzy on a term. |
next_topic |
Walks a curriculum track (foundation/frontend/backend/fullstack) and picks the next uncompleted topic. | Learner asks what to study next or just finished a topic. |
assess_knowledge |
Returns diagnostic questions for a topic. Does NOT grade. | Learner wants to self-diagnose on a specific topic. |
analyze_assessment |
Takes answers + returns gap analysis + a recommended next step. | After the learner has answered assess_knowledge. |
generate_practice_task |
Returns a timeboxed, rubric-backed practice task. | Learner wants hands-on practice. |
debug_help |
Ranked hypotheses, a verification checklist, and the ONE next question. Never writes a fix. | Learner is stuck on an error. |
review_submission |
Rubric-driven Socratic feedback against a known practice task. Never rewrites work. | Learner finished a practice task and wants feedback. |
Every tool response passes through applyTutorPolicy so the checkpoint shape, hint ladder, and anti-vibe redirect are consistent across the board.
Learner: "My fetch keeps failing with
Access to fetch at http://api has been blocked by CORS policy"
LLM calls debug_help with errorText — gets back:
matched: "CORS"- 4 ranked hypotheses (missing Allow-Origin, unhandled preflight, credentials +
*, browser vs server enforcement) - A Network-tab checklist
nextBestQuestion: which exact Origin is the browser sending, and whatAccess-Control-Allow-Originis the server returning?- Checkpoints forcing the learner to paste their attempt and pick one hypothesis to test first.
No "just add cors() middleware" dump.
Learner: "Explain HTTP" →
explain_concept({ concept: "http", hintLevel: 1 })
Gets the definition, why it matters, common mistakes, and three reflection checkpoints.
Later: "Show me the full walkthrough" →
explain_concept({ concept: "http", hintLevel: 4 })without alearnerAttempt.
The tutor downgrades to level 2 with a gateNote explaining hint 4 requires a real attempt. Learner tries, comes back with learnerAttempt: "I built GET /ping returning JSON, verified with curl, but I'm unsure how to pick status codes and what Content-Type really does..." → now gets the level 4 walkthrough and trade-offs.
generate_practice_task({ topic: "http_basics" })→http-echo-jsontask (30 min timebox, goal, steps, rubric).
Learner does the work, then review_submission({ taskId: "http-echo-json", submission: "..." }).
Each rubric criterion gets a met/partial/missing status plus a coaching question. The tool picks the single most impactful next iteration (oneThingToImprove) and — if the learner provided a selfAssessment — gently notes where it over- or under-calls their work.
HTTP request ── POST /mcp ──┐
▼
routes.ts
(envelope + tool-input validation via Zod)
│
▼
mcpController.ts
(lookup + context normalization)
│
▼
tool.execute()
(explain_concept, next_topic, ...)
│
▼
applyTutorPolicy
(default checkpoints, anti-vibe guardrail,
hint-ladder attachment)
│
▼
MCPResponse to caller
MCP client (Claude Desktop / Cursor / Zed)
│ JSON-RPC 2.0 over stdio
▼
stdioEntry.ts ── bootstrapTools() ── toolRegistry
│ ▲
└── stdioServer.ts ──tools/list, tools/call──┘
(same applyTutorPolicy layer)
Both transports share the same tool registry via src/mcp/bootstrapTools.ts, so there is exactly one place to register a new tool.
- Add a Zod input schema in
src/mcp/schemas/toolSchemas.tsand register it intoolInputSchemas. - Add a matching JSON Schema entry in
src/mcp/toolJsonSchemas.ts(this is what MCP clients see). - Create
src/mcp/tools/yourTool.tool.tsexporting anMCPTool<YourInput>. - Register the tool and its
"Use when …"description insrc/mcp/bootstrapTools.ts(two places: the tools array andTOOL_DESCRIPTIONS). - Add a Playwright spec in
tests/covering happy path, edge cases, and the "never dumps code" contract.
You do not need to touch httpServer.ts or stdioEntry.ts — bootstrapTools() covers both transports.
- Extend the
TopicIdunion insrc/tutor/curriculum/topicGraph.tsand add itsTopicNode. - Add it to the appropriate track(s) in
src/tutor/curriculum/tracks.ts. - Optional but encouraged: add a glossary entry (
src/tutor/glossary/glossary.ts), diagnostic questions (src/tutor/diagnostics/diagnostics.ts), and a practice task (src/tutor/practice/practiceBank.ts).
Practice tasks live in src/tutor/practice/practiceBank.ts. Each task has a stable id, a topic, a timebox (15/30/45/60), and a rubric. review_submission uses the rubric's looksLike strings for keyword matching, so write them to name the concrete signals you would look for in a strong submission.
There are no unit tests in the traditional sense. The Playwright API suite covers every tool over the HTTP transport plus the tutor policy directly.
npm test # run the whole suite (auto-boots the dev server)
npm run test:headed # interactive runner UIThe webServer hook in playwright.config.ts will reuse a running instance, so if you already have npm run dev open, tests will use it instead of spawning a second one.
MIT