The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Anteater MCP listing page.
An MCP server that lets Claude and ChatGPT help you pick classes at UC Irvine.
It wraps Anteater API — UCI's course catalogue, the live schedule of classes (WebSoc), historical grade distributions, enrollment history, prerequisite trees, AP credit, degree requirements and supplementary RMP ratings — and exposes them as 20 tools, 6 guided prompts and 4 reference resources, shaped around the questions students actually ask.
Zero runtime dependencies. Run the source with Node 24 LTS, or download a standalone Linux, macOS or Windows executable that already contains the same Node runtime.
Then add it to your client — Claude Desktop, Claude Code, ChatGPT, or any other MCP client.
Nothing else is required. An API key is optional but recommended.
Each GitHub Release contains native
archives for Linux, macOS and Windows on amd64 and arm64, plus SHA256SUMS.txt. These do
not require Node.js:
In an MCP client configuration, use the absolute path to anteater-mcp (or
anteater-mcp.exe) as command and omit the args array. macOS archives are ad-hoc
signed; public Developer ID signing and notarization are not yet configured.
The published image supports Linux amd64 and arm64. Docker Desktop on macOS and Windows runs the same Linux image:
The Compose service is non-root, read-only, capability-free and bound to loopback by default. See DEPLOY.md before placing it behind public HTTPS.
Open the config file:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Add an anteater entry. The path must be absolute — Claude Desktop does not run the
server from your project directory:
With an API key:
Restart Claude Desktop completely (quit, don't just close the window). You should see 20 anteater tools, and the 6 prompts appear as slash commands.
claude_desktop_config.example.json in this repo is the same thing, ready to copy.
With a key:
Verify with claude mcp list.
Two routes. The first needs no server.
gpt-actions-openapi.json into the
Schema boxAuthorization: Bearer <key> on every call and you get your own quota. ⚠️ The key is
stored with the GPT, so anyone you share the GPT with uses your key. Fine for a
private GPT; do not publish one with your key in it.gpt-instructions.md into the Instructions boxThe GPT calls anteaterapi.com directly. Twelve operations cover courses, WebSoc,
grades, enrollment history and degree requirements.
The trade-off: the GPT receives raw JSON. WebSoc responses nest four levels deep
(schools > departments > courses > sections), so broad queries get truncated by
Actions — the supplied instructions tell it to always narrow. Prerequisite evaluation
and conflict detection are not available; the model has to reason them out itself.
ChatGPT's Developer Mode connector needs a public HTTPS MCP endpoint:
Add the URL under Settings → Connectors → Advanced → Developer mode. All 20 tools work, including batch course lookup, deterministic degree-progress checks, prerequisite checking and conflict detection.
ChatGPT's developer mode supports OAuth, no authentication, or mixed — there is no field for a custom header, so put the token in the URL query string:
This URL form is a compatibility option, not a security improvement: URLs may be saved in connector settings and reverse-proxy logs. Use a Bearer header whenever the client supports one.
Both transports ChatGPT accepts, SSE and streaming HTTP, are implemented. For a permanent deployment rather than a tunnel, follow DEPLOY.md — the setup is identical to the Claude one, only the connector UI differs.
⚠️ Without
ANTEATER_MCP_TOKENthe endpoint is unauthenticated. Set it before exposing anything. See HTTP mode security.
⚠️ ChatGPT's MCP plugins are web-only. OpenAI's own documentation says developer mode is "available to Pro, Plus, Business, Enterprise, and Education accounts on the web" — the phone apps cannot reach an MCP server. For ChatGPT on a phone, use a Custom GPT whose Action points at this server's REST facade, described below.
| Custom GPT + Actions | Developer mode + MCP | |
|---|---|---|
| Server needed | No | Yes, public HTTPS |
| Auth options | None · API Key (Basic/Bearer/custom header) · OAuth | Access token / API key with a Bearer, Basic or custom header scheme · OAuth · none. Claude's connector dialog takes request headers too, so ?token= is only a fallback |
| Can carry your Anteater key | Yes, API Key → Bearer | Yes, server-side via ANTEATER_API_KEY |
| What the model gets | 12 raw API operations, JSON | All 20 tools, formatted, plus 6 prompts and 4 resources |
| Prerequisite / conflict checking | No — the model must reason it out | Yes |
| Works on mobile | Yes | No — MCP plugins are web-only |
Claude's iOS and Android apps support remote MCP servers. Deploy this behind HTTPS, add it once on claude.ai in a browser, and it syncs to the apps — you cannot add a new server from the phone itself. DEPLOY.md has a complete recipe: token auth, a systemd unit, Caddy or nginx, and the connector setup.
The ChatGPT Custom GPT route also works on mobile and needs no server at all.
Codex speaks streamable HTTP and reads the token from an environment variable, so it never lands in a config file:
Or write it to ~/.codex/config.toml directly — a project-local .codex/config.toml
takes precedence over the global one:
Codex reads the variable at connect time and sends Authorization: Bearer <token>.
The server speaks standard MCP over stdio. Point your client at:
Or run it as a streamable-HTTP server at http://127.0.0.1:8787/mcp with
node anteater-mcp.mjs --http.
Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05 are all accepted;
the server negotiates down rather than echoing whatever it is sent.
Finding classes
| Tool | What it answers |
|---|---|
search_sections | The workhorse. Live sections for a term: times, instructor, room, seats, waitlist, final exam. Supports an exclusive day filter (daysOnly) and a blocked-window filter (avoidDays + avoidStart/avoidEnd) for "I work Monday afternoons" |
recommend_courses | The one you want. Filter by GE, days, time window and open seats; rank by historical GPA |
search_courses | What courses exist at all |
get_course | One course in full: description, prerequisites, restrictions, what it unlocks |
get_courses_batch | Up to 50 known courses in one compact comparison, with optional details |
list_terms | Which quarters have data, plus the academic calendar |
list_departments | Department codes (CS resolves to COMPSCI) |
Judging a class before you take it
| Tool | What it answers |
|---|---|
get_course_grades | Grade distribution by instructor or by term — "which professor should I take?" |
get_instructor | A professor's courses and the grades they actually give |
get_rmp_ratings | Rate My Professors average, review count and profile link for instructors teaching in a term |
get_enrollment_history | Day-by-day fill curves — "will I get in?" |
get_syllabi | Links to syllabi from past offerings — real workload and grading breakdown |
get_course_materials | Required and recommended textbooks, with ISBNs and UCI Library links |
Checking you can actually enrol
| Tool | What it answers |
|---|---|
check_prerequisites | Walks the prerequisite tree against what you've completed |
check_schedule | Meeting conflicts, final-exam conflicts, total units, and whether the set is actually enrollable — missing discussions/labs, cancelled or full sections, TBA meetings |
get_ap_credit | What an AP score is worth: units, GE, courses cleared |
Planning a degree
| Tool | What it answers |
|---|---|
get_program_requirements | Degree requirement trees, including university-wide GE |
check_degree_progress | Deterministic progress check from completed courses and AP scores; keeps uncertain rules visibly unverified |
list_programs | Majors, minors, specializations |
get_sample_program | The catalogue's recommended quarter-by-quarter sequence |
Five of these compute things the upstream API does not provide: prerequisite-tree evaluation, schedule conflict detection, the join between the live schedule and historical grade data, AP-grant rendering, and deterministic degree-progress evaluation.
All 20 are annotated readOnlyHint: true — nothing here mutates anything.
In Claude Desktop these appear as slash commands. Each one drives a full multi-tool workflow rather than a single lookup.
| Prompt | Arguments | What it does |
|---|---|---|
plan-quarter | term*, goals | Builds a complete conflict-free schedule from scratch |
pick-professor | course*, term | Compares instructors by grades, then by who's actually teaching |
find-easy-ge | term, ge, constraints | Finds a GE that fits your schedule and grades well |
check-my-schedule | term, sections | Validates section codes for conflicts, units and enrollment risk |
can-i-take | course*, completed | Checks eligibility, including AP substitutions |
degree-check | major*, completed, apScores | Runs the deterministic progress check, then plans what remains |
* = required. Arguments named term, ge, department, major and course offer
autocomplete through the MCP completions API.
Reference tables your client can read directly, without spending a tool call:
anteater://reference/departments — every department code and nameanteater://reference/terms — every term with schedule dataanteater://reference/ge-categories — GE codes and what they meananteater://reference/restriction-codes — enrollment restriction codes, per the RegistrarOptional, but recommended: anonymous calls draw on a shared hourly quota that is easy to exhaust, and a key gives you your own.
It will not unlock fuzzy search. /v2/rest/search rejects ordinary keys with
not permitted to access this resource — it needs elevated permission ICSSC grants
separately. Without it search_courses falls back to substring matching on title, then
description, and says so in its output. The structured filters (department,
geCategory, courseLevel, units) are unaffected and are usually the better tool
anyway.
secret — publishable keys are verified against the Origin
header, which Node does not send, so they will not work here.env is gitignored. Never paste a key into an issue, PR or screenshot.
| Variable | Purpose |
|---|---|
ANTEATER_API_KEY | Your secret key. See above. |
ANTEATER_API_BASE | Defaults to https://anteaterapi.com. Point at a self-hosted instance. |
ZOTCOURSE_API_BASE | Independent fallback for live WebSoc schedule queries. Defaults to https://zotcourse.appspot.com; set to off to disable. Only search_sections, check_schedule and recommend_courses use it, and fallback results are labelled as potentially cached. |
ANTEATER_MCP_TOKEN | HTTP mode only, and required before you expose the server. Clients send Authorization: Bearer <token>, which is preferred, or use https://host/mcp?token=<token> where the client accepts only a URL. Any token in a URL can land in browser history, connector settings, and proxy logs. /health stays open. Unset means no authentication, which is only safe on loopback. |
ANTEATER_ALLOWED_ORIGINS | HTTP mode only. Comma-separated extra origins to allow. |
ANTEATER_TRUSTED_PROXIES | HTTP mode only. Which direct peers may set X-Forwarded-*. Accepts CIDRs, bare addresses, and the shorthands private and loopback. Unset means the headers are ignored, because they are client-supplied and would otherwise let anyone forge their address in your log. Behind a reverse proxy set this, or every request looks like it came from the proxy. private covers the Docker bridge. |
HOST / PORT | HTTP mode only; equivalent to --host / --port. |
127.0.0.1 by default. server.listen(port) with no host binds every
interface, which would expose an unauthenticated server to the whole LAN. --host
is required to change that, and it warns when you do.Origin. The MCP specification requires this of local HTTP servers:
without it, any page you visit can POST to your port and drive every tool (DNS
rebinding / CSRF). Requests with no Origin — native MCP clients — are allowed;
requests with one must be localhost or listed in ANTEATER_ALLOWED_ORIGINS.Access-Control-Allow-Origin echoes the single validated origin, never *.ANTEATER_MCP_TOKEN is set. That is fine on loopback and
not fine anywhere else; the server warns at startup if it is bound off-loopback without
one. See DEPLOY.md.[REDACTED]./health reports status and the source URL; /source redirects to this repository,
which helps anyone deploying a modified copy comply with AGPL section 13.| Symptom | Cause and fix |
|---|---|
| Tools don't appear in Claude Desktop | The path must be absolute, and you must fully quit and reopen the app. Check the config parses: node -e "require('./claude_desktop_config.json')". |
Anteater API rate limit hit | The anonymous quota is shared and replenishes hourly. Set ANTEATER_API_KEY. |
search_courses returns odd results | Fuzzy search needs a privileged key that ordinary keys are not granted; it falls back to substring matching and says so. Use the structured filters instead. |
Too broad from search_sections | A whole term is tens of thousands of sections. Add department, courseNumber, ge, instructor or sectionCodes. |
"2026 summer" is ambiguous | UCI has three summer terms. Use Summer1, Summer2 or Summer10wk. |
Unknown department "..." | Use list_departments, or read anteater://reference/departments. |
| A course has no grade data | Recent quarters lag, and P/NP-only courses have none. |
| Seats look stale | Live figures are cached for 5 minutes; the catalogue for 24 hours. |
| Schedule fallback appears | Anteater API was unavailable, so the schedule came from Zotcourse's WebSoc-backed endpoint. Verify seats and section pairing in WebReg. Set ZOTCOURSE_API_BASE=off if you do not want this network fallback. |
UPSTREAM.md records the exact Anteater API version and the upstream commits this server was validated against, endpoint by endpoint — start there when the API changes and something begins returning wrong or empty results.
VERIFICATION.md is a full release checklist — per-regression pass criteria, security checks and integration checks — written so someone who has never read the code can run it.
CI uses the same pinned Node 24 LTS release as Docker and native packaging. It runs the offline suite, syntax and OpenAPI checks, a standalone-executable smoke test, and a locked-down container protocol test. It deliberately makes no live Anteater API calls, so it never draws on the public rate limit.
Pushing a tag that exactly matches v plus the package.json version builds six native
executables, generates checksums and provenance attestations, pushes an amd64/arm64 image
to GHCR, and publishes the GitHub Release. Stable tags update :latest; every release
updates :beta; exact :vX.Y.Z tags are immutable.
After the first image publish, set the GHCR package visibility to Public once. The
workflow uses the scoped GITHUB_TOKEN; if repository policy blocks bot-created Releases,
add a fine-grained RELEASE_PAT secret with Contents write access, matching the upstream
project's fallback.
Anteater API also serves dining halls, library traffic and study-room bookings. Those are deliberately not wrapped — this server stays focused on choosing and registering for classes.
LARC tutoring sections are in scope but effectively dead upstream: /v2/rest/larc returns
22 courses for 2024 Fall and nothing for any term since, so a tool would always answer
"none" for the term a student is actually planning.
These are properties of the upstream data, not bugs. The server states them rather than papering over them, because the alternative is confident wrong advice.
Lec A → Dis A1), others number companions
independently (I&C SCI 31: lectures A/B, labs 1–9). check_schedule therefore tells
you when a required component is missing entirely, and when a pairing cannot be
verified — but it cannot confirm that a given lab goes with a given lecture. Confirm
that on WebReg. Neither of ICSSC's own clients infers this either: AntAlmanac and
PeterPortal both render sections as a flat list and leave the pairing to the student,
which is good evidence the data simply is not there.search_sections and
recommend_courses surface the codes and their meanings; whether you satisfy
"Major only" or "Graduate only" is enforced by the registrar.check_prerequisites reports
any completed course it does not recognise instead of silently ignoring it, but only an
advisor can clear transfer credit.STAFF. A small sample size makes an average GPA unreliable — the
tools always print n so you can judge.recommend_courses ranks by the average across all
past instructors, which may not be whoever is teaching this term. Use get_course_grades
to check the specific instructor before deciding.A six-dimension parallel review (logic, MCP conformance, live-API contract, security, robustness, repo readiness) produced 41 raw findings, 38 after deduplication. The ones that would actually have misled someone:
| Bug | Consequence |
|---|---|
A bare null JSON-RPC message killed the process | Both transports. Over HTTP that was an unauthenticated 4-byte denial of service. |
| Every final exam date was one month early | WebSoc's finalExam.month is 0-indexed (11 = December); the code treated it as 1-indexed. "Tue Nov 8" was really December 8 — someone books a flight on the wrong date. |
| 7 of 19 restriction codes were wrong | K was labelled "Cross-listed" but means Graduate only, and appears 608 times in a single term. X was "Separate final exam" but means authorization codes are needed even to drop. |
"2026 Summer 1" silently returned Spring | The bare s alias swallowed summer. Three of six quarters were unreachable by natural phrasing, and it returned a confident, entirely wrong schedule. |
| Standalone lab courses counted as 0 units | check_schedule excluded Lab sections, so CHEM 1LD (3 units, no lecture) vanished — a student could misjudge the 12-unit full-time threshold that gates financial aid and F-1 status. |
recommend_courses forced sectionType: Lec | Every seminar-only GE was invisible; GE-1A returned nothing at all. |
get_program_requirements with ugrad always failed | The endpoint has a required id parameter the tool never sent. |
HTTP mode bound 0.0.0.0 without validating Origin | While logging "listening on localhost". |
| Fall sorted as the earliest term of its year | Reversed the chronology in get_enrollment_history and get_course_grades. |
A second round, driving the server through six realistic student scenarios end to end, found 54 more — 10 of them blockers. The worst:
| Bug | Consequence |
|---|---|
check_schedule gave a clean all-clear to unenrollable schedules | It checked only times. A lecture with no required lab, or a full or cancelled section, passed silently — the student would be rejected at WebReg. |
days meant "meets on at least one of" | A student who could only attend Tu/Th was shown three- and four-day courses, and courses whose mandatory labs were all MWF. |
| A bare instructor surname matched nothing | get_course_grades blamed the course — "may be new or graded P/NP only" — for a professor with 1,642 grades on record. |
| Degree requirements defaulted to the 2023–2024 catalogue | Three years stale, with no indication, for a student on 2026–2027. |
check_prerequisites silently dropped unrecognised courses | Then printed a confident "NOT satisfied" for work the student had actually done. |
recommend_courses hid restriction codes | Its highest-ranked GE picks were courses the student could not enrol in. |
check_schedule threw a raw TypeError | When given the comma-separated string that search_sections documents for the same parameter name. |
Also fixed: inverted check marks in NOT prerequisite subtrees, course numbers lost for
the 48 department codes containing a space, multi-byte UTF-8 corrupted across HTTP chunk
boundaries, a quadratic-backtracking regex on unvalidated input, and socAvailable
mislabelled as "enrollment opens" when it is the schedule publication date.
Undocumented upstream; all handled here, and listed in case they save you the debugging.
days must be comma-separated. Tu,Th works, TuTh is rejected. It matches
at least one of the listed days, not all of them.enrollmentHistory returns parallel arrays (dates[], totalEnrolledHistory[],
requestedHistory[], …), not scalars. -1 means "not tracked".finalExam.month is 0-indexed.<p> and ".startTime / endTime mean "starts at or after" and "ends at or before", not
interval overlap./v2/rest/search needs a privileged API key. An ordinary key is refused with
not permitted to access this resource, which is a different error from the
key is required you get with no key at all — worth distinguishing, since telling
someone who already has a key to get a key is a dead end./v2/rest/websoc/syllabi takes courseId, not department + courseNumber./v2/rest/courseMaterials needs department AND courseNumber together. Either
alone is refused, and it collapses the three summer sessions into a single Summer."A and N" must be split on whitespace and
have the literal and/or discarded, or the conjunction renders as a code.OPEN, Waitl, FULL, NewOnly, or
empty. Only OPEN means a continuing student can enrol now — NewOnly marks seats
held for incoming students. numNewOnlyReserved counts seats inside the capacity that
are reserved the same way, so the apparent opening overstates the real one.Published to the MCP Registry as
io.github.KKazuhaK/anteater-mcp, listing the container image only. The registry entry
describes how to run your own instance; it does not point at anyone's deployment, since a
hosted instance is gated behind its own token and its upstream quota belongs to whoever
runs it.
Each release also publishes the GHCR image and standalone binaries for six platforms.
Data from Anteater API, maintained by ICSSC Projects.
This is not an official UCI tool. Verify on WebReg or the General Catalogue before registering. Use is subject to Anteater API's attribution policy.
Licensed AGPL-3.0-or-later, matching the upstream Anteater API server so code can move freely in either direction if this is ever contributed upstream. This is an independent HTTP client and contains no upstream source code. If you modify it and run it as a network service, AGPL section 13 requires you to offer users your Corresponding Source — see NOTICE.
For academic use: