Skip to main content
Glama

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.git

No git? You can also click Code → Download ZIP on the GitHub page.

2. Install dependencies

pip install -r requirements.txt

Only two: requests and mcp.

3. Get a token

python get_token.py https://moodle.你的學校.edu.tw

It 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

diagnose()

Connection health check. Run this first if you can't connect

list_current_courses()

Courses in progress

list_history_courses()

All courses still visible (note the known limitation below)

search_courses(keyword)

Find your courses by keyword

get_course_overview(course_id)

How many sections, materials, and assignments a course has

list_materials(course_id)

Material list (with download URLs)

list_homework(course_id)

Assignment list

list_submissions(assignment_id)

Whole-class submission status

read_members(course_id)

Enrollment roster (name / email / role)

read_score_matrix(course_id)

Grade matrix: each student × each graded item

get_completion_status(course_id)

Activity completion status

download_file(fileurl, dest_path)

Download a material file

raw_call(wsfunction, params_json)

Call any Moodle function directly (for exploration)

get_submission_report(assignment_id)

Submission report: submission time, late submissions, resubmission count

get_student_grade_record(course_id, uid)

One student's itemized grades

get_student_email(course_id, uid)

Look up a student's email

fetch_course_bundle(course_id, dest)

Grab a whole course's materials + assignments + roster + grades in one package

fetch_all_courses_bundle(dest)

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

cdp_status()

Whether debug Chrome is reachable and logged in

probe_course_access(course_id)

Can I still get into this course — the question the API can't answer

enrolment_details(course_id)

Status, method, enrollment time, start/end dates for each enrollment

find_hidden_courses()

Scan to find courses that "exist but you can no longer see"

webservice_overview()

Which service is bound to which functions, and who can issue their own token

role_capabilities(role_id)

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

legacy_status()

Is the old site alive, and does it go through API or CDP. Run this before digging for data

legacy_list_courses()

Courses visible on the old site's dashboard

legacy_probe_course(course_id)

The old-site version of "can I still get into this course"

legacy_course_contents(course_id)

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 (visible=0)

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

invalidlogin

Wrong username or password

Your Moodle username isn't necessarily your email

servicenotavailable

The site doesn't have the mobile service enabled

Ask the admin to enable enablemobilewebservice

cannotcreatetoken

Your account doesn't have permission to create its own token

The school changed the default permissions; ask the admin to issue a token

sitemaintenance

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, checkMultiLogin multi-login handling, 32 lxml xpath parsers, CDP (debug Chrome) connection

  • Kept: 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.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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.
    14
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with Moodle learning management systems through the REST API. Supports course management, user enrollment, assignment handling, and forum operations through natural language.
    14
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides 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.
    14
    16
    MIT

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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