Content
# Tool List
Full-fledged agent with MCP server for university assistant based on LLM. Gives the language model access to process data through a set of tools — students, schedule, disciplines, educational materials. Also, a connected RAG for searching educational materials by content.
| Part | Description |
|---|---|
| MCP Server | Server for interacting with the database and RAG |
| RAG | Search for educational materials by content (separate HTTP service) |
| API with Agent | Interaction with the agent and custom client agent: [Agent Core](demo/api/agent/) |
| Web UI | Site card with a chat for interacting with the agent and presentations |
## Capabilities
What the agent can use:
| Tool | What it does |
|---|---|
| `get_student(student_id)` | Student card |
| `find_student_by_name(name)` | Search for a student by name |
| `get_schedule(group_id, day?)` | Group schedule, optionally by day |
| `get_disciplines(student_id)` | Student disciplines through their group |
| `get_materials(discipline_id, type?)` | List of files by discipline |
| `search_materials(query, discipline_id?)` | Search by content of materials |
| `get_student_grades(student_id, discipline_id?)` | Student grades, optionally by one discipline |
| `get_teacher_by_name(name)` | Search for a teacher |
| `get_teacher_schedule(teacher_name, day?)` | Teacher schedule |
| `list_documents(discipline_id?)` | List of documents in the RAG index |
| `search_documents(query, discipline_id?, limit?)` | Search for relevant document fragments |
| `get_rag_context(query, discipline_id?, limit?)` | Ready context for answering by documents |
> **Note:** `import_document` is only available through the CLI `agent-ingest`, not through the MCP server.
## Tech Stack
- Python 3.12 and 3.13
- [FastMCP](https://github.com/modelcontextprotocol/python-sdk) — MCP transport layer
- [LiteLLM](https://github.com/BerriAI/litellm) — unified interface for working with various LLM providers
- SQLite — local storage
- Pydantic — response schemes
- Faker — generation of test data
- python-docx — generation of DOCX materials for fixtures
- built-in PDF generator — generation of PDF lectures for fixtures without external utilities
- Sentence Transformers — local embedding model
- ChromaDB — local vector database for RAG search
- Ollama — local text generation of materials through `agent-ingest`
## Structure
```
agent-tutor/
├── mcp_server/ # MCP server (previously server.py in the root)
│ ├── __init__.py
│ ├── server.py # FastMCP server, entry point, HTTP transport
│ └── tools/
│ ├── student.py # StudentTools
│ ├── disciplines.py # DisciplineTools (get_materials/search_materials via doc_repo)
│ ├── grades.py # GradeTools
│ ├── teacher.py # TeacherTools
│ └── rag.py # Facade for backward compatibility (uses RagClient)
├── db/
│ ├── database.py # SQLite, table creation, fixture loading
│ └── models.py # Pydantic models (re-export RAG models from rag.models)
├── rag/ # RAG layer (independent of db package)
│ ├── __init__.py # create_rag_pipeline(connection, config)
│ ├── config.py # RagConfig from environment variables
│ ├── interfaces.py # EmbeddingProtocol, VectorStoreProtocol
│ ├── embeddings.py # SentenceTransformerEmbedding
│ ├── vector_store.py # ChromaDBVectorStore
│ ├── parser.py # DocumentParser (PDF, DOCX, TXT, MD, HTML)
│ ├── chunker.py # TextChunker (semantic, recursive, sentence)
│ ├── repository.py # DocumentRepository — CRUD documents/chunks in SQLite
│ ├── pipeline.py # RAGPipeline — orchestration parsing → chunking → saving
│ ├── models.py # Pydantic models (Document, Material, RagSearchResult, ...)
│ ├── http_models.py # HTTP DTO models for RAG service
│ ├── service.py # HTTP service RAG (Starlette) — endpoints /health, /search, /context, /documents/*
│ └── client.py # HTTP client for calling RAG service from MCP
├── fixtures/
│ ├── generate.py # Test data generator
│ ├── ingest.py # CLI agent-ingest for RAG documents and material generation
│ └── document_generator.py # Generation of PDF/DOCX materials for fixtures
├── demo/ # Demo part: API and web interface
│ ├── settings.py # Demo configuration (ports, Ollama URL, timeouts)
│ ├── api/
│ │ ├── __init__.py
│ │ ├── agent/ # Agent logic: orchestrator, LLM, MCP, tools, context
│ │ ├── backlog.py # Interaction backlog with the model (JSONL)
│ │ ├── data.py # Database management
│ │ └── server.py # REST/SSE API server
│ └── web/
│ ├── __init__.py
│ ├── server.py # Static web server
│ └── static/ # Static files (HTML, CSS, JS)
└── fixtures.json # Test data
```
## RAG Package Architecture
The `rag/` package is independent of `db/`.
`DocumentRepository` accepts raw `sqlite3.Connection`.
- `rag/interfaces.py` — protocols (`EmbeddingProtocol`, `VectorStoreProtocol`) for replacing implementations
- `rag/embeddings.py` → `SentenceTransformerEmbedding`, `rag/vector_store.py` → `ChromaDBVectorStore`
- Pydantic models (`Document`, `Material`, `RagSearchResult`, ...) moved to `rag/models.py`; `db/models.py` re-exports
- `server.py` uses `create_rag_pipeline(db.connector)` directly (instead of `RagTools`)
### New Architecture (Phase 0 completed)
RAG is highlighted as a **separate HTTP service**:
- `rag/service.py` — Starlette application with endpoints:
- `GET /health` — service status check
- `POST /search` — semantic search by documents
- `POST /context` — receiving RAG context for LLM
- `POST /documents/list` — list of documents
- `POST /documents/import` — document import
- `POST /documents/delete` — document deletion
- `rag/client.py` — HTTP client for calling RAG service from MCP and other components
- `rag/http_models.py` — Pydantic models for HTTP contract
- MCP server (`server.py`) uses `RagClient` for calling RAG via HTTP
- `tools/rag.py` left as a **minimalistic stub** for backward compatibility with `fixtures/document_generator.py`
## RAG by Documents
RAG layer works locally through SQLite + ChromaDB:
1. `import_document` reads the file.
2. Text is split into chunks.
3. For each chunk, an embedding is calculated using the `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` model.
4. Vectors and chunk texts are saved to ChromaDB.
5. Document metadata is saved to SQLite.
6. `search_documents` searches for similar fragments in ChromaDB.
7. `get_rag_context` returns fragments and instructions for the model: answer only according to the found sources.
By default, ChromaDB stores the index in the `chroma_db/` folder.
When using RAG for the first time, the embedding model may be downloaded from Hugging Face if the launch is done through the MCP server and `RAG_LOCAL_FILES_ONLY` is not enabled. CLI `agent-ingest` currently forcibly sets `RAG_LOCAL_FILES_ONLY=1`, so for it, the model must already be in the local cache or specified by a local path through `RAG_EMBEDDING_MODEL`.
```bash
RAG_LOCAL_FILES_ONLY=1
RAG_DEVICE=cuda
```
Example:
```bash
uv run agent-ingest import ~/Documents/test.pdf
```
```text
Find task number 11 in Методичка по базам данных
```
## Quick Start
```bash
git clone https://github.com/trash2bin/agent-tutor
cd agent-tutor
```
The project is managed through `uv` and described in `pyproject.toml`. Main entrypoint commands:
- `agent-tutor` — MCP server, entry point `mcp_server.server:main`.
- `agent-ingest` — CLI for documents and generation, entry point `fixtures.ingest:main`.
- `rag-service` — HTTP RAG service, entry point `rag.service:main`.
Basic commands:
```bash
uv sync
uv run agent-ingest --help
uv run mcp dev server.py
uv run python -m rag.service # Launch RAG HTTP service on port 8082
uv tool install . --reinstall
```
Use `uv run ...` for development. `uv tool install . --reinstall` is needed when you need to update a globally installed CLI command after changing the code.
## Demo Site and Chat
For demonstration purposes, there are two components separated into API and web parts:
- **API Server** (`demo/api/server.py`) — REST/SSE API over the model, responsible for calling models via LiteLLM, working with tools, and managing context
- **Web Server** (`demo/web/server.py`) — static site with a data showcase and a floating chat window
API Architecture:
- `demo/api/agent/` — agent logic: calling models through LiteLLM, connecting to MCP server, calling tools, validating calls, recursive model calls, context memory, streaming answers, processing reasoning; the main orchestrator is located in `demo/api/agent/orchestrator.py`
- `demo/api/backlog.py` — full backlog of interactions: JSONL file for each session with requests, answers, tool calls, tokens, and timings (stores data in `backlog/`)
- `demo/api/server.py` — HTTP API with endpoints: `GET /health`, `GET /api/data`, `POST /api/chat` (Server-Sent Events), `GET /api/backlog`, `GET /api/backlog/{session_id}`
### Key Features
- **LiteLLM Integration**: The agent uses LiteLLM for unified work with various LLM providers
- **Thought Mode**: The model can reason in several stages to come to a specific answer for the user
- **Streaming Answers**: Streaming transmission of tokens and events through Server-Sent Events (SSE)
- **Session Memory**: Automatic storage of dialogue history for each session
- **Full Backlog**: All interactions with the model are logged in JSONL files in the `backlog/` folder
Launch:
```bash
# API Server (default port 8081)
uv run python -m demo.api.server
# Launch with Mostral
DEMO_DEBUG=1 MISTRAL_API_KEY={TOKEN} MISTRAL_MODEL=mistral-medium uv run python -m demo.api.server
# Web Server (default port 8080)
uv run python -m demo.web.server
```
By default, the site is available at `http://127.0.0.1:8080`, and the API is available at `http://127.0.0.1:8081`.
Environment Variables:
- `DEMO_API_HOST`/`DEMO_API_PORT` — host/port of the API server (default `127.0.0.1:8081`)
- `DEMO_WEB_HOST`/`DEMO_WEB_PORT` — host/port of the web server (default `127.0.0.1:8080`)
- `OLLAMA_URL` — Ollama address (default `http://127.0.0.1:11434`)
- `OLLAMA_MODEL` — Ollama model (default `qwen2.5:0.5b`)
- `DEMO_REQUEST_TIMEOUT` — request timeout (default `600` seconds = 10 minutes)
- `ENABLE_THINK` — enable thought/reasoning mode (`true`/`false`, default `true`)
- `DEMO_HISTORY_TURNS` — number of stored turns in session history (default `8`)
- `DEMO_HISTORY_CONTENT_CHARS` — maximum length of content in history (default `6000`)
- `BACKLOG_DIR` — folder for JSONL backlog files (default `./backlog/`)
- `BACKLOG_RETENTION_DAYS` — days to store backlog files (default `30`)
- `LITELLM_DEBUG` — enable LiteLLM debug mode (`true`/`false`)
- `DEMO_DEBUG` — enable demo debug mode
Example launch with a custom model:
```bash
OLLAMA_MODEL=carstenuhlig/omnicoder-9b:latest uv run python -m demo.api.server
```
> **Note:** A running local Ollama (or another provider supported by LiteLLM) is required for the demo to work.
Synchronize dependencies in the local environment `.venv`:
```bash
uv sync
```
Run commands without activating the environment:
```bash
uv run agent-ingest --help
uv run agent-tutor
```
## Virtual Environment
Usually, it's enough to use `uv sync`: it creates `.venv` if it doesn't exist and installs dependencies from `pyproject.toml`/`uv.lock`.
Useful commands for working with the virtual environment:
| Command | What it does |
|---|---|
| `uv sync` | Create/update `.venv` according to `uv.lock` |
| `uv run <command>` | Run a command inside the project environment |
| `uv venv --python 3.12` | Explicitly create `.venv` with Python 3.12 |
| `source .venv/bin/activate` | Activate the environment in the current shell |
| `deactivate` | Exit the activated environment |
| `uv pip list` | Show packages in `.venv` |
| `uv add <package>` | Add a runtime dependency to `pyproject.toml` |
| `uv remove <package>` | Remove a dependency from the project |
| `rm -rf .venv && uv sync` | Recreate the environment from scratch |
For development, it's usually more convenient to use `uv run ...`, as the command always uses the current code and dependencies from the project. Installation via `uv tool install . --reinstall` is needed when you need to update a globally installed CLI command after changing the code.
If you need a test database (`fixtures.json`):
```bash
uv run python fixtures/generate.py
```
Install CLI commands as `uv tool`:
```bash
uv tool install .
```
Rebuild the package after changing the code:
```bash
uv tool install . --reinstall
```
`uv tool install` puts the command in a separate tool environment. For development, it's usually more convenient to use `uv run ...`, as the command always uses the current code and dependencies from the project.
## uv Virtual Environment
Usually, it's enough to use `uv sync`: it creates `.venv` if it doesn't exist and installs dependencies from `pyproject.toml`/`uv.lock`.
Useful commands:
| Command | What it does |
|---|---|
| `uv sync` | Create/update `.venv` according to `uv.lock` |
| `uv run <command>` | Run a command inside the project environment |
| `uv venv --python 3.12` | Explicitly create `.venv` with Python 3.12 |
| `source .venv/bin/activate` | Activate the environment in the current shell |
| `deactivate` | Exit the activated environment |
| `uv pip list` | Show packages in `.venv` |
| `uv add <package>` | Add a runtime dependency to `pyproject.toml` |
| `uv remove <package>` | Remove a dependency from the project |
| `rm -rf .venv && uv sync` | Recreate the environment from scratch |
## CLI `agent-ingest`
`agent-ingest` is located in `fixtures/ingest.py`. It works with a local SQLite database, ChromaDB index, and a generator of training materials. It forcibly sets `RAG_LOCAL_FILES_ONLY=1`, so the embedding model must be in the local cache or specified by a local path via `RAG_EMBEDDING_MODEL`.
General format:
```bash
uv run agent-ingest <command> [options]
```
If the package is installed via `uv tool install . --reinstall`, you can run it without `uv run`:
```bash
agent-ingest <command> [options]
```
Commands:
| Command | Purpose |
|---|---|
| `import <path>` | Load PDF/DOCX/TXT/MD/HTML into RAG index |
| `list` | Show documents in the index |
| `search <query>` | Check semantic search without MCP server |
| `generate -d <discipline-id>` | Generate materials for one discipline |
| `generate-all` | Generate materials for all disciplines |
| `clear-generated` | Remove generated materials from SQLite, ChromaDB, and disk |
| `delete` | Remove one document from the index |
Command options:
| Command | Option | What it does |
|---|---|---|
| `import` | `path` | Path to the document: PDF, DOCX, TXT, MD, or HTML |
| `import` | `--discipline-id`, `-d` | Bind the document to a discipline |
| `import` | `--title`, `-t` | Set a human-readable document title |
| `list` | `--discipline-id`, `-d` | Show documents of only one discipline |
| `search` | `query` | Search query |
| `search` | `--discipline-id`, `-d` | Search only in documents of one discipline |
| `search` | `--limit`, `-n` | Number of results, default `5` |
| `generate` | `--discipline-id`, `-d` | Discipline ID, required option |
| `generate` | `--force` | Recreate discipline files |
| `generate` | `--model`, `-m` | Override Ollama model for launch |
| `generate-all` | `--force` | Recreate files for all disciplines |
| `generate-all` | `--model`, `-m` | Override Ollama model for launch |
| `clear-generated` | `--discipline-id`, `-d` | Remove generation of only one discipline |
| `delete` | `--path` | Remove a document by exact original path |
| `delete` | `--document-id` | Remove a document by ID from `agent-ingest list` |
Load a document into RAG:
```bash
# Loaded lectures
uv run agent-ingest import ./lectures/lec01.pdf -d "cs-101" -t "Lecture 1: Introduction"
uv run agent-ingest import ./lectures/lec02.pdf -d "cs-101" -t "Lecture 2: Sorting"
uv run agent-ingest import ./textbook.docx -t "Alohi Textbook"
# Checked what was loaded
uv run agent-ingest list
# Tested search
uv run agent-ingest search "how quicksort works" -n 3
# If the result is not satisfactory
uv run agent-ingest import ./lectures/lec02.pdf -d "cs-101" -t "Lecture 2: Sorting (corrected)"
# Deletion
uv run agent-ingest delete --document-id e077bb64-31e6-4d37-98c6-d2f350d7a947
```
## Environment Variables
`agent-ingest` sets the following environment variables on startup:
| Variable | Value | Purpose |
|---|---|---|
| `RAG_LOCAL_FILES_ONLY` | `1` | Do not download the embedding model from the network during CLI import |
| `HF_HUB_DISABLE_TELEMETRY` | `1` | Disable Hugging Face telemetry |
| `TOKENIZERS_PARALLELISM` | `false` | Remove extra parallelism/warning from tokenizer libraries |
Database and RAG settings:
| Variable | Default | Purpose |
|---|---|---|
| `DB_PATH` | `./university.db` | Path to the SQLite database |
| `RAG_SERVICE_URL` | `http://127.0.0.1:8082` | URL of the RAG HTTP service for the MCP server |
| `RAG_HOST` | `127.0.0.1` | Host of the RAG service |
| `RAG_PORT` | `8082` | Port of the RAG service |
| `RAG_HTTP_TIMEOUT` | `60.0` | Timeout for HTTP requests to the RAG service (seconds) |
| `RAG_EMBEDDING_MODEL` | `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | HF ID or local path to the embedding model |
| `RAG_EMBEDDING_BATCH_SIZE` | `64` | Batch size for embeddings |
| `RAG_DEVICE` | `cpu` | Device for embeddings: `cpu`, `cuda`, `mps` |
| `RAG_CHUNKER_TYPE` | `semantic` | Type of chunking: `semantic`, `recursive`, `sentence` |
| `RAG_CHUNK_SIZE` | `512` | Target chunk size |
| `RAG_CHUNK_OVERLAP` | `80` | Chunk overlap |
| `RAG_PAGE_OVERLAP_TOKENS` | `50` | Text overlap between pages |
| `CHROMA_PATH` | `./chroma_db` | Folder for the ChromaDB index |
| `CHROMA_COLLECTION` | `university_documents` | Name of the ChromaDB collection |
| `RAG_CONTEXT_MAX_TOKENS` | `8000` | Maximum context size for `get_rag_context` |
Material generation settings:
Material generation requires a local Ollama. Check availability: `curl ${OLLAMA_HOST:-http://127.0.0.1:11434}/api/tags`.
| Variable | Default | Purpose |
|---|---|---|
| `DOCGEN_MODEL` | `qwen2.5:0.5b` | Ollama model for text generation |
| `OLLAMA_HOST` | `http://127.0.0.1:11434` | Base address of Ollama, if not set `DOCGEN_OLLAMA_URL` |
| `DOCGEN_OLLAMA_URL` | `$OLLAMA_HOST/api/generate` | Full endpoint of the Ollama generate API |
| `DOCGEN_NUM_PREDICT` | `4500` | Limit of generated tokens |
| `DOCGEN_MAX_ATTEMPTS` | `2` | Number of attempts to get a non-too-short answer |
| `DOCGEN_MIN_RESPONSE_CHARS` | `120` | Minimum desired response size from the model |
| `DOCGEN_FAKE_SEED` | not set | Seed for reproducible Faker framework |
| `DOCGEN_OUTPUT_DIR` | `./generated_materials` | Folder for PDF/DOCX files |
Example of isolated launch:
```bash
DB_PATH=/tmp/agent-tutor.db \
CHROMA_PATH=/tmp/agent-tutor-chroma \
uv run agent-ingest list
```
Run through MCP Inspector to check tools:
```bash
# To work with the HTTP RAG service, you need to launch it separately
uv run python -m rag.service
# In another terminal, launch the MCP server with the specified RAG service URL
RAG_SERVICE_URL=http://127.0.0.1:8082 uv run mcp_serve/server.py
```
In the browser, select the transport **HTTP** (or **STDIO** for development).
For STDIO: specify the command `uv`, arguments `mcp_serve/server.py`, then click Connect.
For HTTP: specify the URL `http://127.0.0.1:8083/mcp` (if the MCP server is launched with HTTP transport).
### Example request to the model:
```
What materials are available to a student with ID "456c4e68-290a-4f8b-b0d0-545534adaf3e" for their disciplines?
```
## Current State
The project is at the stage of a working prototype. **Stages 0.0, 0.1, 0.2, 0.3 from ROADMAP have been completed**.
It works:
- MCP server starts and publishes tools (HTTP transport on port 8083, mount_path `/mcp`)
- SQLite database is initialized and loads fixtures on startup
- Tools return typed Pydantic responses
- RAG metadata is stored in SQLite, and the vector index of documents is stored in ChromaDB
- **RAG is separated into a separate HTTP service** (`rag/service.py`) - the service starts on port 8082
- **MCP server uses an HTTP client** (`rag/client.py`) to call RAG via `RAG_SERVICE_URL`
- Direct connectivity between MCP and RAG pipeline has been removed
- `mcp_server/tools/rag.py` is left as a **minimalistic stub** for backward compatibility with `fixtures/document_generator.py`
- **API service has been translated to FastAPI** (previously Starlette), SSE streaming is preserved
- **API uses an HTTP client to MCP** (`mcp.client.streamable_http.streamablehttp_client`)
- Tested in MCP Inspector and Goose, Claude Code, Pi
- **Demo part**: LiteLLM integration, response streaming, session memory, thinking mode, full backlog of all interactions with the model
- The site is available for demonstration
- A custom client on the site already leads a logical narrative and calls tools to solve the user's task
### How to run after changes
```bash
# Terminal 1: RAG HTTP service (port 8082)
uv run python -m rag.service
# Terminal 2: MCP server (port 8083, HTTP transport, uses RAG over HTTP)
RAG_SERVICE_URL=http://127.0.0.1:8082 uv run python -m mcp_server.server
# Terminal 3: API server (port 8081, FastAPI, uses MCP over HTTP)
MCP_SERVICE_URL=http://127.0.0.1:8083/mcp uv run python -m demo.api.server
# Terminal 4: Web server (port 8080)
uv run python -m demo.web.server
```
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available