moodle-mcp
by zuckdorsey
README.md
<div align="center">
# moodle-mcp
> **Model Context Protocol server for Moodle LMS** — connect Hermes, Claude Code, and OpenCode to your Moodle. Fetch assignments, grades, and deadlines, sync to Obsidian, and get WhatsApp alerts.
[](https://python.org)
[](https://modelcontextprotocol.io)
[](https://mseep.ai/app/loyaniu-moodle-mcp)
[](LICENSE)
[Overview](#overview) • [Features](#features) • [Quick Start](#quick-start) • [Configuration](#configuration) • [Agent Setup](#agent-setup) • [Tools](#tools) • [Obsidian Sync](#obsidian-sync)
</div>
---
## Overview
`moodle-mcp` bridges the Moodle Web Services API with the [Model Context Protocol](https://modelcontextprotocol.io), so AI agents can act as your academic assistant. It was built for **Polibatam** (multi-class filtering) but works with any Moodle instance that has Web Services enabled.
Fork of [loyaniu/moodle-mcp](https://github.com/loyaniu/moodle-mcp) — extended from 22 to **40 tools** with Obsidian sync, material downloads, submission tools, calendar integration, concurrent fetching, and semester auto-archive.
> [!TIP]
> Works with any Moodle LMS — just point `MOODLE_URL` at your instance. The Polibatam class filter (`MOODLE_MY_CLASS`) is optional.
---
## Features
- **Assignments & deadlines** — filtered by class slot, sorted by urgency, with actionable task lists
- **Grades & progress** — course health checks, study load, and completion tracking
- **Course content & search** — sections, modules, materials, announcements, recent activity
- **Obsidian sync** — one-command export of dashboards, deadlines, and course notes (with semester auto-archive)
- **Material downloads** — list and download course files and assignment attachments
- **Submissions** — submit text, check status, and read feedback
- **Calendar** — upcoming events, create reminders (H-2), mark activities complete
- **Agent-ready** — single `install-mcp.sh` for Hermes, Claude Code, and OpenCode
---
## Quick Start
### One-line install
```bash
curl -fsSL https://raw.githubusercontent.com/zuckdorsey/moodle-mcp/main/scripts/install-mcp.sh | bash
```
Auto-detects installed agents and configures each one. For a dry run:
```bash
bash scripts/install-mcp.sh --dry-run
```
### Verify
```bash
# Hermes (profile: akademik)
hermes --profile akademik mcp test moodle
# → ✓ Connected ✓ Tools discovered: 40
# Claude Code
claude mcp list
# Run tests
PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -v
```
> [!NOTE]
> Prefer local source over PyPI when developing — the launcher sets `PYTHONPATH` to `src/` automatically.
---
## Configuration
### 1. Get your Moodle token
1. Open `https://<your-moodle>/user/managetoken.php`
2. Find the row with **Moodle mobile web service** in `Service`
3. Copy the token
> [!NOTE]
> Some sites hide or disable `user/managetoken.php` (Polibatam does), leaving no UI to read or
> regenerate the token. Use `scripts/get_moodle_token.py`, which goes through the official
> mobile web service instead:
>
> ```bash
> # interactive — prompts for the password and never echoes or logs it
> python scripts/get_moodle_token.py --site https://learning.polibatam.ac.id --username <you>
>
> # verify a token you already have
> python scripts/get_moodle_token.py --site https://learning.polibatam.ac.id --verify "$MOODLE_TOKEN"
>
> # fetch, verify, then write it into every Hermes .env that already defines MOODLE_TOKEN
> python scripts/get_moodle_token.py --username <you> --hermes
> ```
>
> Two strategies, both token-verified via `core_webservice_get_site_info` before reporting success:
>
> | Strategy | How it works | Use when |
> |---|---|---|
> | `login-token` | `POST /login/token.php` with `service=moodle_mobile_app` | native (non-SSO) account, works in one request |
> | `mobile-launch` | log in, then decode the `moodlemobile://token=<base64>` redirect from `admin/tool/mobile/launch.php` | `login/token.php` is blocked; also works with a browser session |
>
> `--method auto` (default) tries `login-token` first and falls back to `mobile-launch`. For
> SSO-only accounts, log in with your browser, copy the `MoodleSession` cookie value and pass it
> with `--cookie`. Other flags: `--write-env PATH` (repeatable), `--quiet` (token only, for
> piping), `--json`. Exit codes: `0` success, `1` auth/API failure, `2` usage error.
>
> Restart the gateway afterwards — a running MCP server keeps the environment it was spawned with.
### 2. Create `.env`
```bash
cp .env.example .env
```
```dotenv
MOODLE_URL=https://your-moodle.example.com/webservice/rest/server.php
MOODLE_TOKEN=your_token_here
# Optional — Polibatam class-slot filter (e.g. Pagi C, Siang A)
MOODLE_MY_CLASS=Pagi C
# Optional — custom vault path
OBSIDIAN_VAULT_PATH=/home/you/Obsidian Vault
```
| Variable | Required | Description |
|---|---|---|
| `MOODLE_URL` | yes | Moodle REST endpoint (`.../webservice/rest/server.php`) |
| `MOODLE_TOKEN` | yes | Mobile web service token |
| `MOODLE_MY_CLASS` | no | Regex filter for multi-class assignment titles |
| `OBSIDIAN_VAULT_PATH` | no | Vault root (default: `~/Obsidian Vault`) |
### 3. Install package
```bash
# with uv (recommended)
uv pip install -e .
# or pip
pip install -e .
```
> [!WARNING]
> Never commit `.env` — it contains a token with full API access. It is already in `.gitignore`.
---
## Agent Setup
Pick your agent — all use the same local launcher at `scripts/moodle_mcp_local_launch.py`.
### Hermes Agent (recommended)
```bash
bash scripts/install-mcp.sh --agent hermes --profile akademik
```
Manual (`~/.hermes/profiles/akademik/config.yaml`):
```yaml
mcp_servers:
moodle:
command: /home/you/Programming/Python/moodle-mcp/.venv/bin/python
args: [/home/you/Programming/Python/moodle-mcp/scripts/moodle_mcp_local_launch.py]
env:
MOODLE_URL: ${MOODLE_URL}
MOODLE_TOKEN: ${MOODLE_TOKEN}
MOODLE_MY_CLASS: "Pagi C"
OBSIDIAN_VAULT_PATH: "/home/you/Obsidian Vault"
```
### Claude Code
```bash
bash scripts/install-mcp.sh --agent claude-code
# or manually
claude mcp add -s user moodle-mcp -- python3 /path/to/moodle-mcp/scripts/moodle_mcp_local_launch.py
```
Global JSON (`~/.claude/settings.json`):
```json
{
"mcpServers": {
"moodle-mcp": {
"command": "python3",
"args": ["/path/to/moodle-mcp/scripts/moodle_mcp_local_launch.py"],
"env": {
"MOODLE_URL": "https://your-moodle/webservice/rest/server.php",
"MOODLE_TOKEN": "your_token_here"
}
}
}
}
```
### OpenCode
```bash
bash scripts/install-mcp.sh --agent opencode
```
`~/.config/opencode/config.json`:
```json
{
"mcp": {
"moodle-mcp": {
"command": "python3",
"args": ["/path/to/moodle-mcp/scripts/moodle_mcp_local_launch.py"],
"environment": {
"MOODLE_URL": "https://your-moodle/webservice/rest/server.php",
"MOODLE_TOKEN": "your_token_here"
}
}
}
}
```
### Claude Desktop / Cursor
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"moodle-mcp": {
"command": "uvx",
"args": ["moodle-mcp"],
"env": {
"MOODLE_URL": "https://your-moodle/webservice/rest/server.php",
"MOODLE_TOKEN": "your_token_here"
}
}
}
}
```
Restart the desktop app — tools appear in the picker.
---
## Tools
40 tools across 7 groups. Call them by name from any connected agent (e.g. *"list my Moodle courses"* → `get_my_courses`).
<details>
<summary><strong>Courses & Content (6)</strong></summary>
| Tool | Description |
|---|---|
| `get_my_courses` | Enrolled courses |
| `get_course_content` | Sections & modules for a course |
| `search_course_materials` | Search across all materials |
| `get_course_announcements` | News forum announcements |
| `get_recent_activity` | Recent activity since timestamp |
| `get_course_updates` | New materials/announcements |
</details>
<details>
<summary><strong>Assignments & Deadlines (12)</strong></summary>
| Tool | Description |
|---|---|
| `get_assignments` | Assignments (class-slot filtered) |
| `get_assignment_status` | Submission & grading status |
| `get_upcoming_deadlines` | Deadlines sorted by due date |
| `get_overdue_assignments` | Past-due, unsubmitted |
| `get_actionable_tasks` | Prioritized urgency list |
| `analyze_assignment` | Status + requirements + materials |
| `extract_assignment_requirements` | Deliverables & criteria |
| `find_relevant_materials` | Content relevant to assignment |
| `decompose_task` | Subtasks with critical path |
| `create_implementation_plan` | Timeline, resources, milestones |
| `submit_assignment_text` | Submit text answer |
| `get_assignment_feedback` | Feedback & rubric results |
</details>
<details>
<summary><strong>Grades & Progress (5)</strong></summary>
| Tool | Description |
|---|---|
| `get_grades` | Overview or per-course detail |
| `get_course_progress` | Completion progress |
| `get_course_health` | Health check (progress + grades + overdue) |
| `get_study_load` | Assignment distribution by week |
| `get_submission_status_detail` | Detailed submission & feedback |
</details>
<details>
<summary><strong>Calendar & Completion (4)</strong></summary>
| Tool | Description |
|---|---|
| `get_upcoming_events` | Upcoming Moodle events |
| `create_calendar_event` | Create H-2 reminder |
| `get_activity_completion` | Completion status |
| `mark_activity_complete` | Mark activity complete |
</details>
<details>
<summary><strong>Overviews & Q&A (5)</strong></summary>
| Tool | Description |
|---|---|
| `semester_dashboard` | Courses + deadlines + grades |
| `daily_briefing` | Overdue, today, recent grades |
| `weekly_review` | Submitted, graded, overdue, progress |
| `ask_moodle` | Natural language routing |
| `get_course_updates` | Course change detection |
</details>
<details>
<summary><strong>Obsidian & Downloads (6)</strong></summary>
| Tool | Description |
|---|---|
| `sync_moodle_to_obsidian` | Full vault sync (auto-archive on semester rollover) |
| `export_deadlines_to_obsidian` | Deadlines only |
| `export_course_outline` | Course outline as note |
| `list_course_material_files` | List downloadable files |
| `download_course_materials` | Download to `Materials/` |
| `download_assignment_attachments` | Download assignment files |
</details>
> [!TIP]
> Try: *"what's due this week?"* → `get_upcoming_deadlines`, *"analyze tugas basis data"* → `analyze_assignment`, *"sync ke Obsidian"* → `sync_moodle_to_obsidian`.
---
## Obsidian Sync
Sync creates notes under `Academic/Moodle` in your vault:
```
Obsidian Vault/Academic/
Moodle/ ← current semester (always up-to-date)
.semester_courses.json ← hidden state (course IDs)
Dashboard.md
Deadlines.md
Grades.md
Courses/
Archive/
Semester-2026-07/ ← auto-created on rollover
Dashboard.md
Courses/
Archive-README.md
```
**Semester auto-archive** triggers when >50% of course IDs change (or all are new). A single-course swap is ignored.
```bash
PYTHONPATH=src python - <<'PY'
from moodle_mcp import api
print(api.sync_moodle_to_obsidian())
PY
```
---
## Security Notes
> [!CAUTION]
> Your Moodle token grants full API access as your user. Treat it like a password.
- Tokens are loaded server-side and never sent to the model.
- Inject `MOODLE_URL` / `MOODLE_TOKEN` as environment secrets in CI.
- `moodle.py` uses browser-like `User-Agent` + POST for Cloudflare compatibility.
- `scripts/get_moodle_token.py` accepts the password only via `getpass` prompt, `--password-stdin`
or `--password`; it never writes it to disk and never logs it. Token values are reported as
`len=<n> sha256:<10>` fingerprints unless you ask for the token itself.
---
## Acknowledgements
Fork of [loyaniu/moodle-mcp](https://github.com/loyaniu/moodle-mcp) — credit to all original contributors:
| Contributor | Role |
|---|---|
| [Zhonglin Niu](https://github.com/loyaniu) | Original author, Moodle REST integration, core server |
| [Vitalii Liudvynskyi](https://github.com/v-liudwinski) | Dashboard, study load, health checks |
| [Daniel Sticker](https://github.com/stickerdaniel) | Course content & search fixes |
| [Lawrence Sinclair](https://github.com/lwsinclair) | Security assessment badge |
**Additions in this fork**
- Obsidian sync + semester auto-archive
- Material downloads & assignment attachments
- Submission & feedback tools, calendar & completion
- Polibatam class-slot filter (`MOODLE_MY_CLASS`)
- Concurrent fetch (~10× faster actionable tasks)
- Universal `install-mcp.sh` for Hermes / Claude Code / OpenCode
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues