feat(mcp): add NUSMods MCP server (mcp.nusmods.com/mcp) - #4457
feat(mcp): add NUSMods MCP server (mcp.nusmods.com/mcp)#4457TheMythologist wants to merge 6 commits into
Conversation
Add a new `mcp/` pnpm workspace hosting a Model Context Protocol server for NUS module data, to be deployed at mcp.nusmods.com/mcp. - Streamable HTTP transport in stateless mode (fresh server per request), suited to Vercel serverless (mirrors the /export deployment model). - `get_module`: full module details from the public v2 JSON CDN. - `search_modules`: keyword search proxied to the public modules_v2 ElasticSearch index (the same cluster the website Course Finder uses). - In-memory TTL cache, vendored module/ES types, human-readable summaries plus structuredContent output. - Local dev server, health check, Vercel + oxlint config. Rate limiting is handled upstream by Cloudflare. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the website's Course Finder facets against the modules_v2 ElasticSearch index: - semesters (nested semesterData.semester) - levels (moduleCode.level, thousand form) - faculties / departments / gradingBasis (.keyword term filters) - attributes (moduleAttributes.keyword) - minCredit / maxCredit (numeric moduleCredit range) - noExam (must_not exists semesterData.examDate) Filters combine with AND across categories, OR within a category. Also add ES highlight snippets (rendered as markdown bold) and an applied-filters summary in the text output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire vitest into the mcp workspace and cover the pure logic: - cache: caching, in-flight de-dup, TTL expiry, no caching of rejections - nusmodsApi: URL construction, code upper-casing, 404 -> ModuleNotFoundError - elastic: query builder (multi_match/match_all, every filter clause, level normalisation, total parsing) via a mocked fetch - format: module summary + search result rendering incl. highlight -> markdown 30 tests, no network. `pnpm check` now runs lint + typecheck + test. Disable perfectionist/sort-objects for *.test.ts so fixtures can read naturally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the MCP server's `academicYear` update to the existing "PR2" checklist next to the app-config.json bump: update the default in mcp/src/config.ts (and mcp/.env.example), and the Vercel override if set. Note it powers get_module while search_modules is unaffected (modules_v2 is always current-year). Link the root checklist from mcp/README.md and refresh the roadmap now that M4 is done. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an `mcp` job (mirroring the export job, plus `pnpm test`) that runs lint, typecheck and the vitest suite, and require it on checkout in the build_and_test workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4457 +/- ##
==========================================
+ Coverage 54.52% 58.48% +3.96%
==========================================
Files 274 316 +42
Lines 6076 7294 +1218
Branches 1455 1794 +339
==========================================
+ Hits 3313 4266 +953
- Misses 2763 3028 +265 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The in-memory cache is a module-level singleton that persists across tests; relying on unique module codes per test was fragile. Add a clearCache() seam and call it in beforeEach so the suite is robust to test-ordering changes (addresses Greptile review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The |
What
Adds a new
mcp/pnpm workspace: a Model Context Protocol server that exposes NUS module data to LLM clients (Claude Desktop, Cursor, etc.), intended for deployment atmcp.nusmods.com/mcp. It ships two tools backed entirely by existing public NUSMods infrastructure — no new database or data pipeline. Part of #4173Why
Students (and their AI assistants) increasingly want to query modules conversationally — "what are the prereqs for CS3230?", "find level-2000 Computing modules with no exam". An MCP server makes NUSMods' open data directly consumable by any MCP-capable LLM, reusing the same CDN JSON and ElasticSearch cluster the website already relies on.
Tools
search_modules— keyword search over module code / title / description for the current academic year, with faceted filters mirroring the website's Course Finder:semesters,levels,faculties,departments,gradingBasis,attributes,minCredit/maxCredit,noExam. Filters combine with AND across categories and OR within a category. Returns ranked results with<mark>→**highlight snippets.get_module— full details for one module code (case-insensitive, optionalacadYear): description, credits, faculty/department, workload, requisites, exam dates, and per-semester timetable.Both return a human-readable summary in
contentplus the raw object instructuredContent.Architecture
@modelcontextprotocol/sdk, in stateless mode (sessionIdGenerator: undefined,enableJsonResponse: true) — a fresh server per request, which suits serverless. Verified empirically that the SDK serves tool calls per-request without a persisted session.api/mcp.ts), mirroring the/exportdeployment model. Rate limiting is handled upstream by Cloudflare, so it is intentionally not implemented here.get_module→ v2 JSON CDN (https://api.nusmods.com/v2/{AY}/modules/{CODE}.json)search_modules→ themodules_v2ElasticSearch index (the same cluster the website's Course Finder queries directly from the browser)TODO(shared-types)to extract a shared package later).Testing
404 → ModuleNotFoundError, cache (caching / in-flight de-dup / TTL / no caching of rejections), and the formatters.initialize→tools/list→ both tools against the real CDN + ES, plus a filter smoke test where every result was programmatically checked (level, faculty, semester, credit range, attributes OR-semantics,noExam).pnpm check(lint + typecheck + test) is green. A new CircleCImcpjob runs lint/typecheck/test on every build.Not in this PR
mcp.nusmods.com(DNS + env).vercel.jsonis in place (/mcp→ function,/→ health check).moduleInformation.jsonfallback, venues / faculty-listing / academic-calendar tools, and MCP resources (e.g.nusmods://{ay}/module/{code}).Maintenance
The only recurring task is bumping the academic year in
mcp/src/config.ts; this is documented in the repo-rootMAINTENANCE.mdalongside the existingapp-config.jsonbump.