A child-friendly AI tutor built as a pure MCP showcase.
Every prompt, every state mutation, every data read flows through the Model Context Protocol.
Built with Claude by Anthropic.
Live Demo: prof-proton-kpor2k5sb4pwbt3bwcrzae.streamlit.app
API: prof-proton-tutor.onrender.com
Note: The backend runs on Render's free tier — the first request after inactivity may take ~30 seconds to wake up.
Prof. Proton is an AI-powered tutor designed for kids — you give it a topic, it teaches the child in simple friendly language, then quizzes them, accepts answers by text or voice, and hands back a warm personalised report card with their score, strengths, and study tips. You can also upload any document (PDF or text) and the tutor will summarise it, make flashcards, and quiz the child on it. Under the hood, the entire application is built on the Model Context Protocol (MCP): a single MCP server holds all the business logic — teaching prompts, quiz data, student answers, document embeddings — and everything else (the FastAPI backend and the Streamlit UI) just talks to that server over the protocol. This makes the project a live showcase of what a real, protocol-first MCP application looks like.
- What is Pure MCP?
- Architecture
- How a Request Flows
- Why Pure MCP?
- Project Structure
- MCP Server Inventory
- Deployment
- Getting Started
- API Reference
Most "MCP-enabled" apps use MCP as a thin wrapper — they call tools locally and never speak the actual protocol. Prof. Proton does the opposite.
The MCP server (mcp_server.py) is the single source of truth for:
| Concern | How it is handled |
|---|---|
| Teaching prompts | MCP Prompts primitive (get_prompt) |
| Session & quiz state | MCP Tools — write tools that persist to SQLite |
| Document retrieval | MCP Tools — semantic search via OpenAI embeddings |
| Live state inspection | MCP Resources (session://, doc://) |
The FastAPI layer is a thin HTTP façade. The Streamlit UI calls FastAPI. Neither layer contains any business logic — every action is a real MCP protocol call over an in-process memory transport.
┌──────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ │
│ ┌─────────────────────────────┐ │
│ │ Streamlit UI │ │
│ │ (frontend/app.py) │ │
│ └──────────────┬──────────────┘ │
│ │ HTTP REST │
└───────────────────────┼──────────────────────────┘
│
┌───────────────────────▼──────────────────────────┐
│ FastAPI (port 8000) │
│ ┌────────────────────────────────────────────┐ │
│ │ routes/ │ │
│ │ session.py quiz.py document.py │ │
│ │ audio.py │ │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ services/ │ │
│ │ mcp_client.py ──────────────────────┐ │ │
│ └────────────────────────────────────────┼───┘ │
└───────────────────────────────────────────┼──────┘
│ MCP
In-Memory
Transport
│
┌───────────────────────────────────────────▼──────┐
│ MCP SERVER (mcp_server.py) │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ TOOLS (15) │ │
│ │ │ │
│ │ Write ────────────────────────────────── │ │
│ │ create_session set_explanation │ │
│ │ store_quiz save_answer │ │
│ │ save_evaluation upload_document │ │
│ │ │ │
│ │ Read ─────────────────────────────────── │ │
│ │ get_session_info get_all_questions │ │
│ │ get_answers get_question_with_answer │ │
│ │ get_quiz_and_answers search_document │ │
│ │ get_document_text get_document_topic │ │
│ │ document_exists │ │
│ ├────────────────────────────────────────────┤ │
│ │ PROMPTS (12) │ │
│ │ teach_learn teach_revise │ │
│ │ teach_professional quiz_gen │ │
│ │ answer_eval_system full_eval_system │ │
│ │ doc_summary doc_flashcards │ │
│ │ doc_qa_system flashcard_mcq │ │
│ │ flashcard_fib flashcard_tof │ │
│ ├────────────────────────────────────────────┤ │
│ │ RESOURCES (2) │ │
│ │ session://{session_id} │ │
│ │ doc://{doc_id} │ │
│ └────────────────────────────────────────────┘ │
│ │ │ │
│ prompts.py db.py │
│ (12 templates) (SQLite) │
│ │ │
│ ┌──────┴──────┐ │
│ │ tutor.db │ │
│ │ sessions │ │
│ │ questions │ │
│ │ answers │ │
│ │ documents │ │
│ └─────────────┘ │
│ │
│ OpenAI GPT-4o ← reasoning engine │
│ calls MCP tools autonomously │
└──────────────────────────────────────────────────┘
Student enters "Photosynthesis" → Streamlit UI
│
▼ POST /start
│
├─ call_tool("create_session", {session_id, topic, mode, name, age})
│ └─► db.create_session() → tutor.db
│
├─ get_prompt("teach_learn", {topic, name, age}) ← MCP Prompts
│ └─► prompts.teach_learn() → system prompt string
│
├─ OpenAI GPT-4o (system = teach_learn prompt, tools = [search_document])
│ └─ if document uploaded: GPT-4o autonomously calls search_document
│ └─► call_tool("search_document", {document_id, query})
│ └─► OpenAI embeddings + cosine similarity → passages
│
├─ call_tool("set_explanation", {session_id, explanation})
│ └─► db.set_explanation() → tutor.db
│
└─► StartResponse → Streamlit (explanation text + TTS audio)
Student submits answer → POST /quiz/answer
│
├─ get_prompt("answer_eval_system") ← MCP Prompts
│
├─ OpenAI GPT-4o (tools = [get_question_with_answer])
│ └─ GPT-4o autonomously calls get_question_with_answer
│ └─► call_tool("get_question_with_answer", {session_id, question_id})
│ └─► db.get_question()
│ ↑ correct answer never sent to frontend
│
├─ GPT-4o returns {"correct": true, "feedback": "Fantastic! ..."}
│
└─ call_tool("save_answer", {session_id, question_id, correct, feedback})
└─► db.save_answer() → tutor.db
GET /quiz/evaluate/{session_id}
│
├─ get_prompt("full_eval_system", {topic, age, score, total, pct})
│
├─ OpenAI GPT-4o (tools = [get_quiz_and_answers])
│ └─ GPT-4o autonomously calls get_quiz_and_answers
│ └─► all questions + all student answers → GPT-4o
│
├─ GPT-4o returns personalised report (strengths, mistakes, tip, emoji)
│
└─ call_tool("save_evaluation", {session_id, evaluation})
└─► db.save_evaluation() → tutor.db
| Benefit | Explanation |
|---|---|
| Prompt governance | All 12 system prompts live in the server as MCP Prompts primitives. Update once, every client benefits automatically |
| Persistent state | SQLite via write tools — sessions and documents survive server restarts with zero extra infrastructure |
| Security by design | Correct answers are stored server-side and fetched by the LLM via a tool call. They never travel to the frontend |
| LLM-driven retrieval | GPT-4o decides when to call search_document or get_question_with_answer — no hand-written dispatch logic |
| Live observability | Any MCP client can read session://{id} or doc://{id} Resources to inspect state without a dedicated API endpoint |
| Separation of concerns | Data layer (db.py), prompts (prompts.py), protocol (mcp_server.py), orchestration (mcp_client.py) are each isolated |
ai_tutor/
├── .env ← OPENAI_API_KEY
├── README.md
├── venv/ ← Python virtual environment
│
├── backend/
│ ├── mcp_server.py ← ★ Core — 15 Tools, 2 Resources, 12 Prompts
│ ├── prompts.py ← 12 prompt template functions (all str params)
│ ├── db.py ← SQLite data layer (used only by mcp_server.py)
│ │
│ └── app/ ← FastAPI (thin HTTP façade)
│ ├── main.py ← Lifespan boots in-memory MCP transport
│ ├── models/schemas.py ← Pydantic request / response models
│ ├── routes/
│ │ ├── session.py ← POST /start
│ │ ├── quiz.py ← /quiz/generate · /quiz/answer · /quiz/evaluate
│ │ ├── document.py ← /document/upload · /document/qa · /document/flashcard-quiz
│ │ └── audio.py ← /audio/transcribe · /audio/tts
│ ├── services/
│ │ ├── mcp_client.py ← MCP client + OpenAI orchestrator
│ │ └── audio_service.py ← Whisper STT + OpenAI TTS
│ └── utils/
│ └── pdf.py ← PDF extraction (pypdf + GPT-4o Vision OCR fallback)
│
└── frontend/
├── app.py ← Streamlit UI
└── api_client.py ← HTTP client for FastAPI
| Name | Type | Description |
|---|---|---|
create_session |
Write | Create a new learning session in SQLite |
set_explanation |
Write | Store teaching text, advance status to quiz_ready |
store_quiz |
Write | Persist quiz questions, advance status to quizzing |
save_answer |
Write | Record a student answer with correctness and feedback |
save_evaluation |
Write | Persist end-of-quiz report card, mark session done |
upload_document |
Write async | Chunk → embed (OpenAI) → store chunks + vectors in SQLite |
search_document |
Read async | Cosine similarity search over stored embeddings |
get_document_text |
Read | Full document text (up to 12 000 chars) |
get_document_topic |
Read | Topic label for a document |
document_exists |
Read | Check if a document has been uploaded |
get_session_info |
Read | Full session metadata + quiz progress |
get_all_questions |
Read | All quiz questions including correct answers |
get_answers |
Read | All student answers for a session |
get_question_with_answer |
Read | Single question + correct answer (for LLM evaluation) |
get_quiz_and_answers |
Read | Complete quiz + all answers (for report card generation) |
| URI Template | Description |
|---|---|
session://{session_id} |
Live session state as JSON — topic, mode, status, progress, evaluation |
doc://{doc_id} |
Document metadata — topic, chunk count, text preview |
| Name | Purpose |
|---|---|
teach_learn |
Child-friendly "learn mode" lesson from Prof. Proton |
teach_revise |
Bullet-point revision summary |
teach_professional |
Academic / professional explanation with precise terminology |
quiz_gen |
Generate questions with easy/medium/hard spread |
answer_eval_system |
Evaluate one answer — instructs LLM to call get_question_with_answer |
full_eval_system |
Report card — instructs LLM to call get_quiz_and_answers |
doc_summary |
Summarise uploaded document into topic + key points |
doc_flashcards |
Generate flashcards from document |
doc_qa_system |
RAG Q&A — instructs LLM to call search_document |
flashcard_mcq |
Convert flashcards to multiple-choice questions |
flashcard_fib |
Convert flashcards to fill-in-the-blank sentences |
flashcard_tof |
Convert flashcards to true/false statements |
| Service | URL |
|---|---|
| Frontend (Streamlit Cloud) | prof-proton-kpor2k5sb4pwbt3bwcrzae.streamlit.app |
| Backend API (Render) | prof-proton-tutor.onrender.com |
| API Docs (Swagger) | prof-proton-tutor.onrender.com/docs |
- Frontend — Streamlit Cloud (free tier), connected to GitHub
mainbranch, auto-deploys on push - Backend — Render (free tier), runs FastAPI + MCP server; start command:
uvicorn app.main:app --host 0.0.0.0 --port $PORT --app-dir backend - Environment variables set in each platform's dashboard —
OPENAI_API_KEYon Render,API_BASEon Streamlit Cloud
- Python 3.11+
- OpenAI API key
cd ai_tutor
python -m venv venv
venv\Scripts\pip install -r requirements.txtCreate .env in the project root:
OPENAI_API_KEY=sk-...venv\Scripts\python.exe -m uvicorn app.main:app --reload --app-dir backend- API base:
http://localhost:8000 - Interactive docs:
http://localhost:8000/docs
venv\Scripts\streamlit run frontend\app.py- UI:
http://localhost:8501
| Method | Endpoint | Description |
|---|---|---|
POST |
/start |
Start session, receive explanation + TTS audio |
POST |
/quiz/generate |
Generate quiz questions for a session |
POST |
/quiz/answer |
Submit answer (text or voice upload) |
GET |
/quiz/evaluate/{session_id} |
Get personalised report card |
POST |
/document/upload |
Upload document, receive summary + flashcards |
POST |
/document/qa |
Ask a question about an uploaded document |
POST |
/document/flashcard-quiz |
Convert flashcards to MCQ / fill-in-blank / true-false |
POST |
/audio/transcribe |
Transcribe audio via Whisper |
POST |
/audio/tts |
Text-to-speech via OpenAI TTS |
| Component | Technology |
|---|---|
| MCP Framework | FastMCP — MCP SDK 1.27 |
| LLM | OpenAI GPT-4o |
| Embeddings | OpenAI text-embedding-3-small |
| Database | SQLite with WAL mode |
| Backend API | FastAPI + Uvicorn |
| Frontend | Streamlit |
| Speech-to-Text | OpenAI Whisper |
| Text-to-Speech | OpenAI TTS |
| PDF Extraction | pypdf + GPT-4o Vision (OCR fallback) |