Skip to main content
Glama

course-mcp

course-mcp is a local Python MCP server for referencing course files, upcoming work from a UMD Canvas calendar feed, and configured Piazza discussions.

The project combines safe local-file access with bounded, read-only course data from configured external sources.

Requirements

  • Python 3.10 through 3.14.

  • uv for dependency and environment management.

  • MCP Python SDK 1.x. This release uses the v1 low-level Server decorator API and declares mcp>=1.28.1,<2. MCP 2 requires an intentional server and test migration; see the official v1-to-v2 migration guide.

Related MCP server: CoursePack Local MCP Server

Current Features

  • Loads filesystem, calendar, and Piazza configuration lazily from the process environment or, when running from this checkout, its root .env file.

  • Restricts file access to paths inside ROOT_DIR.

  • Provides a FileService for safe file reads.

  • Provides a CourseService for course/file listing and searching.

  • Reads a private Canvas iCalendar feed without requiring a Canvas access token.

  • Reads configured Piazza discussions through the community-built, unofficial piazza-api package.

  • Exposes MCP tools:

    • list-courses: lists the top-level course directories under ROOT_DIR.

    • list-course-files: lists the direct files inside a course directory.

    • search-course-file: searches one UTF-8 text or text-extractable PDF file within a course using case-insensitive literal matching.

    • search-course: recursively searches eligible files throughout one course.

    • get-upcoming-work: returns assignments and events from a bounded date range in the Canvas calendar feed.

    • list-piazza-courses: lists configured Piazza courses accessible to the authenticated account.

    • list-piazza-posts: returns bounded recent post summaries without loading every full thread.

    • get-piazza-post: returns one bounded normalized Piazza thread.

    • search-piazza-posts: uses Piazza's feed search and returns bounded summaries.

search-course-file requires course_title, a course-relative file_path, and a non-empty keyword. It optionally accepts context_lines (default 3, maximum 20) and max_results (default 20, maximum 100). Search results are returned as JSON with matching line numbers and merged context excerpts. PDF results also identify the one-based page containing each excerpt. Scanned PDFs require OCR and are not supported.

search-course accepts the same keyword, context_lines, and max_results search controls, but applies max_results independently to every matching file. It searches direct course files and directories through depth 5. Hidden entries, symbolic links, and directories named venv, __pycache__, node_modules, dist, or build are skipped. Other unreadable or non-searchable files are also skipped without failing the course-wide search.

Both search tools return schema-validated results in MCP structuredContent. They also include the same result serialized as JSON TextContent for clients that do not yet consume structured tool output.

get-upcoming-work accepts optional start_date and end_date values in YYYY-MM-DD format. Both dates are inclusive; without them, the tool returns the seven calendar dates beginning today. It also accepts an optional literal query and max_results from 1 through 100. The result identifies whether its calendar data is stale, whether it was truncated, and how many calendar events could not be normalized through skipped_event_count. If a non-empty feed has no usable events, the tool returns stale cached data when available or a clear error instead of reporting a fresh empty calendar.

The calendar feed includes dated Canvas assignments and events, but it cannot report submission state, grades, or Canvas To Do items. Course hints and item types remain unknown unless they can be derived reliably from the feed.

Piazza tools are read-only and restricted to the course IDs explicitly listed in PIAZZA_COURSES. Post text is returned as bounded plain text and identified as untrusted user-generated content. The tools do not post, answer, edit, download attachments, expose rosters, or perform instructor operations.

list-piazza-posts accepts a limit from 1 through 25 (default 10) and an offset from 0 through 500 (default 0). Request additional pages sequentially only when the previous result has truncated: true. search-piazza-posts accepts a non-empty query of at most 200 characters and max_results from 1 through 25 (default 10). Piazza results are cached in memory for 60 seconds; after a refresh failure, an existing cached response may be returned with stale: true.

The Piazza integration depends on unpublished internal endpoints. It is useful for personal experimentation but is not an official Piazza API, may break when Piazza changes its website, and may be subject to Piazza or institutional usage rules. Keep request limits conservative and do not use it for bulk collection.

Project Layout

src/course_mcp/
  server.py              MCP server boundary
  config/
    env.py               shared lazy .env loading
    filesystem.py        course-root configuration
    calendar.py          Canvas calendar configuration
    piazza.py            Piazza credentials and course allowlist
  mcp_schemas/           MCP JSON Schema contracts
  mcp_tools/             MCP tool catalog
  models/
    calendar_item.py     normalized calendar data
    course.py            course directory data
    file.py              course file data
    piazza.py            bounded Piazza domain models
  services/
    calendar/
      feed_client.py     bounded private-feed loading and cache metadata
      parser.py          RFC 5545 parsing
      profiler.py        aggregate-only feed-shape diagnostics
      service.py         date filtering and result serialization
      factory.py         lazy configured calendar construction
    course/
      service.py         course-oriented operations
      factory.py         lazy course/file composition
    file/
      service.py         safe filesystem access
      pdf_extractor.py   page-oriented PDF text extraction
      factory.py         lazy configured file construction
    piazza/
      client.py          timeout-bound adapter around unofficial piazza-api
      normalizer.py      HTML cleanup and response normalization
      profiler.py        aggregate-only response-shape diagnostics
      service.py         allowlisting, limits, caching, and serialization
      factory.py         lazy configured Piazza construction
tests/
  config/                configuration tests
  mcp_schemas/           structured-output schema tests
  mcp_tools/             tool-catalog tests
  models/                model tests
  scripts/               development-script tests
  server/                MCP registration and dispatch tests
  services/
    calendar/            calendar client, parser, profiler, and service tests
    file/                filesystem and PDF extraction tests
    piazza/              Piazza client, normalizer, profiler, and service tests
  fixtures/              shared synthetic test data
skills/                  project-specific agent skills

Configuration

Each integration is optional and loaded only when one of its tools is called. When running from this checkout, create a private .env from the redacted template:

cp .env.example .env
chmod 600 .env

Configure only the integrations you want to use:

ROOT_DIR="/Users/markseeliger/Desktop/Classes/UMD"
CANVAS_ICAL_URL="https://umd.instructure.com/feeds/calendars/user_REDACTED.ics"
CALENDAR_TIMEZONE="America/New_York"
PIAZZA_EMAIL="student@example.edu"
PIAZZA_PASSWORD="replace-with-your-password"
PIAZZA_COURSES='{"abc123":"CMSC 132","xyz789":"CMSC 216"}'

ROOT_DIR must point to an existing directory. Each direct child directory is treated as a course. It is loaded only when a course-backed tool is called; importing the server does not require any source configuration.

Process environment variables take precedence over .env. If you install the wheel outside this checkout, pass configuration through the process environment because the repository-root .env convention applies only to checkout-based runs.

The current calendar integration is specific to UMD's ELMS-Canvas host. To obtain its URL, sign in to ELMS-Canvas, open the global Calendar, select Calendar Feed in the sidebar, and copy the URL field. The URL is a private credential: do not commit it, paste it into tickets or chat, or include it in logs and screenshots. This repository ignores .env.

For offline use, configure a downloaded snapshot instead of the URL:

CANVAS_ICAL_PATH="/absolute/private/path/calendar.ics"
CALENDAR_TIMEZONE="America/New_York"

Configure exactly one of CANVAS_ICAL_URL and CANVAS_ICAL_PATH. Calendar configuration is loaded only when get-upcoming-work is called, so the existing local course tools remain available without it. Live results are cached in memory for five minutes; after a refresh failure, a previous result may be returned with stale: true. CALENDAR_TIMEZONE defaults to America/New_York.

Piazza configuration is also lazy: the server and unrelated tools work without Piazza variables. PIAZZA_COURSES is a JSON mapping from Piazza course IDs to the names the agent should display. A course ID is the value after /class/ in a Piazza course URL. Every course-scoped call is rejected unless its ID appears in this mapping, and list-piazza-courses returns only configured courses that the authenticated account can access.

Store the Piazza password only in a private local environment or MCP process configuration. Never commit .env, paste credentials into prompts, or include them in logs. Accounts that require institution-only SSO may not support the email/password flow used by the unofficial package.

Run Locally

From this project directory:

uv sync --locked
uv run --frozen course-mcp

Because MCP servers run over stdio, they are usually launched by an MCP client rather than run directly by hand.

Install In Codex

Register the server with Codex:

codex mcp add course-mcp \
  --env ROOT_DIR=/Users/markseeliger/Desktop/Classes/UMD \
  -- uv --directory /Users/markseeliger/Desktop/Coding/create-python-server/course_mcp run --frozen course-mcp

This command passes ROOT_DIR explicitly. When launching from this checkout, configured Canvas and Piazza tools load their remaining variables from the root .env. Without those optional values, their tools report configuration errors while the filesystem tools continue to work.

Verify the registration:

codex mcp get course-mcp

Or refresh the registration with the project script:

ROOT_DIR=/Users/markseeliger/Desktop/Classes/UMD ./scripts/update_mcp_server.sh

If you change MCP tools, restart Codex or start a new Codex session so the tool list is reloaded.

Development

Inspect the configured Canvas calendar's structure without printing event values or the private feed URL:

uv run --frozen python scripts/inspect_canvas_calendar.py

The command reports only aggregate counts for usable/skipped events, date and time shapes, selected field presence, and coarse URL types. A zero event count means the feed is valid but does not yet provide representative data for course matching; it is not evidence that the calendar integration is broken.

After knowingly selecting the unofficial Piazza transport, inspect a bounded sample's structure without printing course IDs, post numbers, titles, bodies, names, or cookies:

uv run --frozen python scripts/inspect_piazza_shapes.py

The inspector loads at most five feed summaries and one full thread from the first configured course. Its output contains aggregate key/type/depth counts only. It still makes live calls to unpublished Piazza endpoints, so do not run it unless that access route is acceptable for your account.

Run the test suite:

uv run --frozen pytest -q

Run a compile check:

uv run --frozen python -m compileall -q src/course_mcp tests scripts

Verify the lockfile and package artifacts after dependency or packaging changes:

uv lock --check
uv build

CI also installs the built wheel into a clean Python 3.14 environment. This is important because locked development tests alone do not prove that the wheel's declared dependency ranges resolve to compatible releases.

Debug with MCP Inspector:

npx @modelcontextprotocol/inspector uv --directory /Users/markseeliger/Desktop/Coding/create-python-server/course_mcp run course-mcp

Available Tools

3 tools
list-course-filesA

List the files in a course. Agents should call list-courses first, then pass one of the returned course titles as course_title.

ParametersJSON Schema
NameRequiredDescriptionDefault
course_titleYesThe course directory title returned by list-courses.

TDQS

A4.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full behavioral burden. It only states the basic action without disclosing error handling, return format, or any side effects. For a read-only operation, minimal behavior disclosure is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences: first states purpose, second provides usage guideline. No wasted words, well-structured and front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (1 parameter, no output schema, no nested objects), the description is mostly complete. It covers the prerequisite workflow, but lacks output description or error behavior, which would improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with parameter description. The description adds value by linking course_title to the output of list-courses, providing essential context for correct parameter usage beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'List the files in a course.' This uses a specific verb ('list') and resource ('files in a course'), and distinguishes from siblings like list-courses (lists courses) and search-course-file (searches files).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs agents to 'call list-courses first, then pass one of the returned course titles as course_title.' This provides a clear prerequisite and usage workflow, avoiding misuse.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list-coursesA

List the courses the user is currently taking. Agents should use this MCP tool whenever they need to check which courses are available.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the core behavior (listing courses) but lacks additional context such as read-only nature, authentication requirements, or performance characteristics. Since no annotations are provided, the description carries full burden and could be more informative.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences, front-loaded with the action. Every sentence earns its place with no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters and a simple purpose, the description is sufficient. It covers the what and when. Could optionally mention the return format, but not critical for this straightforward tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has 0 parameters, so baseline is 4. The description does not need to add parameter details, and it correctly omits any parameter-related information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with 'List the courses the user is currently taking,' which is a specific verb and resource. It clearly distinguishes from siblings (list-course-files, search-course-file) by focusing on courses rather than files.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States 'Agents should use this MCP tool whenever they need to check which courses are available,' providing explicit guidance on when to use. No exclusion criteria or alternatives are mentioned, but siblings are clearly different.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search-course-fileA

Search for a literal keyword in one UTF-8 text or PDF file inside a course. Matching is case-insensitive.

ParametersJSON Schema
NameRequiredDescriptionDefault
keywordYesThe literal text to search for.
file_pathYesThe path relative to the course directory.
max_resultsNoMaximum matching lines to return.
course_titleYesThe course directory title returned by list-courses.
context_linesNoLines of context before and after each match.

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses case-insensitivity and file type limitations (UTF-8 text or PDF), but does not explain output behavior, error handling, or whether the tool is read-only. Adequate but not comprehensive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, well-structured sentence that frontloads the key action and constraints. Every word earns its place with no unnecessary repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 5 parameters and no output schema, the description covers the core search behavior but lacks details on return format, error cases (e.g., file not found, encoding issues), and pagination. Adequate for a simple search but incomplete for edge scenarios.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the description has minimal added value. It confirms literal keyword matching but does not elaborate on parameter semantics beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Search', the resource 'literal keyword in one UTF-8 text or PDF file inside a course', and specifies case-insensitive matching, making the tool's purpose distinct from sibling list tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for searching a single file by keyword but does not explicitly state when to use or avoid this tool, nor does it mention alternatives. The context is clear but lacks exclusion criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 3 tool updatesv0.1.0
    • First observedlist-course-files
    • First observedlist-courses
    • First observedsearch-course-file

TDQS

A4.2/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: listing courses, listing files within a course, and searching within a file. There is no overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern with lowercase and hyphens (list-courses, list-course-files, search-course-file), making them predictable and easy to understand.

Tool Count5/5

With 3 tools, the set is well-scoped for the server's purpose: listing courses, browsing files, and searching content. It is neither too sparse nor overloaded.

Completeness5/5

The tools cover the essential operations for a course material retrieval system: discover courses, list files, and search within files. No obvious gaps for the intended read-only use case.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers