MCP server exposing a Moodle site as 88 typed AI tools: read courses, grades and deadlines; build courses, quizzes, questions and content.
Project description
Table of contents
- What it does
- How it works (two pieces)
- Features
- Quick start
- Tool reference
- Testing from the shell
- Safety & write mode
- Extending it
- Contributing
- License
What it does
Moodle is the learning platform used by thousands of universities and schools. MCP-Moodle connects it to any Model Context Protocol client — Claude Desktop, Gemini CLI, ChatGPT desktop, Cursor, VS Code, and more — so an AI can:
- Read: list courses, inspect course contents, see enrolled students, quizzes, grades, quiz results, activity completion, notifications, and competencies.
- Build: create courses, users, categories, groups, enrolments, and actual content:
pages, books, labels, URLs, forums, assignments, quizzes, and six quiz question types —
or a whole course from one JSON outline with
build_course. - Protect: back any course up to a real
.mbzfile on your disk and restore it later. - Safely: every write operation is off by default and only enabled with an explicit flag.
How it works (two pieces)
Moodle's built-in Web Services API can create courses and users, but it has no core function to create an activity (a Page, Book, Quiz…). So this project ships two parts that work together:
┌────────────────┐ MCP tools ┌──────────────────┐ REST + token ┌─────────────────┐
│ AI client │ ◄────────────────► │ moodle-mcp │ ◄────────────────► │ Moodle site │
│ (Claude, etc.) │ │ (Python server) │ │ + local plugin │
└────────────────┘ └──────────────────┘ └─────────────────┘
| Piece | Path | Role |
|---|---|---|
| Moodle plugin | local/mcpbridge/ |
Adds the activity-creation web service functions Moodle core is missing, using Moodle's official add_moduleinfo() helper, so it's upgrade-safe (no raw DB writes). |
| MCP server | moodle-mcp/ |
Wraps Moodle's read/write API, core and the plugin's new functions, as typed MCP tools for an AI client. |
Compatibility & authentication
Works with any Moodle 4.2 or newer: self-hosted or institutional. There is nothing site-specific in the code.
Authentication uses a web service token, not a password. The MCP server never sends a username or password, it sends a token (a long random string) that an admin generates in Moodle. The token is tied to one user account and one service (a fixed list of allowed functions), and it can only do what that user's capabilities permit.
Because the token is a separate auth channel, it works regardless of how your users log in: Manual, LDAP, SAML/SSO (Shibboleth, ADFS), Google/Microsoft OAuth, CAS, etc. all make no difference. A university Moodle behind SSO works exactly the same: an admin just creates a token.
What you need
| Requirement | Why |
|---|---|
| Admin access (yours or an admin's) | To install the plugin, enable web services, and create the token |
| Web services + REST enabled | The authentication mechanism |
| A user account for the token | The token inherits that user's capabilities |
Caveats (be honest with yourself here)
- You must be able to install a plugin: full control on self-hosted/institutional Moodle; a locked-down managed host may require the admin to do it.
- Some managed clouds (e.g. MoodleCloud free tier) block custom plugins and full web services, activity creation won't work there.
- Without the plugin, the MCP server's core tools (list/create courses, users, enrolment, grades) still work with a token; you only lose the plugin's activity-creation tools.
Features
- 88 tools: reads, a student-assistant layer (deadlines, dashboards, task analysis), core writes, activity creation, quiz results, completion tracking, backup/restore, and a one-call course builder
- Six quiz question types: multiple choice, true/false, short answer, essay, numerical (with tolerances), matching
- Read/write split: read tools always on; write tools gated behind
MOODLE_ALLOW_WRITE=true - Upgrade-safe plugin: uses Moodle's own
add_moduleinfo(), never touches module tables directly - Clean error handling: detects Moodle's JSON-error-on-HTTP-200 quirk and surfaces readable messages
- Token never logged: config from environment variables only
- One token for everything: read, core writes, and the plugin functions in a single service
Quick start
1. Install the plugin (local_mcpbridge)
Copy the local/mcpbridge/ folder into your Moodle so it lives at
{moodle}/local/mcpbridge/, then visit Site administration as an admin,
Moodle will detect it and prompt you to upgrade. Confirm, and you're done.
2. Enable web services & generate a token
In Moodle as an admin:
- Enable web services: Site administration → Advanced features → tick Enable web services.
- Enable REST: Server → Web services → Manage protocols → enable REST protocol.
- Build the service: Server → Web services → External services. Use the ready-made MCP Bridge Service the plugin created, then click Functions and add the core read/write functions you want (list in Tool reference).
- Authorise a user for the service and make sure they have the needed
capabilities (notably
moodle/course:manageactivitiesin the target courses). - Create a token: Server → Web services → Manage tokens → pick the user and the MCP Bridge Service. Copy it.
Verify before you wire up the AI: run the test script to confirm the plugin and token work end to end.
3. Run the MCP server
Easiest — from PyPI:
pip install moodle-mcp-bridge
MOODLE_URL=https://moodle.example.edu MOODLE_TOKEN=your_token moodle-mcp-bridge
Or from a checkout:
cd moodle-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env with your URL + token
python server.py
| Env var | Meaning |
|---|---|
MOODLE_URL |
Base URL, no trailing slash |
MOODLE_TOKEN |
The token from step 2 (never logged) |
MOODLE_ALLOW_WRITE |
true to enable write tools; anything else = read-only |
4. Connect Claude Desktop
Add to claude_desktop_config.json (pip install):
{
"mcpServers": {
"moodle": {
"command": "moodle-mcp-bridge",
"env": {
"MOODLE_URL": "https://moodle.example.edu",
"MOODLE_TOKEN": "your_token_here",
"MOODLE_ALLOW_WRITE": "true"
}
}
}
}
Running from a checkout instead? Use "command": "/absolute/path/to/moodle-mcp/.venv/bin/python" with "args": ["/absolute/path/to/moodle-mcp/server.py"].
Not just Claude: any MCP client works
MCP is an open protocol, so the same server works with Gemini CLI, ChatGPT
desktop (developer mode), Cursor, Windsurf, VS Code Copilot agent mode, Cline,
LM Studio, and anything else that speaks MCP. The config is nearly identical
everywhere — the moodle-mcp-bridge command plus the three env vars. Example
for Gemini CLI (~/.gemini/settings.json):
{
"mcpServers": {
"moodle": {
"command": "moodle-mcp-bridge",
"env": {
"MOODLE_URL": "https://moodle.example.edu",
"MOODLE_TOKEN": "your_token_here",
"MOODLE_ALLOW_WRITE": "true"
}
}
}
}
Tool-use quality depends on the model: with 88 tools, stronger models chain calls (create course → add section → add quiz) more reliably than small local ones.
Restart Claude Desktop and the Moodle tools appear.
Tool reference
88 tools, grouped by what they touch. Access shows whether a tool is
always available (read) or needs MOODLE_ALLOW_WRITE=true (write). Tools
backed by the plugin are marked plugin; everything else uses Moodle core
functions.
Courses & structure
| Tool | What it does | Access |
|---|---|---|
list_courses / search_courses |
List all courses / search by field | read |
get_course_content |
Everything inside a course (sections, activities, files) | read |
get_my_courses |
Courses the current user is enrolled in | read |
create_course / update_course |
Create or update a course | write |
create_category / delete_category |
Manage course categories | write |
create_section / delete_section plugin |
Manage course sections | write |
delete_courses |
Delete courses (irreversible) | write |
import_course |
Copy content from one course into another | write |
Course builder & backup
| Tool | What it does | Access |
|---|---|---|
build_course |
Build a whole course — sections, activities, quizzes with questions — from one JSON outline, with a per-item build log | write |
backup_course plugin |
Full course backup, saved as a real .mbz on your local disk |
write |
restore_course plugin |
Restore a .mbz file into a brand-new course |
write |
Content & activities
| Tool | What it does | Access |
|---|---|---|
create_page / update_page plugin |
HTML page activities | write |
create_book plugin |
Book with its first chapter | write |
create_label plugin |
Inline text/label on the course page | write |
create_url plugin |
External link resource | write |
create_forum / create_choice plugin |
Forum / poll activities | write |
create_assignment plugin |
Assignment with due date and grade | write |
update_activity / move_activity / delete_activity plugin |
Rename, hide, move or delete any activity | write |
upload_file / download_file |
Transfer files to/from Moodle | write |
Quizzes & questions
| Tool | What it does | Access |
|---|---|---|
list_quizzes |
Quizzes in given courses | read |
get_quiz_results |
A user's attempts + best grade for a quiz | read |
get_quiz_attempt_review |
Finished attempt, question by question | read |
get_quiz_report |
Whole-class analytics: grade distribution, average, pass rate, non-attempters, per-question facility index | read |
create_quiz plugin |
Quiz container with timing/grade settings | write |
add_quiz_question plugin |
Multiple-choice question | write |
add_truefalse_question plugin |
True/false question | write |
add_shortanswer_question plugin |
Short-answer question | write |
add_essay_question plugin |
Essay (manually graded) question | write |
add_numerical_question plugin |
Numerical question with tolerances | write |
add_matching_question plugin |
Matching-pairs question | write |
Users, enrolment & groups
| Tool | What it does | Access |
|---|---|---|
get_enrolled_users |
Who is enrolled in a course | read |
create_user / update_user / delete_users |
Manage user accounts | write |
enrol_user / enrol_users / unenrol_user |
Manual enrolment (single or bulk) | write |
assign_role / unassign_role |
Role assignments | write |
create_group / add_group_members |
Course groups | write |
create_cohort / add_cohort_members |
Site-wide cohorts | write |
Communication
| Tool | What it does | Access |
|---|---|---|
get_course_announcements |
News-forum announcements | read |
get_notifications |
Unread count + recent notifications | read |
send_message |
Direct message to a user | write |
start_forum_discussion / reply_forum_post |
Post in forums | write |
Assignments & grading
| Tool | What it does | Access |
|---|---|---|
get_assignments / get_assignment_status |
Assignments and submission/grading state | read |
get_upcoming_deadlines / get_overdue_assignments |
Deadline tracking | read |
analyze_assignment / extract_assignment_requirements |
Requirements + context for one assignment | read |
find_relevant_materials / decompose_task / create_implementation_plan |
Scaffolds for working on an assignment | read |
grade_assignment |
Save a grade with feedback text to the gradebook | write |
Grades, progress & completion
| Tool | What it does | Access |
|---|---|---|
get_grades / get_user_grades |
Grade overview or per-course detail | read |
get_course_progress / get_course_health |
Progress and at-a-glance course health | read |
get_activity_completion |
Which activities a user completed | read |
get_course_completion_status |
Whole-course completion criteria | read |
list_course_competencies / get_learning_plans |
Competency framework data | read |
mark_activity_complete |
Tick your own manual completion box | write |
override_activity_completion |
Teacher: set a student's completion state | write |
Planning & insights
| Tool | What it does | Access |
|---|---|---|
get_upcoming_events |
Calendar events | read |
get_recent_activity |
Updates across courses since a time | read |
get_study_load |
Assignment distribution by week | read |
get_actionable_tasks |
Prioritized to-do list by urgency | read |
semester_dashboard / daily_briefing / weekly_review |
Combined snapshots | read |
Search & assist
| Tool | What it does | Access |
|---|---|---|
server_info |
Health check: server version, write-mode, whether the plugin is installed (start here) | read |
verify_connection |
Site info + available functions | read |
search_course_materials |
Search across course materials | read |
ask_moodle |
Route a natural-language question to the right data | read |
Quizzes: the six
add_*_questiontools use Moodle's Question Bank API (multiple choice, true/false, short answer, essay, numerical, matching). That API is version-sensitive — tested on Moodle 5.0; test on your version first.
Missing function? A tool whose web service function isn't in the token's service returns a clean "access control exception" — add the function to MCP Bridge Service and retry. Installing plugin 1.6.0+ registers the complete list automatically.
Testing from the shell
Confirm the plugin + token work before wiring up the AI:
cd moodle-mcp
# Read-only checks (site info + course list):
MOODLE_URL=https://moodle.example.edu MOODLE_TOKEN=xxxx ./test_rest.sh
# Full write test, creates a page, label, url, book, quiz + question in COURSEID:
MOODLE_URL=https://moodle.example.edu MOODLE_TOKEN=xxxx COURSEID=2 ./test_rest.sh --write
A successful create returns e.g. {"cmid": 47, "instanceid": 12}. A Moodle error
(still HTTP 200) returns a JSON object with an exception field, which the server
surfaces as a clean message.
Safety & write mode
For a full hardening checklist (dedicated service account, scoped role, HTTPS, token expiry), see SECURITY.md.
- Writes hit live data. Every write tool creates or changes real records. There is no dry-run.
- Writes are gated. If
MOODLE_ALLOW_WRITEis not exactlytrue, the server registers read tools only: writes cannot be exposed by accident. - The token is the blast radius. Bind it to a dedicated service account with only the capabilities and courses you need. A site-admin token lets the AI do anything an admin can.
- The token is never logged: read from the environment, sent only to Moodle over HTTPS.
- Start read-only. Turn on write mode deliberately, on a test course, and confirm the shell test passes first.
Extending it
Adding a new capability (e.g. create_assignment, create_forum) is copy-paste-modify.
Each plugin function follows the same three-method shape, see
create_page.php as the template,
and CONTRIBUTING.md for the full recipe.
Contributing
Contributions are very welcome, this is built to be extended!
- Fork the repo (top-right Fork button).
- Create a branch:
git checkout -b feature/my-thing. - Make your change and test it (
php -l,py_compile, ideally against a real Moodle). - Push and open a Pull Request to
main.
See CONTRIBUTING.md for the full workflow, coding standards, and the pattern for adding a new function. Please also read our Code of Conduct. Found a bug or want a feature? Open an issue.
License
MIT, free to use, modify, and distribute.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moodle_mcp_bridge-1.7.0.tar.gz.
File metadata
- Download URL: moodle_mcp_bridge-1.7.0.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a5f897538214bfd9260fab42dfa82727a60e570774a4d56056452b4eb5717e3
|
|
| MD5 |
a6f54c5b54bdf787a16481ee398eeb6f
|
|
| BLAKE2b-256 |
f3502a4059f95e04d42cbfedd5fe37a29ba2a32cab32f379fac5f76e7c0ba5dc
|
Provenance
The following attestation bundles were made for moodle_mcp_bridge-1.7.0.tar.gz:
Publisher:
publish.yml on NmaaAlhawary/MCP-Moodle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moodle_mcp_bridge-1.7.0.tar.gz -
Subject digest:
1a5f897538214bfd9260fab42dfa82727a60e570774a4d56056452b4eb5717e3 - Sigstore transparency entry: 2236743926
- Sigstore integration time:
-
Permalink:
NmaaAlhawary/MCP-Moodle@41c2f4807e9406c141feb349cfb2a64b637f9fdd -
Branch / Tag:
refs/tags/v1.7.0 - Owner: https://github.com/NmaaAlhawary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@41c2f4807e9406c141feb349cfb2a64b637f9fdd -
Trigger Event:
release
-
Statement type:
File details
Details for the file moodle_mcp_bridge-1.7.0-py3-none-any.whl.
File metadata
- Download URL: moodle_mcp_bridge-1.7.0-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b3abb22772a1664b20b374b897c2cf2887aa514ff0e39d1cb71f782581ed751
|
|
| MD5 |
afcd0f7f8270c7d49019d9877ed208c1
|
|
| BLAKE2b-256 |
b470f31e30b6cc91c589d29487228085bc860331085f53e8ff7c7a86f28c5110
|
Provenance
The following attestation bundles were made for moodle_mcp_bridge-1.7.0-py3-none-any.whl:
Publisher:
publish.yml on NmaaAlhawary/MCP-Moodle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moodle_mcp_bridge-1.7.0-py3-none-any.whl -
Subject digest:
4b3abb22772a1664b20b374b897c2cf2887aa514ff0e39d1cb71f782581ed751 - Sigstore transparency entry: 2236744637
- Sigstore integration time:
-
Permalink:
NmaaAlhawary/MCP-Moodle@41c2f4807e9406c141feb349cfb2a64b637f9fdd -
Branch / Tag:
refs/tags/v1.7.0 - Owner: https://github.com/NmaaAlhawary
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@41c2f4807e9406c141feb349cfb2a64b637f9fdd -
Trigger Event:
release
-
Statement type: