isu-moodle-mcp
Enables Claude to interact with Moodle via the official REST API for read-only tasks like listing courses, materials, assignments, submissions, and grades. Uses token-based authentication and emphasizes safe, no-write operations.
README
isu-moodle-mcp
把 Moodle 接進 Claude 的 MCP server。分兩層:
- API 層(18 支,主力):官方 Web Service REST API。不爬 HTML、不用開瀏覽器。
- CDP 層(6 支,補洞)+舊系統層(4 支):只在 API 真的拿不到的時候才用 debug Chrome。
最重要的一支是
probe_course_access()—— 它是唯一能發現「有一門課你已經看不到了」的辦法。
平常只用 API 層就夠。CDP 層是給「我教過的課都在清單裡嗎」這種問題準備的。
給義守大學 AIEA「課程設計 AI 實務」工作坊(2026-08-21)單元 3 使用。 直接 pull 下來改成你自己的,不需要 GitHub 帳號。
這是什麼
課堂上示範的 flipclass-mcp 是南臺科大 FlipClass 的 MCP server。那套系統沒有 API,
所以它靠兩件事拿資料:爬 HTML(32 處 xpath)+ debug Chrome(成績矩陣、成員名單這些
純 HTTP 讀不到的頁面)。那是「系統再封閉,有帳密就能完整逆向」的示範。
這一套是同一件事的另一半:當對方有 API 的時候,同一組 MCP tool 契約可以整個抽換後端。
| flipclass-mcp | moodle-mcp | |
|---|---|---|
| 取資料 | 爬 HTML + lxml xpath | 官方 REST API |
| 認證 | 帳密 + anticsrf token + cookie 快取 | 一組 token,無狀態 |
| 多重登入互踢 | 會(已知問題) | 不會 |
| 成員名單 / 成績矩陣 | 要開 debug Chrome 走 CDP | 一般 API 就有 |
| 學生 email | 用學號拼字串推導 | 名單直接給 |
| 認證相關程式碼 | 約 247 行 | 約 15 行 |
| debug Chrome | 每次都要開(沒它就沒成績矩陣) | 只在查選課關係時才要 |
tool 名稱與 docstring 兩邊刻意保持一致,這樣你可以直接對照,看同一個需求在 「有 API」和「沒 API」兩種情況下分別長什麼樣。
快速開始
1. 拿到程式
git clone https://github.com/scatjay/isu-moodle-mcp.git
沒有 git 也可以在 GitHub 頁面按 Code → Download ZIP。
2. 裝相依套件
pip install -r requirements.txt
只有兩個:requests 和 mcp。
3. 換一組 token
python get_token.py https://moodle.你的學校.edu.tw
它會問你的 Moodle 帳號密碼,成功就把 token 寫進 .env。
請在你自己的終端機跑這一步,不要在 AI 對話裡跑。 對話逐字稿可能被保存或備份,密碼和 token 一旦出現在裡面就等於外洩。
為什麼是 token 不是帳密? token 可以撤銷、只綁你自己的權限、而且不會像密碼那樣
一洩就全盤皆輸。Moodle 的 token 預設 12 週到期——學期中工具突然壞掉、說
invalidtoken,回來重跑這支就好。
4. 接到 Claude
在 Claude Desktop 的設定檔(claude_desktop_config.json)加:
{
"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. 先跑體檢
接好之後,第一句先叫 Claude 跑 diagnose()。它會告訴你 token 有沒有效、
你實際能呼叫哪些函式、缺了什麼。接不上的時候第一個該跑的就是這支。
有哪些工具
| Tool | 做什麼 |
|---|---|
diagnose() |
連線體檢。接不上先跑這個 |
list_current_courses() |
進行中的課 |
list_history_courses() |
所有還看得到的課(注意下面的已知限制) |
search_courses(keyword) |
用關鍵字找自己的課 |
get_course_overview(course_id) |
課程有幾個單元、幾份教材、幾份作業 |
list_materials(course_id) |
教材清單(含下載網址) |
list_homework(course_id) |
作業清單 |
list_submissions(assignment_id) |
全班繳交狀況 |
read_members(course_id) |
修課名單(姓名 / email / 角色) |
read_score_matrix(course_id) |
成績矩陣:每位學生 × 每個評分項目 |
get_completion_status(course_id) |
活動完成度 |
download_file(fileurl, dest_path) |
下載教材檔案 |
raw_call(wsfunction, params_json) |
直接呼叫任意 Moodle 函式(探索用) |
get_submission_report(assignment_id) |
繳交報表:含繳交時間、遲交、重繳次數 |
get_student_grade_record(course_id, uid) |
單一學生的逐項成績 |
get_student_email(course_id, uid) |
查某位學生的 email |
fetch_course_bundle(course_id, dest) |
一門課的教材+作業+名單+成績,整包抓下來 |
fetch_all_courses_bundle(dest) |
所有看得到的課,整批抓 |
CDP 層(要先跑 python start_debug_chrome_moodle.py 並在那個視窗登入)
| Tool | 做什麼 |
|---|---|
cdp_status() |
debug Chrome 通不通、登入了沒 |
probe_course_access(course_id) |
這門課我還進不進得去——API 回答不了的那題 |
enrolment_details(course_id) |
每一筆選課的狀態、方法、加選時間、起訖日 |
find_hidden_courses() |
掃描找出「存在、但你已經看不到」的課 |
webservice_overview() |
哪個服務綁了哪些函式、誰能自己領 token |
role_capabilities(role_id) |
角色的 capability 矩陣(300+ 條,API 拿不到) |
舊系統層(學校換過平台時用)
需要在 .env 加一行 MOODLE_LEGACY_URL=https://舊站網址。
| Tool | 做什麼 |
|---|---|
legacy_status() |
舊站活著嗎、走 API 還是走 CDP。挖資料前先跑這支 |
legacy_list_courses() |
舊站儀表板上看得到的課 |
legacy_probe_course(course_id) |
舊站版的「這門課我還進不進得去」 |
legacy_course_contents(course_id) |
舊站某門課的單元與教材連結 |
這一版故意不含任何寫入工具(例如改成績的 mod_assign_save_grade)。
唯讀的東西弄錯了頂多是資料不對;寫入弄錯了是真的改到學生成績。
真的需要再自己加,但請先在測試站練過。
已知限制(請務必讀完這一節)
🔴 舊課會安靜地消失——但條件比你想的窄
core_enrol_get_users_courses 只回「你目前還有選課關係」的課。
2026-08-20 用兩台本機 Moodle 4.1.18 實測,逐項驗過:
| 學校做了什麼 | 課還在你的清單裡嗎 |
|---|---|
課程設成隱藏(visible=0) |
照樣看得到 |
| 課程結束日已經過了 | 照樣看得到 |
| 老師的選課關係被設為「已停用」 | 消失。而且完全不會報錯 |
這張表推翻了一個很常見的說法(也包括本 README 的前一版): 「隱藏或封存舊課會讓它消失」。實測不成立。 真正會讓課消失的只有最後那一列。 寫在這裡是因為:一個被實測推翻的說法留在文件裡,比沒寫還糟—— 你會照著它去跟管理員要錯的東西。
課程、學生、作業都還在資料庫裡,只是你看不到。而 API 不會告訴你 「有一門課你看不到了」,它只是不提。
所以要做長時段分析之前,先跑 find_hidden_courses() 或
probe_course_access(course_id) 逐一探測,不要只信 list_history_courses() 的清單。
那支一定會回一個 caveat 欄位提醒你,請不要忽略它。
Moodle 的錯誤是 HTTP 200
Moodle 回錯誤時 HTTP 狀態碼仍然是 200,錯誤藏在 body 的 exception 欄位裡。
raise_for_status() 完全抓不到。本 server 已經處理,但你自己寫程式打 Moodle 時要記得。
accessexception 很難查
官方列出的成因有七八種,而除非管理員把 debug 開到 NORMAL 以上,
錯誤訊息不會告訴你是哪一種。本 server 會把它翻成白話並給出最可能的三個原因,
但真正要確定是哪一個,還是得跑 diagnose() 看你的 token 到底含哪些函式。
你只看得到自己的課
這是 Moodle 內建的保證,不是本工具的限制。token 完全繼承你本人的權限, 每次呼叫都會做 context 層級的權限檢查。這同時是安全保證也是限制。
陣列參數不能用 JSON
Moodle REST 用 PHP 的 $_POST 解析,陣列必須寫成 courseids[0]=5&courseids[1]=7。
丟 JSON 字串會被當成單一字串而報 invalidparameter。本 server 已自動攤平。
檔案下載的參數名不一樣
REST 端點用 wstoken,但 webservice/pluginfile.php 用的是 token。
移植時最容易漏掉這一點。而且服務的 downloadfiles 必須是開的。
如果 get_token.py 失敗
| 錯誤 | 意思 | 怎麼辦 |
|---|---|---|
invalidlogin |
帳密不對 | Moodle 帳號未必等於你的 email |
servicenotavailable |
站台沒開行動裝置服務 | 請管理員開 enablemobilewebservice |
cannotcreatetoken |
你的帳號沒有自建 token 的權限 | 學校改過預設權限,需請管理員發 token |
sitemaintenance |
站台維護中 | 等一下再試 |
Moodle 原廠預設把 moodle/webservice:createmobiletoken 給所有已登入使用者,
所以老師通常不需要管理員就能自己換 token。但學校可以改這個預設值——
如果改過,只會在你實際去換的時候才發現,從外面探測不出來。
開發筆記
這支是從 flipclass-mcp 移植過來的。移植時砍掉的是最痛的那一半、留下的是最有價值的那一半:
- 砍掉(約 247 行):
_login、anticsrf 處理、cookie 快取、checkMultiLogin多重登入處理、32 處 lxml xpath 解析、CDP(debug Chrome)連線 - 保留:FastMCP 骨架、每個
@mcp.tool()的簽名與 docstring ——這才是真正的資產,因為那是 LLM 看到的契約
會這樣做,是因為現成的 Moodle Python 套件沒有一個能用:moodlepy 停更近兩年
且把相依鎖在 attrs<23(2022 年的版本);moodle_api.py 停更三年且不在 PyPI;
python-moodle 還在維護但根本是爬 HTML,不是 REST client。
Moodle REST 簡單到十幾行就寫完,引入停更套件只是多背一份技術債。
授權
MIT。拿去改成你自己學校的版本,不用問。
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.