Skip to main content
Glama
README.md
<div align="center">

<img src="docs/assets/mit-catalog-banner.png" alt="MIT Catalog MCP" width="900">

# ๐ŸŸ MIT Catalog โ†’ MCP

**Most of the web has no API, so agents can't use it.** This MCP server turns the
MIT Course Catalog (real site, no public API) into typed agent tools โ€” TinyFish
Fetch does the reading underneath.

[![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org/)
[![MCP](https://img.shields.io/badge/MCP-5%20tools-34D399)](https://modelcontextprotocol.io/)
[![TinyFish Fetch](https://img.shields.io/badge/TinyFish-Fetch-34D399)](https://tinyfish.ai/)
[![Tests](https://github.com/Ehsas317/mit-catalog-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Ehsas317/mit-catalog-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

TinyFish Student Bounty **Site to MCP** (Drop 002) ยท 3+ endpoints โ†’ **150 pts tier**

</div>

## ๐Ÿ“‘ Contents

- [๐Ÿš€ Installing / Getting started](#-installing--getting-started)
- [๐ŸŽฏ Usage](#-usage)
- [๐Ÿค– Use with AI (MCP)](#-use-with-ai-mcp)
- [๐Ÿ›  Tools](#-tools)
- [๐Ÿ—๏ธ How it works](#๏ธ-how-it-works)
- [โœ… Live proof](#-live-proof)
- [๐Ÿ“ Project structure](#-project-structure)
- [๐Ÿค Contributing](#-contributing)
- [๐Ÿ”— Links](#-links)
- [๐Ÿ“œ Licensing](#-licensing)

## ๐Ÿš€ Installing / Getting started

```shell
pip install -r requirements.txt
export TINYFISH_API_KEY=...   # https://agent.tinyfish.ai/api-keys
mit-catalog health              # all 4 tools live; status: ok
```

What actually happens โ†‘: each tool fetches the live MIT catalog page via TinyFish
Fetch and parses it into typed JSON. No API key for MIT itself โ€” the key is only
for TinyFish.

## ๐ŸŽฏ Usage

```shell
mit-catalog subjects --limit 6
mit-catalog courses 18 --limit 3
mit-catalog search "computer science" --limit 3
mit-catalog degree electrical-engineering-computing-course-6-5
```

## ๐Ÿค– Use with AI (MCP)

```json
{
  "mcpServers": {
    "mit-catalog": {
      "command": "mit-catalog-mcp",
      "env": { "TINYFISH_API_KEY": "..." }
    }
  }
}
```

Tools: `list_subjects` ยท `list_courses(subject)` ยท `search_degrees(query)` ยท
`get_degree_info(url_or_slug)` ยท `health_check()` โ€” all return typed JSON, and the
CLI above is the same code without an MCP client.

## ๐Ÿ›  Tools

| Tool                    | Input                 | Output                                          | Real call                                            |
| ----------------------- | --------------------- | ----------------------------------------------- | ---------------------------------------------------- |
| `list_subjects(limit)`  | โ€”                     | `[{course_number, name}]`                       | `subjects --limit 6`                                 |
| `list_courses(subject)` | `"18"`, `"7"`, `"6"`โ€ฆ | `[{code,title,units,term,prereq,description}]`  | `courses 18 --limit 3`                               |
| `search_degrees(query)` | `"computer science"`  | `[{title,url,snippet}]`                         | `search "computer science"`                          |
| `get_degree_info(slug)` | slug or URL           | `{title,overview,courses:[{code,title,units}]}` | `degree electrical-engineering-computing-course-6-5` |
| `health_check()`        | โ€”                     | `{status, details[4]}`                          | `health`                                             |

Multi-step agent use works because the tools compose: `search_degrees` returns a URL
or slug, and `get_degree_info` consumes it in the next turn.

## ๐Ÿ—๏ธ How it works

```mermaid
flowchart LR
    subgraph tinyfish["TinyFish Fetch (underneath)"]
      F[fetch markdown<br/>catalog.mit.edu]
    end
    F --> P[Pure parsers<br/>parsers.py]
    P --> T1[list_subjects] & T2[list_courses] & T3[search_degrees] & T4[get_degree_info]
    T1 & T2 & T3 & T4 --> MCP[MCP tools + CLI<br/>typed JSON]
    MCP --> H[health_check<br/>fails loudly if MIT redesigns]
```

- **TinyFish Fetch** does the reading/clicking for every tool โ€” remove it and nothing works.
- **Pure parsers** (`parsers.py`) are offline-tested (`make test`, 7 tests).
- **Retry with backoff** โ€” one flaky fetch doesn't break a tool call.
- **Health check** runs all 4 tools against known inputs and reports `ok` / `broken` with per-tool detail.

## โœ… Live proof (2026-11-08)

`health` โ†’ `status: ok`, 4/4 tools green. Examples in [`examples/`](examples/):

- `subjects.json` โ€” 16 Aeronautics, 21A anthropology, โ€ฆ
- `courses-18.json` โ€” `18.01 Calculus, 5-0-7 units, Fall/Spring`
- `degrees.json` โ€” CS degree search hits with URLs
- `degree.json` โ€” EECS 6-5 chart: `6.100A Intro to CS Programming in Python`, โ€ฆ

## ๐Ÿ“ Project structure

```
mitcatalog/
โ”œโ”€โ”€ __init__.py   # version + catalog base URL
โ”œโ”€โ”€ cli.py        # same tools, no MCP client needed
โ”œโ”€โ”€ client.py     # TinyFish Fetch wrapper (retry/backoff)
โ”œโ”€โ”€ parsers.py    # pure offline-tested parsers
โ”œโ”€โ”€ server.py     # MCP tools (FastMCP)
โ”œโ”€โ”€ health.py     # 4-tool health check
mitcatalog.py     # shim (python3 mitcatalog.py โ€ฆ)
tests/            # parsers + health (mocked)
examples/         # live JSON outputs
```

## ๐Ÿค Contributing

Issues and PRs welcome. Parser changes need a test; new tools need a health-check entry.

## ๐Ÿ”— Links

- Repository: https://github.com/Ehsas317/mit-catalog-mcp
- Site wrapped: https://catalog.mit.edu (no public API โ€” that is the point)
- TinyFish docs: https://docs.tinyfish.ai ยท API keys: https://agent.tinyfish.ai/api-keys

## ๐Ÿ“œ Licensing

MIT โ€” see [`LICENSE`](LICENSE).

---

<p align="right"><a href="#contents">Back to top โ†‘</a></p>