Skip to main content
Glama
README.md
# learning-mcp

An MCP server that teaches you a topic by **enforcing** a pedagogical workflow
instead of suggesting one.

Ask a chat model to "teach me Kafka step by step" and it will agree, then
quietly compress five steps into one. That isn't disobedience — it's what
happens when the whole plan sits in context at once: the model can see step 5
while working step 1, so it hedges step 1 toward the finish and merges anything
it judges redundant.

This server holds the steps instead. The next instruction does not exist in the
model's context until it has handed back a valid artifact for the current one.
Sequencing stops being a request and becomes a data dependency.

## The workflow

```
research  ->  decompose  ->  drill (loop)  ->  done
```

1. **Research** — build a comprehensive, cited picture of the topic.
2. **Decompose** — break it into atomic elements, each with its prerequisites.
   The result is a DAG, not a list.
3. **Drill** — the loop that actually teaches. One element at a time, chosen by
   what's unlocked, due, and weakest.

## What makes it a tutor rather than a quiz

**Retrieval practice over re-reading.** The default action is to ask, wait, and
grade. Explanation is the fallback after a miss, not the main event.

**Escalating demand.** Each element moves `recall` → `explain_back` → `apply` as
it becomes familiar. You have to produce the idea, not recognize it.

**Prerequisites are enforced.** An element is only drillable once everything it
depends on is mastered, so you're never quizzed on consumer groups before
partitions.

**Interleaving comes free.** Once several elements are unlocked, the scheduler
alternates rather than drilling one to exhaustion.

**Spacing, and a gate that resists cramming.** Reviews follow an SM-2 interval
ladder. Mastery additionally requires that one success landed a full day after a
previous one — three right answers in a single sitting is short-term memory, and
the gate says so.

**The answer has to be yours.** During a drill the server suspends mid-call to
collect your answer, which reaches the model as a tool result it did not author.
It cannot ask a question and answer it on your behalf.

## Running it

Requires **MCP SDK 2.0+** — the drill loop uses `MCPServer` and the
`Resolve`/`Elicit` round trip, neither of which exists in the 1.x FastMCP API.

### Locally (start here)

This is the right choice for almost everyone. The server runs as a subprocess of
your client, your data stays on your disk, and there is nothing to secure.

```bash
pip install learning-mcp          # or: pip install git+https://github.com/ryantthomas/learning-mcp
claude mcp add learning -- learning-mcp
```

Then ask it to teach you something.

### With Docker

```bash
docker build -t learning-mcp .
docker run -p 8000:8000 \
  -v learning-data:/data \
  -e LEARNING_MCP_TOKEN="$(openssl rand -hex 32)" \
  learning-mcp
```

**The volume is not optional.** Every topic, element and review date lives in one
SQLite file under `/data`. Without a persistent volume the container starts
empty every time, and you won't notice until the day you come back to review.

### On your own cloud

The Dockerfile is the only deploy artifact, deliberately — it works on Fly,
Railway, Render, Cloud Run, or any VPS, and locks you into none of them. Two
things actually matter:

1. **Attach a persistent volume** and point `LEARN_HOME` at it (the image
   defaults to `/data`). Platforms with ephemeral filesystems will silently
   discard everything on restart.
2. **Set `LEARNING_MCP_TOKEN`.** The server refuses to start on a non-loopback
   interface without one. That's a deliberate fail-closed, not an obstacle to
   work around — an open URL is a read/write handle on your entire learning
   history.

`$PORT` is honoured, so most platforms need no further configuration.

## Authentication, honestly

`LEARNING_MCP_TOKEN` enables `Authorization: Bearer <token>` on the HTTP
transport. Configuration is per-instance, and each person runs their own — there
is no multi-user mode and no notion of accounts.

| Client | Works with a bearer token? |
| --- | --- |
| Local stdio (Claude Desktop, Claude Code) | N/A — no network exposure |
| Claude Code against a remote URL | Yes, via `--header` |
| Other CLI / custom MCP clients | Yes, if they can send a header |
| **claude.ai and the mobile apps** | **No — the custom connector UI only accepts OAuth** |

That last row is worth reading twice if your goal is studying on your phone.
Claude's custom connector settings expose Authorization URL, Token URL, Client
ID and Client Secret — there is no field for a static token or custom header. A
bearer-token server cannot be registered there. Making that work needs a real
OAuth authorization server; the SDK supports it via `auth_server_provider`, but
this project doesn't implement one yet.

## Where your data lives

Everything is under `~/.learn` (override with `LEARN_HOME`). SQLite is the
source of truth; markdown is a projection of it, so a topic stays readable and
greppable without the tool.

```
~/.learn/
  learn.db
  topics/kafka/
    research.md
    elements/01-partitions.md
    progress.md
```

**A hosted instance moves this off your machine.** The markdown mirror ends up on
the server, where you can't grep or commit it. If those local files are the point
for you, run stdio locally instead of deploying.

The schema is deliberately graph-shaped — prerequisites and concepts are their
own tables, never JSON on a row — so a Neo4j projection later is an export
rather than a rewrite. `concepts` is global while `elements` are topic-scoped,
which is the seam that will let "partitioning" learned under Kafka count for
itself again under Kinesis.

## Design

The pedagogy lives in `steps.py`, `mastery.py`, and `scheduler.py`, none of
which import `mcp`. That's deliberate: the teaching logic is a plain Python
library that happens to be served over MCP, so it can be unit-tested without a
model in the loop and re-fronted without a rewrite.

`server.py` is a thin adapter. Every advance requires the previous step's
artifact as an argument — the model cannot obtain step N+1 without paying for
step N.

## Development

```bash
pip install -e ".[dev]"
pytest
```

The two tests worth knowing about, because they encode the whole point:

- **the gate** — a malformed artifact must not advance the phase
- **concealment** — a step's response must not contain any later step's text

## License

MIT.

TDQS

A4.5/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct stage of the learning workflow: starting/resuming a topic, submitting work, drilling, checking status, asking questions, grading, and listing due reviews. There is no meaningful overlap.

Naming Consistency4/5

The naming is mostly verb_noun snake_case (start_topic, submit_step, ask_user, grade_drill), but 'next_drill' and 'status' break the pattern, and 'resume' is a bare verb. Overall the style is consistent and readable.

Tool Count5/5

Seven tools is well-scoped for a focused learning assistant. Each tool serves a clear purpose and none feel superfluous.

Completeness5/5

The tool set covers the full learning lifecycle: initiating a topic, advancing through steps, drilling, grading, assessing status, and handling due reviews. No obvious gap in the intended workflow.

Maintenance

ActivitySlowing
ResponsivenessNo issues