Skip to main content
Glama
README.md
# purdue-mcp

One MCP server for **all public, real-time Purdue University data** — dining menus, live gym occupancy, the course catalog, bus times, campus events, student orgs, library hours, athletics, news, and weather. Point any MCP client (Claude Code, Claude Desktop, Cursor, …) at it and ask "what's for dinner at Wiley", "how busy is the CoRec", or "when's the next bus from the PMU".

**33 tools across 13 public sources.**

Everything it reads is public and unauthenticated. It never touches a student account, grades, schedules, bursar records, or anything behind a Purdue login.

> Unofficial and community-run. Not affiliated with, endorsed by, or operated by Purdue University.

## Install

**Terminal / application CLI**

```bash
npx -y -p purdue-mcp@latest purdue-data list
npx -y -p purdue-mcp@latest purdue-data schema dining_menu
npx -y -p purdue-mcp@latest purdue-data call dining_menu '{"location":"Wiley","meal":"Lunch"}'
```

`list` and `schema` print JSON. `call` prints the MCP tool result as JSON and exits
nonzero on tool errors. No shell command is passed through to a Purdue tool.

The CLI ships alongside the existing MCP transports; publish this package version
before using the `@latest` commands above.

**Claude Code**

```bash
claude mcp add purdue -s user -- npx -y purdue-mcp@latest
```

**Codex CLI**

```bash
codex mcp add purdue -- npx -y purdue-mcp@latest
```

**Cursor / Claude Desktop / Windsurf** — merge into the client's MCP config:

```json
{
  "mcpServers": {
    "purdue": {
      "command": "npx",
      "args": ["-y", "purdue-mcp@latest"]
    }
  }
}
```

The `@latest` tag re-resolves the newest version on every launch, so it stays
current on its own. Needs Node 20+. No API key, no account, no auth.

Config paths, VS Code, offline/global install, and troubleshooting are in
[install.md](install.md).

### Let an agent install it

Paste this into any coding agent:

> Install the `purdue-mcp` MCP server for me — public real-time Purdue University
> data (dining menus, live gym occupancy, courses, bus times, events, athletics,
> library hours, weather). On npm as `purdue-mcp`, needs Node 20+, no API key.
> Detect my MCP client and register it with `npx -y purdue-mcp@latest` as the
> command, using the `@latest` tag so it stays current. Then verify it connects
> and tell me which tools it exposes.
> Full instructions: https://raw.githubusercontent.com/sharziki/purdue-mcp/main/install.md

### Or run from source

```bash
git clone https://github.com/sharziki/purdue-mcp && cd purdue-mcp
npm install && npm run build
```

## Tools

### Dining — Purdue HFS dining API

| Tool | What it answers |
| --- | --- |
| `dining_locations` | Every dining court / Quick Bites / On-the-GO!, open right now or next meal time |
| `dining_menu` | Full menu for a location and date, by meal and station, with vegan/vegetarian/allergen filters |
| `dining_find_item` | "Who's serving chicken tenders today?" — searches every dining court at once |
| `dining_item_nutrition` | Full nutrition panel for any menu item |
| `dining_line_length` | Crowdsourced live line-length reports (populated around peak meal hours) |

### Registration — live seats (Purdue Banner)

| Tool | What it answers |
| --- | --- |
| `course_availability` | **How many seats are actually open right now** in each section, with waitlists, CRNs, meeting times, instructors |
| `section_details` | Seats, waitlist, prerequisites, and major/level restrictions for one CRN — "why can't I register for this" |
| `banner_terms` | Which terms are open for registration vs. view-only |

### Academics — Purdue.io course catalog

| Tool | What it answers |
| --- | --- |
| `list_terms` | Term codes and date ranges, newest first |
| `list_subjects` | Every subject code (CS, MA, ENGR, …) |
| `search_courses` | Courses by subject, number, or title keyword — with descriptions and credit hours |
| `course_sections` | CRNs, section types, meeting days/times, rooms, and instructors for a term |
| `find_building` | Resolve a building code, e.g. `LWSN` → Lawson Computer Science Bldg |

### Campus life

| Tool | What it answers |
| --- | --- |
| `search_events` | Official university calendar — lectures, athletics, career fairs, deadlines |
| `search_student_orgs` | ~1,200 student orgs, searched by what someone is *into* — typo-tolerant, understands campus synonyms, reads full descriptions. `include_links` returns each club's email, website and socials |
| `student_org_profile` | One org in full: mission, contact email and phone, website and every social account, categories, whether it's taking members, next events |
| `search_club_events` | Upcoming club events: callouts, socials, meetings — same search, plus filters for host org, theme, category, free food/free stuff, and date window |
| `club_event_details` | One club event in full: complete description, street address and coordinates, perks, RSVP count and spots left |
| `boilerlink_categories` | The exact org/event category and theme names the two searches accept |
| `huddle_events` | Student-posted flyers from Huddle — callouts, free-food nights, tryouts and socials that never reach the official calendar. Same typo-tolerant search, plus tag/org filters and a ~2,600-event archive |
| `reddit_purdue` | What students are actually talking about on r/Purdue (unofficial) |
| `purdue_exponent` | Purdue Exponent student newspaper — campus reporting, editorially independent |

### Facilities

| Tool | What it answers |
| --- | --- |
| `recwell_occupancy` | Live headcount and % capacity for all 37 counted RecWell spaces — "how busy is the CoRec right now" |
| `library_hours` | Today's hours and open/closed status for every library, or the full week |

### Athletics

| Tool | What it answers |
| --- | --- |
| `athletics_sports` | Every varsity team and its current season schedule |
| `athletics_schedule` | Full season for one team — opponents, rankings, home/away, venue, results |
| `athletics_upcoming` | Next Boilermaker games across all sports, soonest first |

### Getting around

| Tool | What it answers |
| --- | --- |
| `bus_routes` | Every CityBus route serving campus and Greater Lafayette |
| `bus_stops` | Find stops by name, or the stops nearest a lat/lon |
| `bus_next_departures` | Next scheduled departures from a stop, with minutes-until |

### Exams

| Tool | What it answers |
| --- | --- |
| `course_exams` | Every scheduled exam for a course — date, time, rooms, days remaining |
| `upcoming_exams` | What is coming in the next N days, optionally only your courses |
| `exam_schedule_status` | Which term the published schedules cover, and how many exams they hold |

Purdue schedules **evening exams** at night, outside normal class meetings, and
they are what mid-semester study planning turns on; a course requiring them is
flagged in the catalog. **Final exams** are the separate end-of-term block. Both
come from the Registrar's published schedules.

```
upcoming_exams(days: 21, courses: ["MA 26100", "CS 18000"])
  → CS 18000GLD — Wed 2026-09-30 08:00p-09:00p (in 12d) · HAAS G050, …  [evening]
    MA 26100    — Mon 2026-10-05 08:00p-09:00p (in 17d) · Loeb Plyhs, … [evening]
```

### News and deadlines

| Tool | What it answers |
| --- | --- |
| `purdue_news` | Official newsroom articles, searchable |
| `academic_calendar` | First day of classes, breaks, finals week, add/drop deadlines |

### Environment

| Tool | What it answers |
| --- | --- |
| `campus_weather` | Current conditions, forecast, and active NWS alerts for West Lafayette |

Dates default to **today in the campus timezone** (`America/Indiana/Indianapolis`), so the server gives the right answer no matter where it runs.

## Data sources

| Source | Endpoint | Notes |
| --- | --- | --- |
| Purdue Dining (HFS) | `api.hfs.purdue.edu/menus/v2` | Official, public, unauthenticated |
| Purdue Banner | `selfservice.mypurdue.purdue.edu/prod` | Public class search — **no login**. Authoritative for seats/waitlist/prereqs. HTML, so parsing is version-sensitive. |
| Purdue.io | `api.purdue.io/odata` | Community-run open-source catalog mirror ([Purdue-io/PurdueApi](https://github.com/Purdue-io/PurdueApi)) |
| Purdue Events | `events.purdue.edu/api/2` | Localist public API |
| Huddle | `gethuddle.social/api/firestore/events` | Student-run event app; one request returns the whole college corpus. Behind Vercel's bot challenge, so it is pulled by `purdue-mcp-huddle` on your own machine rather than fetched live — see below. |
| BoilerLink | `boilerlink.purdue.edu/api/discovery` | Anthology Engage public discovery API. Same host students use; `purdue.campuslabs.com/engage` serves it too. Org **website keys are not in the search index** — they only resolve through `/organization/bykey/{key}`, which is also the only place email and socials live. Upstream search is plain keyword OR, so all 1,206 orgs (13 requests) and ~1,500 upcoming events (4 requests) are crawled once and ranked locally. |
| Purdue RecWell | `goboardapi.azurewebsites.net` (Connect2) | Live occupancy counters; account key is the one Purdue's own public widget ships |
| Purdue Libraries | `calendar.lib.purdue.edu` | Springshare LibCal public hours endpoints |
| Purdue Athletics | `purduesports.com/website-api` | Official athletics site's public JSON API |
| Purdue Newsroom / Registrar | `purdue.edu/{newsroom,registrar}/wp-json` | WordPress REST API |
| Registrar exam schedules | `purdue.edu/registrar/pdf/exam/current_*.pdf` | The only published form; there is no exam API. UniTime-generated PDFs at stable `current_*` URLs, replaced in place each term. Text is extracted directly (no PDF dependency) and rows are attributed by carrying the subject and course forward, since the report prints each only once per block. |
| CityBus | `bus.gocitybus.com` GTFS | Static schedule feed; **no public real-time feed exists** |
| r/Purdue | `reddit.com/r/Purdue/.rss` | Unofficial student chatter |
| Purdue Exponent | `purdueexponent.org` RSS | Independent student newspaper |
| NOAA / NWS | `api.weather.gov` | Public federal API |

### Huddle needs one command from you

Huddle is the only source here that cannot be fetched at request time. Every
plain HTTP client — curl, `fetch`, this server — gets `429` with
`x-vercel-mitigated: challenge`, whatever the headers. And the challenge is
IP-reputation gated: a real browser clears it in about a second from a home or
campus connection, and **never** clears it from a datacenter address (measured
on a VPS, which by extension rules out CI runners and any hosted mirror).

There is therefore no server anyone can run that stays fresh. So the fetch
happens on your machine, when you ask for it:

```bash
npx -y purdue-mcp-huddle          # ~4s, uses a Chrome you already have
```

That writes `~/.cache/purdue-mcp/huddle-purdue.json` (~2,600 events) and
`huddle_events` prefers it from then on. Nothing is installed as a service,
nothing runs in the background, and no browser profile or login is touched — a
throwaway profile opens the public events page, reads the API from inside it,
and exits. `puppeteer-core` drives a browser you already have; no browser is
downloaded.

To keep it current, put it on cron — or don't, and refresh when you care. Every
response states how old its copy is and warns past six hours. Full setup,
scheduling and troubleshooting: [`skills/purdue-huddle`](skills/purdue-huddle/SKILL.md),
which an agent can follow on your behalf.

| Where it reads from | When |
| --- | --- |
| `PURDUE_MCP_HUDDLE_MIRROR` | Set — a file path or a URL. Lets a club share one copy. |
| `~/.cache/purdue-mcp/huddle-purdue.json` | You ran the refresher. |
| A copy on this repo's `data` branch | You haven't. Refreshed by hand, often stale, and labelled "shared copy" in every response. |

Requires a Chromium-family browser (Chrome, Chromium, Edge, Brave). Firefox
cannot be used — it does not speak the DevTools protocol.

Responses are cached in-process with short TTLs (30s–24h depending on how fast the data moves) to stay a polite client. Nothing is persisted to disk.

## Ruled out (investigated, no public source)

- **Laundry machine availability** — Purdue moved residence-hall laundry to CSCPay Mobile, which has no public web status page. The old `washalertweb` host the community app scraped no longer resolves.
- **Real-time bus positions** — CityBus publishes GTFS static only. No GTFS-Realtime feed is listed on Transitland or the Mobility Database, and MyRide's backend is not public. `bus_next_departures` returns timetable times.
- **Parking garage availability** — Purdue Parking publishes no live space counts.
- **Grades, personal schedules, bursar** — behind the myPurdue login. Out of scope by design.
- **Library study-room availability** — LibCal's spaces API requires OAuth credentials the library would have to issue.
- **Laundry (again)** — CSCPay has no public read API of any kind.

Seat availability *was* on this list — it turned out Banner's class search needs no login, so `course_availability` now covers it.

## Adding a source

Each source is one file in `src/sources/` exporting a `registerX(server)` that calls `server.registerTool(...)`. Register it in `src/index.ts`. Use `getJSON()` from `src/lib/http.ts` so you inherit the timeout, User-Agent, and cache.

Rules of the road:

1. **Public data only.** Nothing that needs a Purdue login, and nothing about a specific individual.
2. **Verify the endpoint is live** before shipping it — `npm run smoke` hits every real upstream and is the test suite.
3. **Be a polite client.** Sensible cache TTL, no tight polling.

```bash
npm run build
npm run smoke   # live end-to-end check of all tools
```

## License

MIT

TDQS

A3.7/5.0

Scored across 29 tools

Disambiguation5/5

Each tool targets a distinct resource and action, such as bus stops versus departures, dining locations versus menus, and course search versus availability. The descriptions clearly differentiate overlapping domains like official events and club events.

Naming Consistency5/5

All 29 tools follow a consistent snake_case pattern with a domain-specific prefix (bus_, dining_, list_, search_, etc.) and a descriptive verb or noun. The naming is predictable and easy to interpret.

Tool Count3/5

With 29 tools, the set is on the high side, exceeding the typical 3-15 range for a well-scoped server. However, the server covers a broad range of university functions (dining, transportation, courses, events, athletics, etc.), and each tool serves a specific purpose, making the count borderline reasonable.

Completeness4/5

The server covers most common Purdue queries: dining, bus, courses, registration, events, athletics, news, calendar, library, weather, recwell, and student organizations. Minor gaps like parking or a campus map exist, but the surface is extensive and cohesive.