moodle-mcp

mcp
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 7 GitHub stars
Code Pass
  • Code scan — Scanned 3 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

MCP server that lets AI agents operate Moodle: courses, assignments, forums, grades and files-as-markdown.

README.md

moodle-mcp hero banner

CI Codecov Release crates.io License: MIT Rust MCP Release

English | 繁體中文 | 简体中文 | 日本語 | Español | Deutsch | Português | Français

A Model Context Protocol server that lets
LLM agents use a real Moodle LMS — read courses, fetch assignment PDFs,
write code, upload and submit — through a single static binary.

Built in Rust on the official MCP SDK (rmcp).
Validated live against Moodle 3.7 → 5.x.


❌ Without moodle-mcp

A generic chatbot has no way into your Moodle, so every study task stays manual:

  • ❌ Five browser tabs open just to answer "what's due this week?"
  • ❌ Assignment PDFs downloaded by hand, then re-uploaded into a chat window
  • ❌ Forum threads and grades copy-pasted one page at a time

✅ With moodle-mcp

The agent signs in as you. 22 typed tools cover the full student journey just like a
real human — from a quick deadline check to fully automated assignment assistance,
without ever opening a browser:

What's due in the next two weeks? Plan my studying around it.
Finish my Information Security HW1: read the assignment brief and the
attached PDF, work through the problems, and submit a draft.
I missed what happened in Data Communication this week — catch me up.

The agent chains the tools itself: course map → assignment brief → PDF →
markdown → solution → upload → draft submission → read-back verification.
Full journeys in Prompt examples and
How the agent works.

✨ Features

  • Batteries included — document conversion (anydoc), HTML → markdown
    (htmd), auth CLI and the escape hatch ship in the box: no plugins,
    no helper scripts, no second runtime
  • Agent-native — tools take and return local file paths instead of
    base64; results embed the IDs for the next tool; defaults come with hints
    ("pass course_ids to filter"); errors are plain-language and suggest the fix
  • 22 curated tools, human-shaped — each one mirrors a step you'd
    actually take clicking through Moodle: courses, course map, assignments,
    submission, forums, files (fetch/convert/download/upload), grades,
    schedule, messaging, quiz attempts — not a 1:1 dump of the ~350 raw
    web-service functions; a generic escape hatch (call_ws_function +
    list_functions) still reaches everything else
  • Course map in one call — get_course_overview merges five Moodle
    endpoints into a single markdown document with every ID and file URL embedded
  • PDF → markdown — assignment briefs become readable requirements via
    anydoc; HTML descriptions keep their
    tables via htmd
  • Version-adaptive — verified against Moodle 3.7 and designed for 4.x/5.x;
    differences (function names, pagination, response shapes) are absorbed
    automatically
  • Safe by default — write tools require explicit confirmation; drafts are
    preferred over irreversible submissions; quiz-taking is intentionally not
    provided
  • Single static binary — no runtime dependencies

📋 Requirements

  • A Moodle 3.7 → 5.x site you have an account on
  • Web services enabled site-side: REST protocol and the moodle_mobile_app
    service allowed for your account
    — the same switches the official Moodle mobile
    app uses, so most sites already qualify
  • HTTPS strongly recommended — the token travels with every request (see
    Security notes)
  • Any MCP client — Claude Code, Codex, OpenCode, pi, or anything that speaks MCP
    over stdio
  • Rust 1.88+ only for cargo install or building from source (prebuilt
    binaries need nothing)

📦 Install

Download a prebuilt binary

No Rust toolchain needed — grab the archive for your platform from the
latest release:

Platform Archive
Linux x86_64 moodle-mcp-<version>-x86_64-unknown-linux-gnu.tar.gz
Linux arm64 moodle-mcp-<version>-aarch64-unknown-linux-gnu.tar.gz
macOS Apple Silicon moodle-mcp-<version>-aarch64-apple-darwin.tar.gz
macOS Intel moodle-mcp-<version>-x86_64-apple-darwin.tar.gz
Windows x64 moodle-mcp-<version>-x86_64-pc-windows-msvc.zip
tar xzf moodle-mcp-*-aarch64-apple-darwin.tar.gz
sudo mv moodle-mcp-*/moodle-mcp /usr/local/bin/   # or anywhere on PATH

Or install with cargo

cargo install moodle-mcp   # needs Rust 1.88+

Or build from source

git clone https://github.com/G36maid/moodle-mcp
cd moodle-mcp
cargo build --release
# binary: target/release/moodle-mcp (~15 MB, static)

Get a token

Use the built-in CLI (same binary):

moodle-mcp auth
# Moodle URL (e.g. https://moodle.example.com): https://moodle.example.com
# Username: yourname
# Password: ********   (masked as you type)
# → export MOODLE_URL='https://moodle.example.com' MOODLE_TOKEN='…'

Anything passed as a flag (--url, --username, --password) skips the
matching prompt, so scripts work non-interactively too.

With SSO (no password), open the Security keys page in a browser instead:

moodle-mcp auth --url https://moodle.example.com --open

If the token request fails, the site is missing something from
Requirements — ask your admin to enable web services.

Connect a client

All four clients read the same stdio server: launch /path/to/moodle-mcp with
MOODLE_URL / MOODLE_TOKEN in its environment. Claude Code can also be
wired up with a single command:

claude mcp add moodle \
  --env MOODLE_URL=https://moodle.example.com \
  --env MOODLE_TOKEN=your-token \
  -- /path/to/moodle-mcp

Otherwise, pick your client:

Claude Code — .mcp.json (project) or ~/.claude.json (global)
{
  "mcpServers": {
    "moodle": {
      "command": "/path/to/moodle-mcp",
      "env": {
        "MOODLE_URL": "https://moodle.example.com",
        "MOODLE_TOKEN": "your-token"
      }
    }
  }
}
Codex — ~/.codex/config.toml (global)
[mcp_servers.moodle]
command = "/path/to/moodle-mcp"
env = { "MOODLE_URL" = "https://moodle.example.com", "MOODLE_TOKEN" = "your-token" }
OpenCode — opencode.json (project) or ~/.config/opencode/opencode.json (global)
{
  "mcp": {
    "moodle": {
      "type": "local",
      "command": ["/path/to/moodle-mcp"],
      "environment": {
        "MOODLE_URL": "https://moodle.example.com",
        "MOODLE_TOKEN": "your-token"
      }
    }
  }
}
pi — .pi/mcp.json (project) or ~/.pi/agent/mcp.json (global)
{
  "mcpServers": {
    "moodle": {
      "command": "/path/to/moodle-mcp",
      "env": {
        "MOODLE_URL": "https://moodle.example.com",
        "MOODLE_TOKEN": "your-token"
      }
    }
  }
}
Env var Required Default Description
MOODLE_URL ✔ — Site base URL, no trailing slash
MOODLE_TOKEN ✔ — Web service token
MOODLE_TIMEOUT_SECS — 60 Per-request timeout
RUST_LOG — info Log filter (logs → stderr; stdout is the MCP stream)

💬 Prompt examples

All of these work with no further setup — the agent discovers the tools from
their descriptions and chains them itself:

Full-auto (one prompt, whole journey)

You say The agent chains
"Finish my Information Security HW1: read the assignment brief and the attached PDF, work through the problems, and submit a draft." get_assignment (brief + attachments) → fetch_file (PDF → markdown, math included) → solves it → writes the answer file → upload_file → submit_assignment → get_assignment (verify submitted ✓)
"Based on upcoming deadlines and course materials, build me a complete study plan for the next two weeks." get_schedule (all deadlines) → get_course_overview per course (materials & reading list) → day-by-day plan balanced against due dates
"I missed what happened in Data Communication this week — catch me up, including the announcements forum." get_notifications + get_forum_discussions → get_discussion_posts → synthesized weekly digest
"Prep me for the Software Engineering quiz: what did the teacher post about it?" get_course_overview (quiz entry) → get_assignment (requirements) → related forum threads

Quick queries

You say The agent chains
"What's due in the next two weeks?" get_schedule → merged timeline of due dates, quiz closes, events
"What's the HW1 assignment about? Read the brief." get_assignment → fetch_file converts the attached PDF to markdown
"How did I do on the Data Communication labs?" list_courses → get_grades(course_id) → items, feedback, weights
"Which of my courses have graded items?" get_grades() → cross-course summary
"Summarize every announcement in the Data Communication forum." get_course_overview → get_forum_discussions → get_discussion_posts
"Upload my solution.pdf for Quiz1 and save it as a draft." upload_file → submit_assignment(item_id, finalize=false)
"What's on the agenda for the Software Engineering course?" get_course_overview → sections, files, forums
"Can I message my teacher about an extension?" get_user / get_conversations → draft → confirms → send_message

🔄 How the agent works

The tools mirror how you navigate Moodle — each level's output embeds the IDs
for the next. Every journey starts from the course list and the course map;
from there the agent drills down or runs a full workflow on its own:

flowchart TD
    Q["💬 prompt (intent only)"] --> LC["list_courses(search?)<br/>find the course"]
    LC --> OV["get_course_overview(course_id)<br/>markdown map: sections · files ·<br/>assignments · quizzes · forums · grades"]
    OV --> FD["get_forum_discussions(forum_id)<br/>→ get_discussion_posts(discussion_id)"]
    OV --> AS["get_assignment(assignment_id)<br/>brief · attachments · submission · grade"]
    AS --> FF["fetch_file(url)<br/>assignment PDF → readable markdown"]
    FF --> WK["agent works:<br/>answers · code · report on disk"]
    WK --> UP["upload_file(path, filename)<br/>solution → draft area → item_id"]
    UP --> SB["submit_assignment(assignment_id, item_id?, finalize?)<br/>save draft or turn in"]
    SB --> VF["get_assignment<br/>verify: submitted ✓ files ✓"]
    OV --> SC["get_schedule(time window)<br/>deadlines → study plan"]
    OV --> GR["get_grades(course_id)<br/>graded items + feedback"]

The same building blocks compose into larger flows — a study plan is
get_schedule (deadlines) plus get_course_overview per course (materials);
a weekly digest is get_notifications plus the forum read chain.

Everything the 20 dedicated tools don't cover is reachable through the
call_ws_function + list_functions escape hatch — full coverage of the
~350 functions a typical token can call (20 + 2 = 22 in total).

🛠 Tool reference

RW — what a call does to your Moodle: 📖 read-only · ✍️ write. Write tools act
as the token owner; the server's instructions tell the agent to confirm with you
first, and uploads land in your private draft area, not with your teacher.

Identity & courses

Tool Signature RW Purpose
whoami () 📖 Token owner identity + site/release
list_courses (search?) 📖 Enrolled courses; optional name filter
get_course_overview (course_id) 📖 Whole-course markdown map

Assignments

Tool Signature RW Purpose
get_assignment (assignment_id) 📖 Full document: times, state, description, attachments, submission, grade & feedback
submit_assignment (assignment_id, text?, item_id?, finalize?) ✍️ The single write: online text and/or draft files, optionally finalizing

Forums

Tool Signature RW Purpose
get_forum_discussions (forum_id) 📖 Thread list with opening-post previews
get_discussion_posts (discussion_id) 📖 Whole thread, converted to markdown, attachments included
create_forum_discussion (forum_id, subject, message) ✍️ Start a thread
reply_to_discussion (post_id, subject, message) ✍️ Reply (to the opening post = reply to the thread)

Files

Tool Signature RW Purpose
fetch_file (url) 📖 PDF/DOCX/PPTX/XLSX/ODT/RTF/EPUB/CSV → markdown (anydoc); 100k-char cap
download_file (url, save_to) 📖 Raw bytes → local path (metadata only)
upload_file (path, item_id?) ✍️ Local file → draft area

Grades, schedule, messaging

Tool Signature RW Purpose
get_grades (course_id?) 📖 Cross-course summary, or every grade item of one course
get_schedule (time_from?, time_to?, course_ids?) 📖 Merged timeline (default: next 30 days)
get_notifications (read?, limit?) 📖 Recent notifications
get_conversations (limit?) 📖 Conversations with member ids
send_message (user_id, text) ✍️ Direct message
get_quiz_attempts (quiz_id) 📖 Attempt records (read-only)
get_user / search_users (field, value) / (search?, limit?) 📖 Profile lookup / user search

Escape hatch

Tool Signature RW Purpose
call_ws_function (wsfunction, params?) 📖/✍️ Call any enabled WS function (PHP bracket notation handled)
list_functions (query?) 📖 Discover callable functions by keyword

🔐 Security notes

  • Use HTTPS — the token travels with every request.
  • The token is a credential: never commit it, never send it to hosts other
    than your Moodle site (the server only injects it into same-site
    pluginfile.php URLs, and redirects leaving the Moodle host are stopped).
  • The token never appears in tool output or errors: any token=… in a
    rendered error message is redacted to token=[REDACTED].
  • Transfers are bounded: inline fetches cap at 100 MB, streamed downloads at
    500 MB, uploads at 100 MB. Downloads stream to disk (never buffered whole
    in memory) and are limited by a read-timeout rather than a total timeout,
    so large files finish while stalled connections still die.
  • Write tools act as the token owner. The server's instructions tell the
    agent to confirm with you before submitting or sending.
  • Quiz-taking is intentionally not provided.

🧰 Development

cargo test                 # 39 tests: unit + wiremock integration + MCP protocol
cargo clippy --all-targets
cargo fmt -- --check
cargo build --release

ID flow between tools:

ID Produced by Consumed by
course_id list_courses / get_course_overview overview / grades / schedule / assignments
forum_id get_course_overview get_forum_discussions / create_forum_discussion
discussion_id get_forum_discussions get_discussion_posts
post_id get_discussion_posts reply_to_discussion
assignment_id get_course_overview / get_assignment get_assignment / submit_assignment
item_id (draft) upload_file submit_assignment
user_id conversations members / notifications / get_user send_message / get_user

📄 License

MIT — see LICENSE.

Reviews (0)

No results found