# brightspace-mcp [![CI](https://github.com/JhostinAleck/brightspace-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/JhostinAleck/brightspace-mcp/actions/workflows/ci.yml) [![Docs](https://github.com/JhostinAleck/brightspace-mcp/actions/workflows/docs.yml/badge.svg)](https://jhostinaleck.github.io/brightspace-mcp/) [![npm version](https://img.shields.io/npm/v/brightspace-mcp.svg)](https://www.npmjs.com/package/brightspace-mcp) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE) [![Node.js](https://img.shields.io/node/v/brightspace-mcp.svg)](./package.json) πŸ“– **[Full documentation site β†’](https://jhostinaleck.github.io/brightspace-mcp/)** MCP server for D2L Brightspace. Gives Claude (and any MCP-compatible client) access to your courses, grades, assignments, content, calendar, and more β€” with multi-strategy authentication, full MFA support, and production-grade resilience built in. --- ## Quick start ```bash npx brightspace-mcp@latest setup # interactive wizard (recommended for first time) ``` The interactive wizard handles everything: base URL, auth strategy, MFA, credential storage, and auto-registration with Claude Desktop / Cursor / Windsurf. For CI pipelines or DevContainers with no TTY, use the non-interactive `init` command instead: ```bash npx brightspace-mcp@latest init \ --base-url https://yourschool.brightspace.com \ --strategy api_token \ --token-ref env:BRIGHTSPACE_API_TOKEN ``` --- ## Documentation Deep-dive guides live in [`docs/`](./docs/) β€” start with [`docs/README.md`](./docs/README.md). | Topic | Doc | |---|---| | Setup walkthrough | [`docs/setup-guide.md`](./docs/setup-guide.md) | | Auth strategies | [`docs/auth-strategies.md`](./docs/auth-strategies.md) | | Known-good presets (Microsoft AAD, etc.) | [`docs/presets.md`](./docs/presets.md) | | Write operations (submit, post, mark) | [`docs/writes.md`](./docs/writes.md) | | MCP tools reference | [`docs/tools.md`](./docs/tools.md) | | MCP Resources + Prompts | [`docs/tools.md#mcp-resources`](./docs/tools.md#mcp-resources) | | Troubleshooting | [`docs/troubleshooting.md`](./docs/troubleshooting.md) | | Architecture (DDD) | [`docs/architecture.md`](./docs/architecture.md) | | Register with MCP clients | [`docs/clients.md`](./docs/clients.md) | For AI assistants and contributors, [`AGENTS.md`](./AGENTS.md) is a one-page map of the repo. --- ## Table of contents - [Installation](#installation) - [Authentication strategies](#authentication-strategies) - [MFA strategies](#mfa-strategies) - [Configuration reference](#configuration-reference) - [Output: timezone and language](#output-timezone-and-language) - [Redis cache](#redis-cache) - [Write operations](#write-operations) - [Available tools](#available-tools) - [MCP Resources](#mcp-resources) - [MCP Prompts](#mcp-prompts) - [TUI dashboard](#tui-dashboard) - [Register with an MCP client](#register-with-an-mcp-client) - [CLI reference](#cli-reference) - [Docker](#docker) --- ## Installation ### npx (recommended β€” no install needed) ```bash npx brightspace-mcp@latest setup # first-time wizard npx brightspace-mcp@latest serve # run the server ``` ### Global install ```bash npm install -g brightspace-mcp brightspace-mcp setup brightspace-mcp serve ``` ### From source ```bash git clone https://github.com/JhostinAleck/brightspace-mcp.git cd brightspace-mcp npm install && npm run build node build/cli/main.js serve ``` **Requirements**: Node.js β‰₯ 20. --- ## Authentication strategies Pick the strategy that matches your Brightspace setup. Run `npx brightspace-mcp@latest setup` and it will walk you through the right one. ### API Token (simplest) Requires a Valence API token from your Brightspace admin panel. ```yaml profiles: my_school: base_url: https://school.brightspace.com auth: strategy: api_token api_token: token_ref: env:BRIGHTSPACE_API_TOKEN ``` ```bash export BRIGHTSPACE_API_TOKEN="your-token" npx brightspace-mcp@latest serve ``` ### Headless (username + password) Automates HTTP-level login β€” no browser window. Supports all MFA strategies including **Duo Push**. ```yaml profiles: my_school: base_url: https://school.brightspace.com auth: strategy: headless headless: login_url: https://school.brightspace.com/d2l/login username_ref: env:BRIGHTSPACE_USERNAME password_ref: env:BRIGHTSPACE_PASSWORD mfa: strategy: duo_push # or: totp, manual_prompt, none duo_push: {} # uses defaults: poll every 1s, timeout 120s ``` ### Browser (Playwright) Launches a headless Chromium instance and automates the login UI. Best for SSO flows (Microsoft Azure AD, SAML) where the login page has complex JavaScript. ```bash npm install playwright && npx playwright install chromium ``` ```yaml auth: strategy: browser browser: login_url: https://school.brightspace.com/d2l/login headless: true username_ref: env:BRIGHTSPACE_USERNAME password_ref: env:BRIGHTSPACE_PASSWORD selectors: username: "#i0116" password: "#i0118" submit: "#idSIButton9" password_submit: "#idSIButton9" mfa_input: "#idTxtBx_SAOTCC_OTC" mfa_submit: "#idSubmit_SAOTCC_Continue" post_login: "d2l-labs-navigation" mfa: strategy: totp totp: secret_ref: env:BRIGHTSPACE_TOTP_SECRET ``` The setup wizard includes a **Microsoft SSO preset** that fills all selectors automatically. ### Session Cookie Paste the D2L session cookies from your browser's DevTools. Useful when other strategies are blocked. ```yaml auth: strategy: session_cookie session_cookie: cookie_ref: env:BRIGHTSPACE_COOKIE session_ttl_seconds: 3600 ``` ```bash # Cookie format: "d2lSessionVal=XXX; d2lSecureSessionVal=YYY" export BRIGHTSPACE_COOKIE="d2lSessionVal=...; d2lSecureSessionVal=..." ``` --- ## MFA strategies | Strategy | When to use | |---|---| | `none` | No MFA on your account | | `totp` | Authenticator app (Google Authenticator, Authy, etc.) | | `duo_push` | Duo Security β€” server polls for mobile approval automatically | | `manual_prompt` | Any TOTP/OTP β€” server pauses and asks you to paste the code | ### TOTP example ```yaml mfa: strategy: totp totp: secret_ref: env:BRIGHTSPACE_TOTP_SECRET # base32 secret from QR code setup digits: 6 # 6 or 8 period: 30 # seconds algorithm: SHA1 # SHA1, SHA256, or SHA512 ``` ### Duo Push example ```yaml mfa: strategy: duo_push duo_push: poll_interval_ms: 1000 # how often to check (default: 1000) timeout_ms: 120000 # give up after this many ms (default: 120000) ``` --- ## Configuration reference Full config file (`~/.brightspace-mcp/config.yaml`): ```yaml default_profile: my_school profiles: my_school: base_url: https://school.brightspace.com auth: strategy: api_token # api_token | browser | headless | session_cookie | oauth api_token: token_ref: env:BRIGHTSPACE_API_TOKEN session: cache_backend: memory # memory | file | redis preemptive_refresh_seconds: 300 output: tz: America/Bogota # IANA timezone; default: auto-detected from system locale: es-419 # en-US | es-419 | pt-BR | fr-CA; default: auto-detected format: markdown # markdown (default) | plain include_meta_footer: true logging: level: info # debug | info | warn | error writes: enabled: false dry_run: false # Optional β€” required when session.cache_backend: redis redis: url: redis://localhost:6379 key_prefix: "brightspace:" ``` ### Credential references Secret values are never stored in plain text. Use `ref:` notation to point to the actual value: | Prefix | Example | Description | |---|---|---| | `env:NAME` | `env:BRIGHTSPACE_API_TOKEN` | Read from environment variable | | `keychain:service/account` | `keychain:brightspace-mcp/token` | OS keychain (macOS Keychain, GNOME Keyring, Windows Credential Manager) | | `file:label` | `file:api_token` | Encrypted file (`~/.brightspace-mcp/credentials.enc`, AES-256-GCM) | --- ## Output: timezone and language All tool responses are formatted in your configured timezone and language. ```yaml output: tz: America/Bogota # IANA name; default: auto-detected from system locale: es-419 # en-US | es-419 | pt-BR | fr-CA; default: auto-detected format: markdown # markdown (default) | plain include_meta_footer: true ``` Run `brightspace-mcp setup` and choose your timezone and language. Or set it in `~/.brightspace-mcp/config.yaml`. --- ## Redis cache When running multiple instances or want cache persistence across restarts, enable Redis: **1. Add the `redis` section to config:** ```yaml redis: url: redis://localhost:6379 key_prefix: "brightspace:" profiles: my_school: session: cache_backend: redis ``` **2. Install ioredis (optional dependency):** ```bash npm install ioredis ``` **3. Start Redis and the server:** ```bash docker run -d -p 6379:6379 redis:7-alpine npx brightspace-mcp@latest serve ``` The domain cache (courses, grades, assignments, etc.) automatically uses Redis as persistent layer when the `redis:` section is present in config. Session tokens are stored with TTL derived from the token expiry. --- ## Write operations Write tools (`submit_assignment`, `post_discussion_reply`, `mark_announcement_read`) are disabled by default and require two separate opt-ins: **1. Config file:** ```yaml writes: enabled: true dry_run: false # set true to preview without mutating D2L ``` **2. CLI flag:** ```bash npx brightspace-mcp@latest serve --enable-writes ``` All write operations: - Require a client-supplied `idempotency_key` (8–128 chars). Repeat calls with the same key return the cached response without re-executing. - Emit a WARN-level audit log line with correlation ID, tool name, and redacted args. - Respect `dry_run: true` to return a preview response without touching D2L. --- ## Available tools ### Read tools (always available) | Tool | Description | |---|---| | `check_auth` | Verify authentication and show the active user identity | | `list_my_courses` | List all enrolled courses | | `get_my_grades` | Get grades for a course | | `get_assignments` | List assignments and dropbox folders | | `get_assignment_files` | Download and read instructor-posted assignment files | | `get_upcoming_due_dates` | List assignments due in the next N days | | `get_feedback` | Read instructor feedback on submitted assignments | | `get_syllabus` | Fetch the course syllabus | | `get_course_content` | Browse modules and topics (includes topic IDs) | | `get_topic_file` | Download and read a content topic file (DOCX, PDF, HTML, plain text) | | `get_announcements` | List course announcements | | `get_discussions` | Browse discussion forums and threads | | `get_calendar_events` | List calendar events in a date range | | `get_roster` | Get the full course roster | | `get_classlist_emails` | Get classmate email addresses | | `get_my_groups` | List group enrollments per course with member rosters | | `list_quizzes` | List quizzes with attempt counts, time limits, due dates | | `get_quiz_attempts` | Your attempts on a quiz with scores and timestamps | | `list_notifications` | User activity feed (announcements, due-date reminders, grade releases) | | `search_course` | Full-text search across content, announcements, and discussions | | `get_audit_log` | Local history of write operations (correlation IDs, redacted args) | | `get_diagnostics` | Show cache stats, circuit breaker state, and version info | | `clear_cache` | Clear memory and persistent cache backends | ### Write tools (require `--enable-writes`) | Tool | Description | |---|---| | `submit_assignment` | Upload a file to a Brightspace Dropbox folder | | `post_discussion_reply` | Reply to a discussion thread | | `mark_announcement_read` | Mark an announcement as read | --- ## MCP Resources Four stable URIs for Brightspace content (readable by any MCP client via `resources/read`): | URI | Content | |---|---| | `brightspace://{courseId}/syllabus` | Course syllabus, HTML stripped | | `brightspace://{courseId}/content/topics/{topicId}` | Topic file (text extracted from PDF, or base64 fallback) | | `brightspace://{courseId}/assignments/{assignmentId}/files` | All assignment attachments as text | | `brightspace://{courseId}/announcements/{announcementId}` | Announcement text | Obtain IDs from tools like `list_my_courses`, `get_assignments`, `get_announcements`. --- ## MCP Prompts Four pre-built prompt templates visible in your MCP client's prompt picker: | Prompt | Arguments | Purpose | |---|---|---| | `weekly_briefing` | none | 7-day overview: due dates, announcements, recent grades | | `grade_audit` | `course_id?` | Grade analysis + what you need to pass | | `study_planner` | `days_ahead?` (default 7) | Study plan from due dates and calendar | | `course_summary` | `course_id` (required) | Full course overview | --- ## TUI dashboard ```bash brightspace-mcp tui # launch full-screen terminal dashboard brightspace-mcp tui --profile work # use a specific profile ``` Full-screen interactive terminal UI (Ink + React). Six tabs navigated with `Tab` / `β†’` / `←`: | Tab | Contents | |-----|----------| | **Inicio** | 3-column dashboard: upcoming assignments, 7-day calendar agenda, recent announcements | | **Cursos** | Live-search course list β†’ drill into a course β†’ sub-tabs Tareas / Notas / Anuncios | | **Calendario** | 30-day event agenda across all active courses | | **Config** | Profile summary, field-by-field form editor (dropdowns from schema) or `$EDITOR` | | **CachΓ©** | Hit rate, miss count, clear button | | **Logs** | Audit log (last 50 entries, `/` to filter by tool name) | Press `Ctrl+C` to exit. --- ## Register with an MCP client See [`docs/clients.md`](./docs/clients.md) for Claude Desktop, Cursor, and Windsurf snippets, or run `brightspace-mcp setup` which auto-detects and registers for you. **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "brightspace": { "command": "npx", "args": ["--yes", "brightspace-mcp@latest", "serve"], "env": { "BRIGHTSPACE_CONFIG": "/Users/you/.brightspace-mcp/config.yaml" } } } } ``` --- ## CLI reference ``` npx brightspace-mcp@latest setup Interactive first-time setup wizard (detects system language) npx brightspace-mcp@latest init [flags] Non-interactive config writer (CI/scripts, no TTY) npx brightspace-mcp@latest serve Start the MCP server (stdio transport) npx brightspace-mcp@latest serve --enable-writes Start with write tools enabled npx brightspace-mcp@latest tui Full-screen terminal dashboard (Ink TUI) npx brightspace-mcp@latest auth Re-authenticate and test the config npx brightspace-mcp@latest record-auth Open browser for manual login, capture session cookies npx brightspace-mcp@latest doctor End-to-end smoke test: config β†’ auth β†’ API β†’ list_my_courses npx brightspace-mcp@latest profile list List profiles (* marks the default) npx brightspace-mcp@latest profile use Switch the default profile npx brightspace-mcp@latest config show Print config (secrets redacted) npx brightspace-mcp@latest config show --resolved Show all secret refs as [redacted] npx brightspace-mcp@latest config validate Validate config schema without running npx brightspace-mcp@latest config set Edit a nested config value npx brightspace-mcp@latest cache clear Clear memory + file/Redis cache npx brightspace-mcp@latest cache clear --context Clear a specific cache context npx brightspace-mcp@latest upgrade Upgrade brightspace-mcp to the latest version ``` --- ## Docker ### Standalone ```bash docker pull ghcr.io/jhostinaleck/brightspace-mcp:latest docker run --rm -i \ -v "$HOME/.brightspace-mcp:/config:ro" \ -e BRIGHTSPACE_CONFIG=/config/config.yaml \ ghcr.io/jhostinaleck/brightspace-mcp:latest serve ``` ### With Redis (docker-compose) ```bash # Start server + Redis docker compose --profile redis up # Or standalone (in-memory cache) docker compose up ``` The `config.yaml` inside `~/.brightspace-mcp/` must have the `redis:` section pointing to `redis://redis:6379` when using the compose profile. --- ## Architecture highlights - **Resilience**: retry with exponential backoff + jitter, circuit breaker (5 failures β†’ 30s cooldown), request coalescing, bulkhead (max 5 concurrent requests) - **Cache tiers**: HTTP response cache (L1, in-memory, 60s TTL) + domain cache (L2, layered memory β†’ file or Redis) - **Security**: HTTPS-only transport, secrets redaction in all log output, OS keychain integration, AES-256-GCM encrypted credential file, session tokens expire with the D2L token - **DDD structure**: bounded contexts (`assignments`, `authentication`, `calendar`, `communications`, `content`, `courses`, `grades`, `groups`, `notifications`, `quizzes`) with clean domain / application / infrastructure separation --- ## License [MIT](./LICENSE) Β© Jhostin Aleck