NCCU Moodle MCP
The server is an MCP endpoint that logs into NCCU Moodle with per-request credentials and exposes the student's courses, assignments, grades, contents, announcements, and notifications as tools.
Courses: list enrolled courses by semester, search by keyword, and look up your role in a course.
Assignments: list assignments with due/open dates and submission status, filter by semester/course/date range, and see upcoming deadlines.
Submissions: open an assignment and submit an online-text answer (save only, not finalize).
Course content: fetch everything posted in a course by week/section, and open any item (files, pages, forums, quizzes, assignments) by cmid.
Grades: get your grade items for a course.
Announcements: list announcement tiles and read full announcement threads.
Notifications: get your Moodle notification bell items with unread count.
Provides access to the NCCU Moodle platform, allowing students to list and search courses, view assignments and upcoming deadlines, retrieve grades, course contents, modules, announcements, and notifications using their NCCU single sign-on credentials.
Click on "Deploy 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., "@NCCU Moodle MCP What assignments are due this week?"
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.
NCCU Moodle MCP
An MCP server that accesses NCCU Moodle (moodle.nccu.edu.tw) on behalf of a
student. It logs in through the NCCU single-sign-on portal (i.nccu.edu.tw) and
exposes Moodle data as MCP tools.
It runs as a hosted HTTP server: you (the operator) run one instance, and everyone else just adds its URL to their MCP client config — no install on their side.
Multi-tenant & stateless — each call authenticates with the credentials passed to it, uses that session for the one call, and discards it. Nothing is cached to disk or shared between calls, so concurrent users never interfere.
Tools
Tool | Description | WS function |
| Enrolled courses by semester, each with the user's role (student/teacher/…). No |
|
| Filter enrolled courses by a keyword (the app's "filter my courses" box) — case-insensitive substring of the name (code / 中文 / English). Searches every term by default; |
|
| The user's role in one course ( |
|
| Assignments with due dates, submission status, and your role per course. Only your student courses by default ( |
|
| Upcoming due dates/quiz closings within |
|
| Your grade items for one course ( |
|
| Everything the teacher posted in a course ( |
|
| Open one posted item by |
|
| Submit (繳交) an assignment's online-text answer by |
|
| Announcement tiles (headers only) for one course or all current-semester courses; paginated. |
|
| Read one announcement's thread (posts + replies) by |
|
| The notification bell (due reminders, grading, forum posts); reports unread count. |
|
Course data comes from Moodle's mobile Web Services API, not HTML scraping:
after SSO login, the server obtains a Web Services token the way the Moodle app
does (admin/tool/mobile/launch.php → moodlemobile://token=…) and calls the
REST API. Semester is the NCCU term code encoded in each course's short name
(e.g. 1151).
Nothing about the Moodle instance is hardcoded. On startup the server fetches
Moodle's login page (via the stable entry moodle.nccu.edu.tw) and discovers both
the current backend host (e.g. moodle45.nccu.edu.tw) and the NCCU SSO login URL
(which encodes the MoodleSSOxx.aspx path) — so if the school moves to a different
instance number, it keeps working. The discovered values are cached in memory for
the process (site-wide config, not per-user state).
Part A — For users (setup guide)
You do not clone or install anything. You just add one block to your MCP client's config: the central server's address plus your own NCCU credentials as headers. The server reads those headers on each request, so every user connects as themselves.
Before you start, get these three things:
The server address from the operator — e.g.
http://140.119.x.x:3033/mcporhttps://moodle-mcp.example.com/mcp.Your NCCU student ID (e.g.
112703016).Your NCCU portal password (the one you use at https://i.nccu.edu.tw).
In every snippet below, replace SERVER_ADDRESS, YOUR_STUDENT_ID, and
YOUR_PASSWORD.
Claude Code
Option 1 — CLI (easiest). Run this once; the flag -s user makes it
available in every project:
claude mcp add -s user --transport http nccu-moodle SERVER_ADDRESS \
-H "X-Moodle-Username: YOUR_STUDENT_ID" \
-H "X-Moodle-Password: YOUR_PASSWORD"Option 2 — edit the config file directly. Create/edit .mcp.json in your
project folder (or add this mcpServers block to your existing config):
{
"mcpServers": {
"nccu-moodle": {
"type": "http",
"url": "SERVER_ADDRESS",
"headers": {
"X-Moodle-Username": "YOUR_STUDENT_ID",
"X-Moodle-Password": "YOUR_PASSWORD"
}
}
}
}Verify:
claude mcp list # nccu-moodle should be listedThen start Claude Code and run /mcp — you should see nccu-moodle connected
with the list_courses tool.
opencode
opencode is configured by a JSON file (there's no add command for MCP). Edit one of:
Global (recommended, works everywhere):
~/.config/opencode/opencode.jsonPer project:
opencode.jsonin the project root
Add the mcp block (merge into the file if it already exists):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"nccu-moodle": {
"type": "remote",
"url": "SERVER_ADDRESS",
"enabled": true,
"headers": {
"X-Moodle-Username": "YOUR_STUDENT_ID",
"X-Moodle-Password": "YOUR_PASSWORD"
}
}
}
}Verify: restart opencode; the list_courses tool from nccu-moodle should
be available to the assistant.
Try it
Ask the assistant, for example:
List my Moodle courses this semester. · List all my Moodle courses. What's due this week — and have I submitted it? · What was due last week? Which assignments haven't I submitted yet? · Any assignments in 1142? What are the latest announcements? · Open that announcement and read it. What are my grades in course 18284? · Do I have any notifications?
The assistant picks the right tool and reads your credentials from the headers you set — so you never type your password into the chat, and the assistant never sees or handles your password (the tool has no username/password parameters).
Notes & safety
⚠️ Lockout: NCCU suspends an account for 15 minutes after 5 failed login attempts. Make sure the password in your config is correct.
🔒 Your password is stored in a local config file and sent to the server on each call. Keep that file private, and don't commit a personal
.mcp.json/opencode.jsoncontaining your password to a shared repository. The operator should serve the endpoint over HTTPS (see Part B).
Troubleshooting:
Symptom | Fix |
Tool errors with "Missing credentials…" | The |
"Moodle login failed…" | Wrong student ID or password. Fix it in the config (mind the 5-attempt lockout). |
| Config not loaded — re-check the file location, valid JSON, and restart the client. For Claude Code, confirm with |
Connection/timeout errors | Server address wrong or server not reachable. Confirm |
Related MCP server: moodle-cli
Part B — For the operator (run the server)
This project uses uv to manage Python and dependencies.
1. Install
cd /path/to/moodle_mcp
uv sync # creates .venv and installs from uv.lock(Install uv first if needed: curl -LsSf https://astral.sh/uv/install.sh | sh.)
2. Run as an HTTP server
MCP_HOST=0.0.0.0 uv run nccu-moodle-mcp httpEndpoint: http://<host>:3033/mcp (Streamable HTTP, stateless, JSON responses).
On startup it discovers + caches the Moodle backend and SSO URL, then serves.
Environment variables:
Var | Default | Purpose |
|
| Bind address. Set |
|
| Port. |
|
| Hostnames allowed in the |
| (auto-discovered) | The NCCU SSO login URL ( |
The host check defaults to allowing any host. If you set
MCP_ALLOWED_HOSTSto specific names, a request whoseHostisn't listed is rejected with HTTP 421 — the value must match theHostheader exactly, including the port when one is present.
3. Put it behind HTTPS (recommended)
Terminate TLS with a reverse proxy and forward to the app on localhost. Example Caddy config:
moodle-mcp.example.com {
reverse_proxy 127.0.0.1:3033
}Run the app bound to localhost, optionally locking the host to your domain:
MCP_ALLOWED_HOSTS="moodle-mcp.example.com" uv run nccu-moodle-mcp http(You can also leave MCP_ALLOWED_HOSTS at its default * when the app port is
only reachable through the proxy.)
4. Keep it running
Docker Compose (recommended). The repo ships a Dockerfile (multi-stage,
uv-based) and docker-compose.yml (service nccucourse, bind 0.0.0.0:3033,
MCP_ALLOWED_HOSTS=*, restart policy, healthcheck):
docker compose up -d --build # build and start
docker compose logs -f # watch
docker compose down # stopOverride settings via a local .env or the shell, e.g. to lock the host down:
MCP_ALLOWED_HOSTS=moodle-mcp.example.com docker compose up -dsystemd (bare-metal alternative):
[Unit]
Description=NCCU Moodle MCP
After=network.target
[Service]
WorkingDirectory=/path/to/moodle_mcp
Environment=MCP_HOST=0.0.0.0
Environment=MCP_PORT=3033
ExecStart=/usr/local/bin/uv run nccu-moodle-mcp http
Restart=always
[Install]
WantedBy=multi-user.targetSmoke test
curl -s https://moodle-mcp.example.com/mcp -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Layout
src/nccu_moodle_mcp/ the package
├── __init__.py
├── __main__.py `python -m nccu_moodle_mcp [http]`
├── app.py the MCPServer instance, credential handling, tool runner, param types
├── server.py entry point: imports tools (registers them) and runs the transport
├── moodle_client.py MoodleClient: SSO login, site discovery, ws() core
├── helpers.py shared helpers (time / HTML / term-code / semester filter)
└── tools/ one module per tool — logic + its own @mcp.tool (+ tool-specific helpers)
├── courses.py list_courses, search_courses, get_course_role
├── assignments.py list_assignments
├── deadlines.py upcoming_deadlines
├── grades.py get_grades
├── contents.py get_course_contents
├── module.py get_module (open one item, dispatch by type)
├── submissions.py submit_assignment (save online-text; no finalize)
├── announcements.py list_announcements, get_announcement
└── notifications.py get_notifications
scripts/
└── http_client.py minimal plain-requests client for testing
pyproject.toml metadata, deps, `nccu-moodle-mcp` entry point
uv.lock pinned lockfile (committed)
Dockerfile multi-stage uv build; `prod` target runs the server
docker-compose.yml service `nccucourse` — build + run on port 3033
.dockerignore keeps .venv, secrets, caches out of the build contextAvailable Tools
11 toolsget_announcementRead an announcementA
Open ONE announcement and read its thread — the original post plus any replies — given a discussion_id from list_announcements.
Posts are oldest-first; limit/offset paginate long threads.
Returns {discussion_id, total, posts:[{post_id, parent_id, subject, author, posted, message}]}. posted is Taipei time 'YYYY-MM-DD HH:MM'.
Credentials come from the MCP settings headers, not from you.
Inputs: discussion_id (required), limit, offset.
get_course_contentsGet course contentsA
Get everything the teacher posted in one course (by Moodle course id, from list_courses), grouped by section — NCCU names sections by week, so this is the week-by-week list of materials, links, forums and assignments.
The summary is the teacher's text for that section/week (readings, plan, notes) — many NCCU weeks have only a summary and no attached items. By default a section is shown if it has items OR a summary; only truly blank sections are dropped. Set include_empty true for the full week skeleton including blank weeks.
Each section: {section_id, section, summary, modules:[{id, instance, name, type, url}]}. type is the Moodle module (assign, resource, url, forum, quiz, page, folder, label, …); id is the course-module id (cmid) and instance the activity id — use them to open an item with other tools (e.g. an assign instance is the course_id-independent assignment id, a forum instance feeds get_announcement's forum).
Credentials come from the MCP settings headers, not from you.
Inputs: course_id (required), include_empty.
get_course_roleGet my role in a courseA
Get the user's role in one course (by Moodle course id, from list_courses): 'student', 'teacher' (often the TA/助教), 'editingteacher' (instructor), 'teachingassistant', 'manager', etc.
Returns {course_id, role, roles}: role is the primary role shortname, roles all of them (a user can hold more than one).
Credentials come from the MCP settings headers, not from you.
Inputs: course_id (required).
get_gradesGet course gradesA
Get the student's own grade items for one course (by Moodle course id, from list_courses).
Each item: {item, grade, percentage, range, feedback, type}. A '-' grade means not yet graded.
Credentials come from the MCP settings headers, not from you.
Inputs: course_id (required).
get_moduleOpen a course itemA
Open ONE item a teacher posted and return its content, given its cmid (course-module id — the id in a /mod//view.php?id= link, or from get_course_contents). course_id is OPTIONAL — omit it and it's resolved from the cmid, so a bare cmid/link is enough. It detects the item type and returns:
resource/folder -> files:[{filename, mimetype, size, url}] (open the url/view_url in a browser to download; no direct download here yet)
url -> external_url
page -> html (plain text)
label -> text
forum -> forum_id + intro + discussions[] (read one with get_announcement)
assign -> description (instructions), attachments, due/opens/cutoff, grade_max, status, submission{submitted_at,files,text}, feedback{grade,grade_display,graded_at,comment,files}
quiz -> metadata + attempts · otherwise -> description
Always includes {course_id, cmid, instance, type, name, view_url}.
Credentials come from the MCP settings headers, not from you.
Inputs: cmid (required), course_id.
get_notificationsGet notificationsA
Get the student's Moodle notifications (the notification bell): assignment due reminders, grading, forum posts, etc.
Each notification: {subject, message, posted, read, url}, newest first. limit caps the count. The result also reports how many are unread.
Credentials come from the MCP settings headers, not from you.
Inputs: limit.
list_announcementsList announcementsA
List announcement TILES (headers only, no message body) from a course's "Announcements" forum — like scanning the forum page. To read one, take its discussion_id and call get_announcement.
If course_id is given, lists that course's announcements; otherwise aggregates across the current (latest) semester's courses. limit/offset paginate the newest-first list.
Each tile: {discussion_id, course_id, course, subject, author, posted, replies, pinned, url}. posted is Taipei time 'YYYY-MM-DD HH:MM'.
Credentials come from the MCP settings headers, not from you.
Inputs: limit, offset, course_id.
list_assignmentsList assignmentsA
List assignments (with due dates and submission status) for the student's courses.
Scope, in priority order:
course_ids: if given, list assignments for exactly those Moodle course ids (from list_courses);semis ignored.sem: otherwise filter all enrolled courses by NCCU term code. Default (neither given) = latest semester; "1142" = that term; "all" = every course.
Filter by date (ISO dates in Taipei time, e.g. "2026-09-08"): due_from/due_to bound the DUE date, opens_from/opens_to bound the OPEN (submissions-from) date. Give any subset; compute ranges from today for 'due this week', 'opened last week', etc. This is the best way to answer 'what's due/opened in some period', since it also carries submission status.
By default only courses where you are a STUDENT are included (your own homework); set include_all_role true to also include courses where you are a teacher/TA.
Each assignment: {id, course_id, course, role, name, due, opens, cutoff, status, url}, where role is the user's role in that course (student/teacher/…) and status is 'graded' / 'submitted' / 'not submitted'. Times are Taipei time 'YYYY-MM-DD HH:MM'; null means unset. Sorted by due date.
Credentials come from the MCP settings headers, not from you.
Inputs: sem, due_to, due_from, opens_to, +3 more.
list_coursesList Moodle coursesA
List the student's enrolled Moodle courses (with the user's role in each), filtered by semester.
By default (no sem), returns ONLY the latest semester's courses (the current term). Pass sem as an NCCU term code (e.g. "1142") to get that semester; pass "all" to return every enrolled course.
Each course is returned as an object with:
id (int) Moodle course id, usable in other course tools
name (str) full course title
url (str) direct link to the course
semester (str) NCCU term code the course belongs to (e.g. "1151")
current (bool) true if it is in the current (latest) semester
role (str) the user's role: 'student', 'teacher', 'editingteacher', 'teachingassistant', … (null if unknown)
Credentials come from the MCP settings headers, not from you.
Inputs: sem.
search_coursesSearch my coursesA
Search the user's enrolled courses by keyword — the Moodle app's "filter my courses" box. query is matched as a case-insensitive substring of each course name, which contains the NCCU term code, the Chinese title and the English title — so a course code, a Chinese word or an English word all work (e.g. "物件導向", "Object-oriented", "703009").
Searches ALL enrolled courses across every term by default, so a keyword finds the course whatever semester it is in. Pass sem as a term code (e.g. "1142") to scope the search to one semester, or "latest" for the current term only.
Returns the same fields as list_courses: {id, name, url, semester, current, role}, newest term first. An empty query returns everything in scope (same as list_courses).
Credentials come from the MCP settings headers, not from you.
Inputs: query (required), sem.
upcoming_deadlinesUpcoming deadlinesA
List the student's upcoming action events (assignment due dates, quiz closings, etc.) across courses within the next days (default 14). This is the best 'what's due soon' overview.
By default only events from courses where you are a STUDENT are included; set include_all_role true to also include courses where you are a teacher/TA. (Personal/site events are always kept.)
Each event: {name, course_id, course, role, due, overdue, module, url}. due is Taipei time 'YYYY-MM-DD HH:MM'. Sorted soonest first.
Credentials come from the MCP settings headers, not from you.
Inputs: days, include_all_role.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Most tools target distinct resources (courses, announcements, assignments, grades, modules, notifications). However, list_courses/search_courses overlap as both enumerate courses with different filters, and list_assignments/upcoming_deadlines both surface due-date information, creating minor ambiguity.
Tool names consistently use snake_case with a clear verb_noun pattern (list_*, get_*, search_courses). The only deviation is upcoming_deadlines, which is a noun phrase rather than verb_noun, but it remains predictable and readable.
11 tools is well-scoped for a student-facing Moodle assistant. Each tool has a clear, useful purpose, and the set is neither bloated nor too thin.
The set covers core read-only student workflows: courses, announcements, assignments, deadlines, grades, and course content. Write operations like submitting assignments are missing, but these appear outside the server's apparent purpose, so the gap is minor.
Maintenance
Related MCP Connectors
PLATO (Pusan National University LMS) MCP server via Moodle Web Service API + ubboard scraping
One MCP endpoint for your company's tools, with per-person sign-in, policy and audit.
Aspire Learning MCP — browse courses, chapters, lessons, and import LaTeX quizzes
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Related MCP Servers
- AlicenseAqualityBmaintenanceLets MCP clients access a student's ZHAW Moodle account to list courses, find and download learning materials, sync local copies, and track deadlines, assignments, and announcements.161MIT
- AlicenseNot gradedqualityCmaintenanceEnables read-only Moodle access through MCP for querying courses, content, assignments, grades, forums, discussions, submissions, and calendar events, while supporting multiple profiles and local ICS export.58 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables users to retrieve Modeus schedules, iStudent grade/rating data, and eLearn course listings, content, and files through local MCP tools while using their university service login.1Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables users to link their Ed Discussion, Moodle and OnTrack accounts through a cloud-hosted SSO broker and then browse course materials, threads, deadlines, grades, tasks and attendance-code evidence from an OAuth-capable MCP client. Course discovery and binding tools let users associate courses across platforms, with per-user scoped access and revocation controls.5PolyForm Noncommercial 1.0.0