ntulearn-mcp
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., "@ntulearn-mcpWhat's due in NTULearn over the next two weeks?"
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.
ntulearn-mcp
MCP server + agent-first CLI for NTULearn — NTU Singapore's Blackboard Learn instance. Point any MCP host (Claude Desktop, Claude Code, Cursor, Cline, Prime Agent) at it to ask about your courses, announcements, calendar, and grades — and to organise course files into a folder hierarchy on disk.
The shipped server is a single Rust binary built on
ultrafast-mcp, with 21 tools, 2 prompt templates, and
a course-resource URI. No Python, no pip, no virtualenv.
Website: https://ntulearn-mcp.krtk.app
Quick start
git clone https://github.com/gangula-karthik/ntulearn-mcp.git
cd ntulearn-mcp/rust
cargo build --release # binary: rust/target/release/ntulearn-mcpLog in once — setup opens NTULearn in a throwaway-profile browser and captures your session cookie
for you. No copy-paste, no devtools, no keychain prompts.
rust/target/release/ntulearn-mcp setup
rust/target/release/ntulearn-mcp check # expect: Live validity : OK (200)Register the binary with your MCP host:
{
"mcpServers": {
"ntulearn": {
"command": "/Users/you/ntulearn-mcp/rust/target/release/ntulearn-mcp",
"args": []
}
}
}That's it. The same binary serves MCP with no arguments and acts as a CLI with a command.
Related MCP server: sjtu-canvas-mcp
Why the CLI: about 64x fewer tokens
MCP hosts inject every tool's JSON schema into context every turn — about 6.6k tokens for this server's 21 tools. The same binary doubles as a plain CLI, whose terse tabular output an agent reads in a few hundred tokens:
ntulearn-mcp courses # 6 courses ≈ 100 tokens (MCP tool result: ≈ 450)
ntulearn-mcp tree _2706844_1 # a course's content tree
ntulearn-mcp whats-new --days 7 # announcements + due dates + grades digest
ntulearn-mcp multi "courses" "grades" "upcoming --type GradebookColumn" # concurrentMeasured with the built-in benchmark (ntulearn-mcp selfbench):
Metric | MCP | CLI | Improvement |
Discovery tokens (per conversation) | 6,639 (every turn) | 586 (once, on demand) | 11.3x |
Task output tokens (4-task suite) | 2,193 | 492 (compact TSV) | 4.5x |
10-turn conversation model | ~68,583 tok | ~1,078 tok | 63.6x |
| — | 59ms | 3.4x vs v0.3 |
| — | 82µs | — |
4 commands concurrently ( | 225ms sequential | 59ms (singleflight-deduped) | 3.8x |
The CLI shares the same handlers, cache, and cookie lifecycle as the MCP server. Point your agent at
the binary and have it run ntulearn-mcp help (≈590 tokens, once) instead of registering 21 tools.
What people ask for
"What announcements happened across my courses this week?" — fans out across all enrolled courses, newest first.
"What assignments are due next week?" — reads the calendar, including gradable items.
"Organise this semester's content into
~/NTU/y3s1/sc2002/week 8/…." — walks the course tree and downloads into any folder layout you describe."Pull the due dates and grade weightages out of this course briefing PDF." — reads small text-heavy documents inline.
For multi-page, diagram-heavy lecture decks, use download_file and open the PDF in your client —
MCP tool results are capped at 1 MB, so this server does not compete with drag-and-drop for full decks.
CLI reference
ntulearn-mcp help prints this list; ntulearn-mcp help <cmd> shows one command's flags.
courses my enrolled courses (run this first for course ids)
contents course-id one level of a course's content tree
tree course-id entire content tree of a course in one call
search course-id query search one course's content tree
find query search content across ALL courses
dl course-id content-id download a content item's files to disk
read course-id content-id read a content item's files inline (pdf/office/text)
upcoming calendar items + due dates across courses
announcements announcements across courses, newest first
grades gradebook columns + your grades across courses
attempts course-id column-id attempts on one gradebook column
messages course messages (inbox/sent)
message message-id read one course message
users course-id users enrolled in a course
groups course-id groups in a course
members course-id group-id members of one group
summary course-id one-shot course digest (staff, contents, dates)
whats-new curated digest of everything new since a cutoff
dl-course course-id bulk-download a course's files
ics export calendar items to an .ics file
grades-csv export gradebook to .csvOutput is compact TSV by default. Flags: --json, --md, --toon, --fields a,b,
--max-chars N, --full, --limit N, --offset N. Every MCP tool argument is also accepted as a
kebab-case flag (--include-disabled, --course-ids a,b, --mode vision).
multi runs several commands concurrently in one process, deduplicating identical in-flight HTTP
fetches and sharing one cache:
ntulearn-mcp multi "courses" "announcements --since 2026-01-01" "grades"
# results arrive concatenated, each prefixed with: ===== <command> =====Output formats
Format | Flag | Notes |
Compact TSV | default | Smallest; field-capped, no nesting. |
Markdown |
| Human-readable. |
JSON |
| Raw payload. |
TOON |
| Smallest full-fidelity format — 40% fewer tokens than pretty JSON (52% on large lists). |
TOON (Token-Oriented Object Notation, spec v4.1) declares
array shapes once and drops repeated keys, so the savings scale with how tabular the payload is.
Every MCP tool accepts response_format: "toon" too.
Tools
21 tools, most doing cross-course aggregation by default — you rarely pass course IDs by hand.
Tool | What it does |
| List enrolled courses. |
| Walk a course's content tree. Omit |
| Recursive substring search within one course. |
| Calendar items across enrolled courses. Defaults to the next 2 weeks. |
| Announcements across enrolled courses, newest first. Optional |
| Gradebook columns across enrolled courses, with your scores when available. |
| Download every file on a content item to disk. |
| Read an attached file's content inline (no filesystem hop). |
| List mailbox messages (inbox/sent). |
| Read one message by ID, with full body and recipients. |
| List users in a course (instructors, TAs, students). |
| List the groups defined in a course. |
| List the members of a course group. |
| List submission attempts for an assignment. |
| Search content across all courses; results carry courseId + breadcrumb. |
| One course's entire content tree as nested JSON (bounded by |
| Recursively download every file in a course to |
| One-call digest: announcements + upcoming + gradebook since a cutoff. |
| Export calendar items as an iCalendar |
| Export your gradebook as a CSV string. |
| Briefing for one course: instructors, upcoming, announcements, grades, top folders. |
Read-only tools default to response_format='json'; pass 'markdown' for a readable summary.
List-returning tools accept limit/offset. A resource URI,
ntulearn://courses/{course_id}, returns a JSON course briefing, and two prompts
(ntulearn-weekly-brief, ntulearn-assignment-triage) chain the tools for you.
Known environment limits
list_messages/read_message— the public/users/me/messagesREST API returns 404 on this instance, so the client walks the internal v1 conversation API instead (one cached mailbox flatten → per-course conversations) and derives recipients from the conversation.get_group_members— the public groups endpoint returns 403 for student accounts, so the client uses the internal v1 memberships endpoint instead.Calendar windows wider than ~16 weeks are rejected by NTULearn with a 400
courseErrorsentry — keepsince/untilwithin a semester.read_file_contentextracts text from simple documents; for large or graphical PDFs, usedownload_fileand open the file in your client.
Authentication
The BbRouter cookie is resolved strictly in this order:
NTULEARN_COOKIEenvironment variableConfig file at
<config>/ntulearn-mcp/cookieFirefox
cookies.sqlite(read-only, plaintext)
No keychain access, ever — no password dialogs, no security commands. When NTULearn rejects a
call with 401, the server re-resolves the cookie, persists a working value, and retries once.
Refresh is never proactive; it happens on a live 401 or when you run ntulearn-mcp refresh.
setup is the normal first-run path: it validates any existing cookie live, and if there is none,
opens Chrome/Arc/Brave/Edge/Chromium with a throwaway profile and polls the DevTools protocol for
your cookie (15-minute login timeout). The captured value is validated against the API before it is
saved, then the browser and profile are cleaned up. On a headless server with no supported browser,
it falls back to a one-time paste.
The cookie expires with your NTULearn session (days to weeks). Re-run setup when it does.
Log in at https://ntulearn.ntu.edu.sg.
DevTools (
F12) → Application → Cookies →ntulearn.ntu.edu.sg.Copy the Value of the
BbRoutercookie (starts withexpires:).Run
ntulearn-mcp setupand choose paste, or set it in your MCP host'senvblock:{ "env": { "NTULEARN_COOKIE": "expires:1234567890,id:..." } }Restart your MCP host.
Configuration
All optional — the defaults work for most users.
Env var | Default | Purpose |
| — | Manual cookie fallback. |
|
| Change for a different Blackboard instance. |
|
| Default |
|
| SQLite response cache (falls back to in-memory). |
|
|
|
|
|
|
| — | Set to |
| 86400 | Check throttle. |
| upstream repo | Override for forks. |
| — | Set to |
Updates
The binary checks GitHub for a newer release at most once every 24 hours, and never blocks or breaks the server. When one is available you get a one-line notice in the MCP host's log and at the start of the session.
ntulearn-mcp update # check + prompt before installing
ntulearn-mcp update --check # exit 0 = current, 10 = update available
ntulearn-mcp update --yes # install without prompting
ntulearn-mcp update --auto on # background-install new releases
ntulearn-mcp update --channel beta # try prereleases firstWith --auto on the server downloads the release in the background, verifies its sha256 against
the release's checksums.txt, and atomically swaps the binary — the new version activates when
your MCP host next restarts the server. Every checksums.txt is minisign-signed by CI, and the
public key is committed as minisign.pub and baked into the binary, so a tampered
release is refused. Set require_signature: true in settings.json to refuse unsigned releases outright. Installs the binary did not make itself (cargo, Homebrew, dev builds) are
detected and answered with the right update command instead of a self-replace. A Homebrew formula
template lives in packaging/homebrew/.
Cutting a release (maintainers): bump rust/Cargo.toml, tag v<version>, push. The workflow
builds Linux, macOS (arm64 + x64), and Windows binaries, and publishes checksums.txt with its
minisign signature. Tags with a hyphen (e.g. v0.8.0-rc.1) publish as prereleases automatically.
Troubleshooting
"No NTULearn cookie found", or tools fail with 401 — run
ntulearn-mcp checkto see the cookie source and live validity, thenntulearn-mcp setupto re-capture one.MCP host lists the server but tool calls return nothing — run
rust/target/release/ntulearn-mcpdirectly. The usual cause is an unresolvable cookie; the error message will say so.Prime Agent lists the server but tools aren't available — run
/reload(or restart it) so settings are re-read.read_file_contentreturns "No download URL found" — that content node is not a file (a text page or tool link, say). Usedownload_filefor real attached files.
Development
cd rust
cargo test # 53 unit tests
cargo build --release
target/release/ntulearn-mcp selfbench # token + latency + concurrency benchmarkLayout (rust/crates/ntulearn-mcp/src/):
main.rs no args → MCP stdio server; command → CLI; setup/check/refresh
cli.rs the 21 commands, compact TSV renderer, `multi` concurrency
handlers.rs the 21 tool handlers (shared by MCP server and CLI)
bench.rs selfbench + mock Blackboard API
client.rs reqwest (HTTP/2, retries, parallel fan-out, singleflight dedup)
cache.rs SQLite (WAL, persistent conn) + in-memory TTL cache
cookie.rs layered cookie resolution (never keychain)
setup.rs setup / check / refresh subcommands
capture.rs throwaway-browser CDP cookie capture
parsers.rs HTML body → download URL extraction
render.rs markdown / csv / ics renderers
resources.rs course resource template + reader
prompts.rs prompt templates
schemas.rs tool schemas
tools.rs tool definition registryBefore pushing, check that no credential is staged or in history:
git status --short
git log --all -p | grep -nE '(BbRouter=|expires:[0-9]{10,},id:|Set-Cookie|ghp_[A-Za-z0-9]{20,})' | grep -viE 'test|example|README' || echo "clean"If anything real shows up, do not push. Rotate the credential first.
Disclaimer
Use at your own risk. This is an unofficial, personal-use tool. It is not affiliated with, endorsed by, or sponsored by NTU Singapore, Anthology Inc., or Blackboard Learn.
Your account, your responsibility. Driving the LMS with your session cookie may be inconsistent with NTU's acceptable use policy. Check NTU policy if you are unsure.
Your cookie stays local. It is read locally and sent only to
ntulearn.ntu.edu.sg.Don't share cookie values. Anyone with your
BbRoutercan act as you until it expires.Don't run this for someone else. Each user should run their own instance against their own account.
MIT licensed — see LICENSE. All warranties are disclaimed.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for the Inistate platform: module discovery, entry management, and activity submission.
The Academy curriculum as an offline MCP library. Hosted course connector adds progress.
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Remote MCP server for training, nutrition, wellness, and performance data with OAuth 2.0.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server for Canvas LMS with automatic OAuth authentication. Enables interaction with courses, assignments, grades, modules, discussions, quizzes, files, calendar, messaging, and more without manual API token management.2,438 npmMIT
- AlicenseAqualityDmaintenanceProvides MCP tools to read SJTU Canvas data, download course files, and access video/subtitle resources without authentication simulation.21MIT
- FlicenseAqualityCmaintenanceEnables MCP-compatible clients to read courses, assignments, announcements, files, and grades from a Canvas LMS instance.10-
- AlicenseNot gradedqualityBmaintenanceEnables read-only access to University of Waterloo Learn and Piazza, allowing users to view courses, assignments, grades, submissions, discussions, and more through an MCP server.MIT