Skip to main content
Glama
kesavakantipudi

University Course Catalog MCP Server

University Course Catalog MCP Server

A Model Context Protocol (MCP) server that exposes a university's course catalog to LLM assistants. It gives AI agents the ability to search courses, inspect prerequisites, build prerequisite dependency graphs, and look up instructors — backed by a local SQLite database and fully containerized with Docker.

This is the backend for an AI-powered academic advisor: a model can query the server in real time to help students plan schedules, understand course dependencies, and find the right instructor.


Features

  • MCP Tools — four validated, LLM-callable functions:

    • search_courses — keyword search across titles, descriptions and codes, optionally filtered by department code.

    • get_prerequisites — the direct prerequisites of a course.

    • lookup_instructor — instructor contact details by name.

    • get_prerequisite_graph — the full transitive prerequisite dependency graph (computed with NetworkX) as an adjacency list.

  • MCP Resources — contextual text bodies the model can load:

    • course_descriptions — a formatted list of every course and its description.

    • department_directory — the full department list with their codes.

  • MCP Prompt Templates:

    • course_comparison_template — a reusable template ({{course_code_1}}, {{course_code_2}}) that guides structured course comparisons.

  • Data integrity — every tool input/output is validated with Pydantic schemas; data access uses SQLAlchemy (an ORM, which prevents SQL injection).

  • Persistence — SQLite database stored in ./data/catalog.db, mounted as a volume.

  • Containerized — one command: docker compose up.


Related MCP server: mcp-sqlite-manager

Project Structure

.
├── data/
│   ├── catalog.db            # Seeded SQLite database
│   └── seed_script/
│       └── seed.py           # Idempotent seeding script
├── src/
│   ├── __init__.py
│   ├── config.py             # Environment configuration
│   ├── database.py           # Engine + session helpers
│   ├── models.py             # SQLAlchemy ORM models
│   ├── schemas.py            # Pydantic validation contracts
│   ├── seed.py               # Shared seeding logic + seed data
│   ├── server.py             # MCP server: tools, resources, prompts
│   └── main.py               # Entry point (seeds + serves HTTP)
├── .env.example              # Documented environment variables
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md

Prerequisites: Docker with the Compose plugin.

# From the repository root
docker compose up --build

The service builds the image, maps port 8080, mounts ./data so the database persists, seeds the catalog on first start, and runs a health check.

To confirm the container is healthy:

docker compose ps

You should see mcp-server with a healthy status within about a minute.


Running Locally (without Docker)

Requires Python 3.11+.

# 1. Create and activate a virtual environment
python -m venv .venv
# Windows: .venv\Scripts\activate   |  macOS/Linux: source .venv/bin/activate

# 2. Install dependencies
pip install -r requirements.txt

# 3. (Optional) configure environment
# Copy .env.example to .env and adjust if needed.
# Default: DATABASE_URL=sqlite:///./data/catalog.db

# 4. Seed the database (idempotent — safe to run repeatedly)
python data/seed_script/seed.py

# 5. Start the server
python -m src.main

The server listens on http://localhost:8080.


Connecting an MCP Client

Point any MCP client at the Streamable HTTP endpoint:

http://localhost:8080/mcp

Example using the MCP Inspector:

npx @modelcontextprotocol/inspector
# URL: http://localhost:8080/mcp

You can also connect programmatically with the official mcp Python SDK:

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    async with streamablehttp_client("http://localhost:8080/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("get_prerequisites", {"course_code": "CS201"})
            print(result)

asyncio.run(main())

Tools

All tool inputs and outputs are validated with Pydantic. On unknown input the tools return a structured error, e.g. {"error": "Course not found"}.

search_courses

Searches the catalog by keyword (case-insensitive match against title, description and course code), optionally restricted to a department code.

Parameter

Type

Required

Description

query

string

yes

Keyword to search for.

department_code

string

no

Restrict results to a department (e.g. CS).

Output (success):

[{ "course_code": "CS101", "title": "Introduction to Programming", "credits": 3 }]

Returns [] when nothing matches.

get_prerequisites

Returns the direct prerequisites of a course.

Parameter

Type

Required

Description

course_code

string

yes

E.g. CS201.

Output (success):

{
  "course_code": "CS201",
  "prerequisites": [
    { "course_code": "CS102", "title": "Data Structures and Algorithms" }
  ]
}

Empty list when the course has no prerequisites; {"error": "Course not found"} for an unknown code.

lookup_instructor

Finds an instructor by full or partial name.

Parameter

Type

Required

Description

instructor_name

string

yes

E.g. Grace Hopper.

Output (success):

{
  "name": "Dr. Grace Hopper",
  "email": "grace.hopper@university.edu",
  "department_name": "Computer Science"
}

{"error": "Instructor not found"} when no match exists.

get_prerequisite_graph

Returns the full prerequisite dependency graph for a course — the course itself plus every course in its transitive prerequisite chain — as an adjacency list. The graph is built with NetworkX (source is a prerequisite for target).

Parameter

Type

Required

Description

course_code

string

yes

E.g. CS401.

Output (success):

{
  "nodes": [{ "id": "CS401" }, { "id": "CS201" }, { "id": "CS102" }, { "id": "CS101" }],
  "edges": [
    { "source": "CS101", "target": "CS102" },
    { "source": "CS102", "target": "CS201" },
    { "source": "CS201", "target": "CS401" }
  ]
}

Resources

course_descriptions

catalog://course_descriptions — a single plain-text body listing every course:

[CS101] Introduction to Programming: A foundational course on programming principles...
[CS102] Data Structures and Algorithms: ...

department_directory

catalog://department_directory — a directory of all departments:

Computer Science (CS)
Mathematics (MATH)
Physics (PHYS)

Prompt Template

course_comparison_template

A reusable template that guides the model to produce a structured comparison of two courses:

Create a table comparing the following two courses: {{course_code_1}} and {{course_code_2}}. Include columns for Course Code, Title, Credits, Description, and Prerequisites. ...


Example Natural Language Queries

Once connected to an assistant, the model can answer questions like:

  • "Which courses are about machine learning?"

  • "What do I need to take before CS401, and is there a chain of prerequisites?"

  • "Does MATH101 have any prerequisites?"

  • "Who teaches Database Systems and what is their email?"

  • "Compare CS301 and CS401 side by side."

  • "List all courses offered by the Physics department."

The model resolves these by calling the tools above and reading the resources.


Database

SQLite file: ./data/catalog.db. Schema:

Table

Columns

departments

id (PK), name, code (UNIQUE)

instructors

id (PK), name, email, office, department_id (FK)

courses

id (PK), course_code (UNIQUE), title, description, credits, instructor_id (FK), department_id (FK)

prerequisites

course_id (FK), prerequisite_id (FK) — many-to-many mapping

Seed data: 3 departments, 5 instructors, 10 courses (8 with prerequisites, including multi-level chains such as CS101 → CS102 → CS201 → CS401).

Re-seeding is automatic and idempotent — the server checks whether the catalog is empty before seeding, and the standalone script can be run anytime:

python data/seed_script/seed.py

Environment Variables

Variable

Default

Description

DATABASE_URL

sqlite:///./data/catalog.db

SQLite connection string (path inside container)

HOST

0.0.0.0

Interface the HTTP server binds to.

PORT

8080

Port the HTTP server listens on.

SERVER_NAME

University Course Catalog MCP Server

Name advertised during MCP initialize.

All variables are documented in .env.example.


Verification Checklist

  • search_courses, get_prerequisites, lookup_instructor, get_prerequisite_graph tools

  • course_descriptions, department_directory resources

  • course_comparison_template prompt ({{course_code_1}}, {{course_code_2}})

  • Pydantic-validated inputs/outputs and consistent {"error": ...} responses

  • Seeded data/catalog.db with required schema

  • Dockerfile, docker-compose.yml, .env.example, README.md

Related MCP Connectors

Related MCP Servers