Skip to main content
Glama
scottylabs-mcp

scottylabs-mcp

by shivendoo123

scottylabs-mcp

A Model Context Protocol (MCP) server that wraps the ScottyLabs CMU Courses API (course-tools.apis.scottylabs.org). It lets a Claude session search the CMU course catalog and pull full course details from inside a chat.

Tools

Tool

Description

Auth

search_courses

Keyword/department search over the catalog. Paginated.

none

get_course

Full details for a single course (description, prereqs, schedules, etc.).

none

get_course_schedules

Just the schedules for a course — smaller payload than get_course.

none

get_instructor_schedules

Schedules taught by a given instructor.

none

get_requisites

Prereqs / postreqs / AND-of-ORs prereq relations for a course.

none

get_geneds

Gen-ed-eligible courses for a school (SCS, CIT, MCS, Dietrich).

none

search_instructors

Discover exact instructor name strings for the by-instructor tools.

none

get_course_fces

FCE summary for a course (aggregates + 5 recent rows; include_all=True for full history).

gated

get_instructor_fces

FCE summary for an instructor across courses (same shape as above).

gated

Wraps the public read endpoints of course-tools.apis.scottylabs.org (the backend behind cmucourses.com and courses.scottylabs.org).

Related MCP server: Brown Courses MCP Server

Setup

Prerequisites

  • uv — provides the uvx runner used below. Install once and you're set.

1. Wire it into Claude Desktop

Edit claude_desktop_config.json:

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

Create the file if it doesn't exist. Add the scottylabs-cmu-courses entry under mcpServers (merge with existing entries if present):

{
  "mcpServers": {
    "scottylabs-cmu-courses": {
      "command": "uvx",
      "args": ["scottylabs-mcp"]
    }
  }
}

uvx fetches scottylabs-mcp from PyPI and runs it in an ephemeral environment — no checkout, no manual install. Subsequent runs hit the uv cache.

2. Restart Claude Desktop

Quit fully (system tray → Quit, not just close window) and reopen. In a new chat, click the tools icon — you should see all 9 scottylabs-cmu-courses tools listed.

3. Try it in a chat

Public-endpoint examples (no auth required):

  • "Search CMU courses for machine learning."

  • "Show me the prereqs for 15-213."

  • "What is Iliano Cervesato teaching this semester?"

  • "List SCS gen-eds tagged Science."

4. (Optional) Enable FCE tools

get_course_fces and get_instructor_fces need a Clerk session JWT — see Auth (FCE tools only) below.

Claude Code instead of Desktop?

claude mcp add scottylabs-cmu-courses --scope user -- uvx scottylabs-mcp

Or paste the same mcpServers block into ~/.claude.json under your project entry.

Local development

If you're hacking on the server, point Claude at your checkout instead:

{
  "mcpServers": {
    "scottylabs-cmu-courses": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/scottylabs-mcp",
        "scottylabs-mcp"
      ]
    }
  }
}

Forward slashes in the path are JSON-safe on Windows. Restart Claude Desktop to pick up code changes.

Troubleshooting

Symptom

Cause / fix

Tools don't appear after restart

Check Claude Desktop's MCP log: %APPDATA%\Claude\logs\mcp*.log (Windows) or ~/Library/Logs/Claude/mcp*.log (macOS). Most common: uv not on PATH for the GUI app.

uvx / uv not found in MCP log

Claude Desktop's GUI may not see your shell PATH. Run where uvx (Windows) / which uvx (Unix) and put the absolute path in command, e.g. "C:/Users/you/AppData/Local/Python/pythoncore-3.12-64/Scripts/uvx.exe".

FCE tool 401s after running the auth helper

Clerk sessions expire (~7 days). Re-run scottylabs-mcp-auth.

Want to verify auth state

uv run scottylabs-mcp-auth --show — prints token-file path, presence, and env-var status.

Need to reset the saved token

uv run scottylabs-mcp-auth --remove.

Anything else broken

uv run python scripts/smoke.py from the project root validates all 13 cases against the live API without involving Claude.

Configuration

Env var

Default

Notes

SCOTTYLABS_API_BASE

https://course-tools.apis.scottylabs.org

Override for local backend dev.

SCOTTYLABS_AUTH_TOKEN

(unset)

Static Clerk session JWT for FCE tools (advanced; bypasses cookie refresh). See Auth section.

Auth (FCE tools only)

get_course_fces and get_instructor_fces POST to /fces, which runs through Clerk's isUser middleware. The production backend has auth enabled (verified via the smoke harness — calls without a token come back 401), so the MCP server needs to mint Clerk session JWTs.

Clerk session JWTs only live ~5 minutes, so the MCP server doesn't store a JWT directly. Instead, it stores the long-lived __client cookie (~7-day TTL) and exchanges it for a fresh JWT on each FCE call via Clerk's Frontend API. Tokens are cached in-memory until shortly before they expire.

uv run --directory scottylabs-mcp scottylabs-mcp-auth

The helper:

  1. Opens https://www.cmucourses.com in your browser.

  2. Walks you through copying the __client cookie from DevTools. After sign-in you'll be redirected to www.courses.scottylabs.org — that's where the cookie lives (Application → Cookies → https://www.courses.scottylabs.org → __client → copy the Value). If it isn't there, also check https://clerk.scottylabs.org.

  3. Saves the cookie to a per-user config file (%APPDATA%\scottylabs-mcp\token on Windows, ~/.config/scottylabs-mcp/token elsewhere; 0o600 on Unix).

The MCP server picks it up automatically and refreshes JWTs as needed. Manage it with:

scottylabs-mcp-auth --show     # check where it's stored / whether it's set
scottylabs-mcp-auth --remove   # delete the saved cookie

Re-run the helper when:

  • FCE calls start returning auth errors (cookie expired, ~7 days).

  • You sign out of cmucourses.com (cookie revoked).

Option 2: env var (advanced)

Set SCOTTYLABS_AUTH_TOKEN to a session JWT directly. The MCP server uses this value verbatim — no refresh — so you'll need to keep it under 5 minutes old. Mostly useful for one-off testing. This env var wins over the saved cookie when both are set.

What happens without auth

The first FCE call returns a ScottyLabsError whose message tells the user to run scottylabs-mcp-auth. No silent failure.

The other endpoints (search, get-course, schedules, requisites, geneds, instructors) are public and need no auth.

Smoke test

scripts/smoke.py calls the tool functions directly against the live API, bypassing MCP transport. Useful for sanity checks during development.

uv run --directory scottylabs-mcp python scripts/smoke.py

Expects Summary: 13/13 passed.

Layout

.
├── LICENSE
├── pyproject.toml
├── README.md
├── scripts/
│   └── smoke.py             # live-API harness, bypasses MCP transport
├── tests/
│   └── test_summarizer.py   # offline unit tests, run by CI
└── src/
    └── scottylabs_mcp/
        ├── __init__.py
        ├── __main__.py      # `python -m scottylabs_mcp`
        ├── auth_helper.py   # `scottylabs-mcp-auth` console script
        ├── clerk_auth.py    # Clerk Frontend API: cookie -> fresh JWT, with cache
        ├── client.py        # shared httpx.AsyncClient + error mapping
        ├── models.py        # pydantic types mirroring the upstream schema
        ├── server.py        # FastMCP app, tool registrations, entry point
        └── tools.py         # tool implementations (importable for tests)

Credits

Huge thanks to ScottyLabs — the student organization at Carnegie Mellon University that builds and maintains the Course Tool (cmucourses.com, courses.scottylabs.org) and its public backend API. This MCP server is just a thin Python wrapper around their work; all the data, scrapers, and infrastructure that make it useful are theirs.

Upstream repo: github.com/ScottyLabs/cmucourses. Consider contributing to or supporting them directly.

Available Tools

9 tools
get_courseA

Fetch full details for a single CMU course, including current schedules.

Use this when you already have the exact course ID. Accepts "15-122" or "15122". Returns description, units, prereqs/coreqs, cross-listings, and upcoming schedule with lectures/sections.

Args: course_id: CMU course ID, e.g. "15-122" or "21-241".

Returns: Course object with name, department, desc, units, prereqs, coreqs, crosslisted, schedules, etc.

Inputs: course_id (required). Returns: courseID, name, department, desc, +8 more

get_course_fcesA

Fetch Faculty Course Evaluations (FCE) ratings for a course.

Use this when the user asks about course difficulty, hours per week, or instructor ratings. Returns a compact summary: aggregates over all semesters plus the 5 most recent entries. Set include_all=True only when the user asks for the full history.

Auth: requires the env var SCOTTYLABS_AUTH_TOKEN to be set to a valid Clerk JWT. If the upstream backend has auth disabled, the empty token will work too.

Args: course_id: CMU course ID, e.g. "15-122". include_all: Default False — entries holds the 5 most recent rows and truncated flags whether anything was cut. Pass True to populate entries with every row. Aggregates (avg_hrs_per_week, avg_rating, years_covered) always reflect the full dataset.

Returns: FCESummary with entry_count, years_covered, avg_hrs_per_week, avg_rating, entries, truncated.

Inputs: course_id (required), include_all. Returns: Aggregate view of FCE entries for a course or instructor. The default response…

get_course_schedulesA

Fetch lecture/section schedules for a single course.

Prefer this over get_course when you only need meeting times — the response is much smaller. Returns one schedule per offered semester (typically several across recent years).

Args: course_id: CMU course ID, e.g. "15-122".

Returns: List of Schedule objects.

Inputs: course_id (required). Returns: result

get_genedsA

List gen-ed-eligible courses for a CMU school.

Use this when the user wants courses satisfying a gen-ed requirement for their college.

Args: school: Exactly one of "SCS" (School of Computer Science), "CIT" (engineering), "MCS" (sciences), or "Dietrich" (humanities and social sciences).

Returns: List of Gened objects, each with course info, gen-ed tags, and a startsCounting/stopsCounting window.

Inputs: school (required). Returns: result

get_instructor_fcesA

Fetch Faculty Course Evaluations (FCE) ratings for an instructor.

Use this for a professor's teaching record across courses, or to compare instructors. Returns a compact summary by default; aggregates span every semester they've taught.

Auth: requires SCOTTYLABS_AUTH_TOKEN (see get_course_fces).

Args: instructor: Instructor name, exact-match (use search_instructors). include_all: See get_course_fces. Default False keeps the response tight.

Returns: FCESummary (see get_course_fces).

Inputs: instructor (required), include_all. Returns: Aggregate view of FCE entries for a course or instructor. The default response…

get_instructor_schedulesA

Fetch all schedules taught by the given instructor.

Use this for "what is X teaching?" or to find classes by professor. Pass the instructor name exactly as it appears in the course data — use search_instructors first to discover the canonical spelling.

Args: instructor: Instructor name, exact-match. Example: "Iliano Cervesato".

Returns: List of Schedule objects across all courses and semesters.

Inputs: instructor (required). Returns: result

get_requisitesA

Fetch the prerequisite / postrequisite graph for a course.

Use this for "what do I need before X?" or "what unlocks after X?" questions. Returns:

  • prereqs: required courses (flat list).

  • prereqRelations: AND-of-ORs decoding (outer AND, inner OR).

  • postreqs: courses that list this one as a prereq.

Args: course_id: CMU course ID, e.g. "15-213".

Returns: Object with prereqs, prereqRelations, postreqs.

Inputs: course_id (required). Returns: Prereq/postreq graph for a single course. `prereqRelations` is an AND-of-ORs de…

search_coursesA

Keyword-search the CMU course catalog.

Use this when the user asks about a topic, department, or partial course name and you don't already have an exact course ID. The query is matched against name, department, description, and prereq string.

Args: query: Free-text search. Examples: "machine learning", "discrete math", "Computer Science", "15-122". Department codes work too. page: 1-indexed page number. The backend caps page size at 10.

Returns: Object with totalDocs, totalPages, page, and docs — a list of courses with courseID, name, department, desc, units, prereqs, etc.

Inputs: query (required), page. Returns: totalDocs, totalPages, page, docs

search_instructorsA

Look up CMU instructor names from the FCE roster.

Use this to discover the exact spelling/casing of an instructor before calling get_instructor_fces or get_instructor_schedules. Names are exact-match — pass them verbatim downstream.

Args: query: Optional case-insensitive substring filter (e.g. "cervesato"). limit: Max results, default 50, hard cap 200.

Returns: List of instructor name strings.

Inputs: query, limit. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.7/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool has a distinct purpose clearly outlined in its description. Even tools that return related data (e.g., get_course and get_course_schedules) have explicit guidance on when to use each, eliminating ambiguity.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun snake_case pattern with 'get_' for retrieval and 'search_' for search operations. No mixing of conventions.

    Tool Count5/5

    With 9 tools, the server is well-scoped for the domain of CMU course information. Each tool covers a specific query need without being redundant or excessive.

    Completeness5/5

    The tool surface covers all major aspects of course information: details, schedules, evaluations, instructors, prerequisites, gen-eds, and search. There are no obvious gaps for the intended use case.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers