isu-moodle-mcp
Provides read-only integration with Moodle's REST API, enabling retrieval of course lists, course overviews, materials, assignments, submissions, enrollment rosters, grade matrices, completion status, and file downloads from a Moodle instance.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@isu-moodle-mcpshow me the grade matrix for my course"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
isu-moodle-mcp
An MCP server that connects Moodle to Claude. Two layers:
API layer (18 tools, primary): Official Web Service REST API. No HTML scraping, no browser needed.
CDP layer (6 tools, gap-filling) + legacy system layer (4 tools): Only uses debug Chrome when the API really can't get the data. The most important one is
probe_course_access()— it's the only way to discover "a course you can no longer see."
For everyday use, the API layer alone is enough. The CDP layer is for questions like "are all the courses I taught in the list?"
Made for I-Shou University AIEA "Course Design AI Practice" workshop (2026-08-21), Unit 3. Just pull it down and adapt it to your own use — no GitHub account needed.
What this is
The flipclass-mcp demonstrated in class is an MCP server for STUST's FlipClass. That system has no API,
so it gets data two ways: scraping HTML (32 xpath locations) + debug Chrome (grade matrices, member rosters, and
other pages plain HTTP can't read). That's a demonstration of "no matter how closed the system is, with credentials you can fully reverse-engineer it."
This one is the other half of the same story: when the other side has an API, the same set of MCP tool contracts can have its entire backend swapped out.
flipclass-mcp | moodle-mcp | |
Data retrieval | Scrapes HTML + lxml xpath | Official REST API |
Authentication | Credentials + anticsrf token + cookie cache | One token, stateless |
Multiple-login kick-out | Yes (known issue) | No |
Member roster / grade matrix | Requires debug Chrome via CDP | Available in the regular API |
Student email | Derived by string-matching student IDs | Given directly in the roster |
Auth-related code | ~247 lines | ~15 lines |
debug Chrome | Required every time (no grade matrix without it) | Only needed when checking enrollment relationships |
Tool names and docstrings are deliberately kept identical on both sides, so you can compare them directly and see what the same requirement looks like with and without an API.
Related MCP server: Moodle MCP Server
Quick start
1. Get the code
git clone https://github.com/scatjay/isu-moodle-mcp.gitNo git? You can also click Code → Download ZIP on the GitHub page.
2. Install dependencies
pip install -r requirements.txtOnly two: requests and mcp.
3. Get a token
python get_token.py https://moodle.你的學校.edu.twIt will ask for your Moodle username and password, and on success writes the token into .env.
Run this step in your own terminal, not inside an AI conversation. Conversation transcripts may be saved or backed up; once your password and token appear in one, they're as good as leaked.
Why a token instead of credentials? A token can be revoked, is bound only to your own permissions, and won't lose everything at once the way a password leak does. Moodle tokens expire after 12 weeks by default — if a tool suddenly breaks mid-semester with an invalidtoken error, just rerun this script.
4. Connect to Claude
In Claude Desktop's config file (claude_desktop_config.json), add:
{
"mcpServers": {
"moodle": {
"command": "python",
"args": ["C:/你的路徑/isu-moodle-mcp/server.py"],
"env": {
"MOODLE_URL": "https://moodle.你的學校.edu.tw",
"MOODLE_TOKEN": "貼上 .env 裡那一串",
"MOODLE_LEGACY_URL": "https://舊站網址(沒有舊站就整行刪掉)"
}
}
}
}5. Run a health check first
After connecting, the first thing to ask Claude to run is diagnose(). It will tell you whether your token is valid,
which functions you can actually call, and what's missing. When something won't connect, this is the first thing to run.
Available tools
Tool | What it does |
| Connection health check. Run this first if you can't connect |
| Courses in progress |
| All courses still visible (note the known limitation below) |
| Find your courses by keyword |
| How many sections, materials, and assignments a course has |
| Material list (with download URLs) |
| Assignment list |
| Whole-class submission status |
| Enrollment roster (name / email / role) |
| Grade matrix: each student × each graded item |
| Activity completion status |
| Download a material file |
| Call any Moodle function directly (for exploration) |
| Submission report: submission time, late submissions, resubmission count |
| One student's itemized grades |
| Look up a student's email |
| Grab a whole course's materials + assignments + roster + grades in one package |
| Batch-grab all visible courses |
CDP layer (run python start_debug_chrome_moodle.py first and log in in that window)
Tool | What it does |
| Whether debug Chrome is reachable and logged in |
| Can I still get into this course — the question the API can't answer |
| Status, method, enrollment time, start/end dates for each enrollment |
| Scan to find courses that "exist but you can no longer see" |
| Which service is bound to which functions, and who can issue their own token |
| A role's capability matrix (300+ entries, not available via the API) |
Legacy system layer (for when the school has switched platforms)
Add a line to .env: MOODLE_LEGACY_URL=https://old-site-url.
Tool | What it does |
| Is the old site alive, and does it go through API or CDP. Run this before digging for data |
| Courses visible on the old site's dashboard |
| The old-site version of "can I still get into this course" |
| A course's sections and material links on the old site |
This version deliberately contains no write tools (e.g., mod_assign_save_grade for changing grades).
A mistake in a read-only tool just means wrong data; a mistake in a write tool actually changes student grades.
Add them yourself if you really need them, but practice on a test site first.
Known limitations (please read this section in full)
🔴 Old courses silently disappear — but under narrower conditions than you'd think
core_enrol_get_users_courses only returns courses where you still have an active enrollment relationship.
Tested on 2026-08-20 with two local Moodle 4.1.18 instances, item by item:
What the school did | Is the course still in your list? |
Course set to hidden ( | Still visible |
Course end date has passed | Still visible |
Teacher's enrollment set to "disabled" | Gone. And no error is raised at all |
This table overturns a very common claim (including in an earlier version of this README): "hiding or archiving old courses makes them disappear." Not true in testing. The only thing that actually makes a course disappear is the last row. It's written here because a claim that's been disproven by testing is worse than not writing it at all — you'd go ask the admin for the wrong thing based on it.
The course, students, and assignments are all still in the database — you just can't see them. And the API won't tell you "there's a course you can no longer see"; it just doesn't mention it.
So before doing long-period analysis, run find_hidden_courses() or
probe_course_access(course_id) to probe one by one — don't just trust the list from list_history_courses().
That tool always returns a caveat field to remind you; please don't ignore it.
Moodle errors come back as HTTP 200
When Moodle returns an error, the HTTP status code is still 200; the error is hidden in the exception field of the body.
raise_for_status() won't catch it at all. This server already handles it, but keep it in mind if you write your own code against Moodle.
accessexception is hard to diagnose
The official docs list seven or eight possible causes, and unless the admin has set debug to NORMAL or higher,
the error message won't tell you which one it is. This server translates it into plain language and gives the three most likely causes,
but to actually pin down which one it is, you still need to run diagnose() to see which functions your token actually includes.
You can only see your own courses
This is a built-in guarantee of Moodle, not a limitation of this tool. The token fully inherits your own permissions, and every call does a context-level permission check. This is both a security guarantee and a limitation.
Array parameters can't use JSON
Moodle REST parses with PHP's $_POST, so arrays must be written as courseids[0]=5&courseids[1]=7.
Passing a JSON string gets treated as a single string and raises invalidparameter. This server already flattens them automatically.
File download uses a different parameter name
The REST endpoint uses wstoken, but webservice/pluginfile.php uses token.
This is the easiest thing to miss when porting. Also, the service's downloadfiles must be enabled.
If get_token.py fails
Error | Meaning | What to do |
| Wrong username or password | Your Moodle username isn't necessarily your email |
| The site doesn't have the mobile service enabled | Ask the admin to enable |
| Your account doesn't have permission to create its own token | The school changed the default permissions; ask the admin to issue a token |
| The site is under maintenance | Try again later |
Moodle's default grants moodle/webservice:createmobiletoken to all logged-in users,
so teachers usually don't need an admin to get a new token. But the school can change this default —
if they have, you'll only find out when you actually try to get one; you can't detect it from the outside.
Development notes
This was ported from flipclass-mcp. The port cut the most painful half and kept the most valuable half:
Cut (~247 lines):
_login, anticsrf handling, cookie caching,checkMultiLoginmulti-login handling, 32 lxml xpath parsers, CDP (debug Chrome) connectionKept: the FastMCP skeleton, and every
@mcp.tool()signature and docstring ——that's the real asset, because that's the contract the LLM sees
The reason for this is that none of the existing Moodle Python packages are usable: moodlepy hasn't been updated in nearly two years
and pins its dependencies to attrs<23 (a 2022 version); moodle_api.py hasn't been updated in three years and isn't on PyPI;
python-moodle is still maintained but is basically HTML scraping, not a REST client.
Moodle REST is simple enough to write in a dozen lines; pulling in an abandoned package just means taking on more technical debt.
License
MIT. Fork it into your own school's version, no need to ask.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Moodle learning management systems through the Moodle REST API. Supports course management, user enrollment, assignments, forums, quizzes, and file operations through natural language.14MIT
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Moodle learning management systems through the REST API. Supports course management, user enrollment, assignment handling, and forum operations through natural language.14MIT
- AlicenseNot gradedqualityCmaintenanceProvides Claude with full access to Moodle learning management systems, enabling interaction with courses, files, assignments, grades, and calendar events. It also supports building Obsidian study vaults from course materials through automated knowledge graph creation.1416MIT
- FlicenseAqualityCmaintenanceEnables read-only querying of Moodle as a student, including courses, assignments, grades, forums, and files, using a personal web services token.11
Related MCP Connectors
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.
Read-only tools over the Safer Agentic AI framework: 238 patterns + 14 heuristics.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/scatjay/isu-moodle-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server