blackboard-mcp
blackboard-mcp
An MCP server for Blackboard Learn (Ultra). Gives Claude, Codex, Cursor and any other MCP client read access to your courses, content, files, grades, deadlines, announcements and discussions, using your own browser session.
npx blackboard-mcp auth login # sign in once
npx blackboard-mcp install # register with your MCP clientThen ask your assistant things like:
What do I have due this week? Read me the lecture 4 slides from Machine Learning. How am I doing across all my courses? Catch me up, I've been away a week.
Why this exists
Blackboard's documented REST API requires an OAuth application registered and approved by your institution's Blackboard administrator. Most students and many staff can't get one.
This package targets the internal Ultra API instead: the surface the Blackboard web interface itself calls, authenticated with nothing but a session cookie. It also exposes more than the public API does, including to-do lists, the activity stream, conversations, discussion read state and attendance.
It is read-only in practice, and does not cover quizzes, rubrics or submitting work. See Limitations below.
Install
Requires Node.js 22.5+. Older versions work but must sign in with --paste.
npm install -g blackboard-mcpOr run it without installing, which is what the generated client config uses:
npx -y blackboard-mcpSigning in
blackboard-mcp auth loginThat's the whole flow. No URL, no cookies, no DevTools, no password.
It reads the session from the browser you're already signed in to. Blackboard's BbRouter cookie is emitted only by Blackboard Learn, so finding it identifies both that you have a session and which instance it belongs to. Candidates are verified against the live API before anything is stored.
$ blackboard-mcp auth login
Signed in as Ada Lovelace (a.lovelace@student.example.edu)
Instance: https://blackboard.example.edu
Imported by: browser (automatic)
Auto-refresh: ENABLEDWorks with Chrome, Edge, Brave, Chromium, Vivaldi, Opera and Firefox, including multiple profiles. To see what it can find:
blackboard-mcp auth browsersYou won't have to sign in again
Blackboard sessions expire after about three hours. Two mechanisms avoid re-authenticating:
Keep-alive. While the server runs it pings Blackboard's session endpoint, resetting the inactivity timer. An idle session never expires.
Silent renewal. If the session lapses anyway, the institution's SSO redirect chain is replayed against your identity provider. This is what your browser does when you reload after being logged out.
Renewal works because identity provider sessions outlive Blackboard's by weeks or months. auth login imports those cookies alongside Blackboard's, so renewal needs no human:
GET /ultra -> 302
GET / -> 302
GET /auth-saml/saml/login -> 302
GET login.microsoftonline.com/ -> 200 (auto-submit SAMLResponse)
POST /auth-saml/saml/SSO -> 302
GET /ultra -> 200 (new session)The implementation follows redirects and resubmits whatever SSO form comes back, so SAML, WS-Federation and Shibboleth all work without provider-specific code.
auth login verifies this before reporting Auto-refresh: ENABLED, by forcing one full chain through your provider. It then narrows the stored cookies to Blackboard plus your actual provider, discarding anything imported speculatively.
To renew manually:
blackboard-mcp auth refreshIf browser import can't work
Two cases, both reported with the fallback:
Chrome 127+ on Windows uses App-Bound Encryption, which by design can't be read by another process.
Node older than 22.5 lacks the built-in SQLite needed to read cookie stores.
The manual path:
blackboard-mcp auth login --pasteThis asks you to paste a "Copy as cURL" from DevTools (Network tab, right-click a request, Copy, Copy as cURL). A pasted request carries only Blackboard's cookies, not your provider's, so these sessions can't auto-renew and need signing in again every few hours.
Blackboard's session cookie is
HttpOnly, sodocument.cookiecan't see it. It only appears on a real request.
Non-interactive:
BLACKBOARD_COOKIE='BbRouter=...; JSESSIONID=...' \
blackboard-mcp auth login --url https://blackboard.your-university.eduTo check state:
blackboard-mcp auth status
blackboard-mcp doctor # full diagnosticsRegistering with a client
blackboard-mcp install # print config for every known client
blackboard-mcp install cursor --write # merge into Cursor's configSupported: claude-code, claude-desktop, cursor, codex, vscode, windsurf, zed.
--write merges into the existing config rather than overwriting it. Codex uses TOML, which is printed for you to paste.
Claude Code
claude mcp add blackboard -- npx -y blackboard-mcpClaude Desktop, at ~/Library/Application Support/Claude/claude_desktop_config.json
(%APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"blackboard": { "command": "npx", "args": ["-y", "blackboard-mcp"] }
}
}Cursor (~/.cursor/mcp.json) and Windsurf (~/.codeium/windsurf/mcp_config.json) use the same shape.
VS Code, at .vscode/mcp.json. Note the key is servers:
{
"servers": {
"blackboard": { "command": "npx", "args": ["-y", "blackboard-mcp"] }
}
}Codex CLI, at ~/.codex/config.toml:
[mcp_servers.blackboard]
command = "npx"
args = ["-y", "blackboard-mcp"]Tools
Every course tool takes a courseId, an internal id like _12345_1 rather than the human course code. Get them from bb_list_courses.
Courses and identity
Tool | Purpose |
| Signed-in user and instance |
| Session validity, expiry, cookies |
| Enrolled courses with ids, terms, roles. Start here |
| Course detail and enabled tools |
| Academic terms |
| Participants and their roles |
Content
Tool | Purpose |
| Recursive content tree with folder paths. The main discovery tool |
| One level of a folder |
| Item detail: body text, attached file, link target, due date |
| Search titles and bodies, one course or all |
Files
Tool | Purpose |
| Download and extract text. Pages through long PDFs |
| Save a file to disk |
| Every readable document in a course |
| Bulk download or archive a course |
| Files submitted with an assignment attempt |
Grades
Tool | Purpose |
| Grades for one course or all |
| Score, letter grade, attempts, submissions, instructor feedback |
| Per-course standing and averages |
Deadlines
Tool | Purpose |
| Overdue, due today and upcoming across all courses |
| Calendar events in a date range |
| A course's recurring meetings |
Communication
Tool | Purpose |
| Announcements with full text, one course or all |
| Recent changes across every course |
| Course message threads |
| Discussion forums, posts and replies |
| Unread counts across all courses |
| Attendance records |
Escape hatch
Tool | Purpose |
| Call any Blackboard API path directly |
| Up to 20 reads in one round trip |
| Every endpoint this server knows |
| Mark content reviewed (write, off by default) |
The Ultra API is larger than what's wrapped here. bb_raw_request is the answer when the tool you need doesn't exist.
Prompts
Workflows your client surfaces as slash commands:
whats_duedeadlines, triaged, with submission statuscourse_briefingfull picture of one coursestudy_packfind, read and synthesise material on a topiccatch_upeverything that changed while you were awaygrade_reportstanding across all courses, with feedback themesfind_materiallocate a specific file or reading
Resources
blackboard://meyour profileblackboard://coursescourse listblackboard://course/{courseId}/outlinefull content tree as JSON
Reading files
bb_read_file extracts text from PDF, HTML and plain-text formats, including code, CSV, JSON, Markdown and subtitles.
Long documents are windowed rather than truncated. A PDF returns a page range plus a note telling the model how to continue, so a 300-page course reader is fully readable without flooding the context.
Office formats (.docx, .pptx, .xlsx) and archives can't be extracted. They're ZIP containers needing an unzip implementation Node doesn't ship, and a native dependency would break npx installs. They still download fine via bb_download_file.
Scanned PDFs with no text layer are detected and reported as needing OCR, rather than returning an empty string that looks like a bug.
Linked documents
Instructors often publish lecture material as a Google Slides, Docs or Sheets link rather than an uploaded file, leaving no bytes in Blackboard at all. bb_read_file resolves these through the provider's export endpoint, so a linked deck reads like an attached file, and bb_list_files lists them as course material.
Two constraints, because that URL is written by a third party and arrives as untrusted content:
Provider allowlist. Only Google Docs hosts with a documented export endpoint are fetched. Without this, a pasted link would turn the tool into an arbitrary URL fetcher.
No credentials. These fetches carry no Blackboard session and no Google auth, so only material the instructor already made link-shareable is reachable. A privately shared document reports that plainly instead of returning a sign-in page dressed up as slides.
Formats default to the cheapest to read (txt for decks and documents, csv for sheets). Pass format for pdf, pptx, docx or xlsx.
Adapting to your institution
Learn releases and reverse proxies move endpoints around. If something 404s, record a browser session and import it:
blackboard-mcp har import ~/Downloads/blackboard.har --verboseThis detects your instance, compares every real path against the built-in templates, writes corrections to ~/.blackboard-mcp/endpoints.json, and lists the endpoints your tenant exposes that this package doesn't model. Those stay reachable through bb_raw_request.
To record a HAR: DevTools, Network tab, check Preserve log, browse Blackboard, then right-click and Save all as HAR with content.
Current Chrome strips cookies from HAR exports, so a HAR can't sign you in. That's a good default, since a HAR with cookies is a credential file. Treat one like a password.
Security
Read-only by default. Non-GET requests are refused unless
BLACKBOARD_MCP_ALLOW_WRITES=1is set. Two Blackboard read operations use non-GET verbs (the batch fan-out and the activity stream) and are explicitly allowed.Sessions are encrypted at rest with AES-256-GCM. The key lives in the macOS Keychain or Linux Secret Service, falling back to a
0600keyfile. State lives in~/.blackboard-mcp/.Browser cookies are read locally only. Cookie stores are copied, decrypted with a key the OS already grants this user, and never transmitted. Import pulls identity provider cookies broadly at first, since the provider isn't knowable before the SSO chain names it, so the first renewal prunes the stored jar down to Blackboard plus your actual provider.
Cookies never leave your instance. The HTTP client enforces a host allowlist across every redirect hop, so the session can't be sent to a third party even if a redirect or an instructor-pasted link points there.
Renewal is scoped by delegation. The SSO replay only sends cookies to your Blackboard host and to hosts Blackboard's own redirect chain named.
Untrusted input is treated as data. Server-supplied filenames are sanitised before touching the filesystem, and embedded links are accepted only as instance-relative paths.
bb_raw_requestis restricted to API path prefixes and rejects traversal.
Your Blackboard account governs what's visible. This server reads exactly what you can read in a browser, and nothing more.
A note on academic data
Grades, feedback and submissions are confidential personal data, and fall under GDPR in the EU and UK. Downloaded files land on your local disk unencrypted, so mind where they go and prefer the narrowest tool for the question. If you're staff acting on student data rather than your own, check your institution's data-handling policy first.
Configuration
Variable | Meaning |
| Instance origin, overriding stored config |
| Session cookie for non-interactive login |
|
|
| Where files are saved |
| Per-file ceiling, default 100 MB |
| Default list page size, default 50 |
|
|
| State directory, default |
Use as a library
The package is a usable Blackboard SDK on its own:
import { BlackboardClient } from 'blackboard-mcp';
const bb = await BlackboardClient.create();
for (const m of await bb.listCourses({ availableOnly: true })) {
console.log(m.course?.displayName);
}
// Recursive content tree, both roots
const items = await bb.walkContents('_12345_1');
// Fan out across courses in one request
const grades = await bb.batch([
{ method: 'GET', relativeUrl: 'v1/courses/_12345_1/gradebook/grades?userId=_1_1' },
]);CLI
blackboard-mcp [serve] Run the MCP server on stdio (default)
blackboard-mcp auth login Sign in, importing from your browser
blackboard-mcp auth status Session and connectivity status
blackboard-mcp auth browsers List browser profiles and sessions found
blackboard-mcp auth refresh Renew the session without signing in
blackboard-mcp auth logout [--purge] Forget the session
blackboard-mcp har import <file> Learn this tenant's endpoints
blackboard-mcp doctor Diagnose configuration
blackboard-mcp install [client] Register with an MCP client
blackboard-mcp courses List your courses
blackboard-mcp endpoints [filter] Show the endpoint mapTroubleshooting
Run blackboard-mcp doctor first.
NOT_CONFIGURED or NOT_AUTHENTICATED. Run blackboard-mcp auth login.
SESSION_EXPIRED. The session lapsed and couldn't be renewed silently. If auth status shows auto-refresh unavailable, re-run auth login without --paste to capture identity provider cookies. If it shows enabled, your provider session has itself expired: open Blackboard in a browser, sign in, then auth login again.
"No Blackboard session found in any browser". Sign in to Blackboard in a browser first. auth browsers shows what was scanned.
"The captured session was rejected". You copied a request from your SSO provider rather than the Blackboard host, or the tab had already logged out. Copy a request whose URL is your Blackboard hostname.
A tool 404s. Your tenant may differ. Run har import <har> --verbose, then use bb_raw_request for anything unmapped.
Empty course list. Courses you hid in Blackboard are excluded by default; pass includeHidden: true. Organizations are excluded too; pass organizations: "include".
bb_read_file returns nothing for a PDF. It's a scan with no text layer and needs OCR. If your institution has Blackboard Ally, the course may offer an accessible alternative format.
A linked Google deck won't read. It's shared privately rather than link-shared. Only material the instructor made publicly accessible can be fetched, since no credentials are used.
Development
pnpm install
pnpm build
pnpm test # protocol and unit tests
pnpm typecheck
pnpm inspect # MCP Inspector against the built serverLimitations
Read-only for all practical purposes. Submitting assignments, posting discussion replies and sending messages aren't implemented: the write paths weren't captured, and getting a submission wrong has real consequences.
Auto-renewal needs a browser-imported session. A
--pastesession has no identity provider cookies and will expire in a few hours.Office formats download but don't extract.
No OCR for scanned PDFs.
Ultra-oriented. Courses in the older Classic experience expose less through this API.
bb_raw_requestis the fallback.Unofficial. This uses an internal API with no stability guarantee. Blackboard may change it without notice, which is why HAR import exists.
Legal
Not affiliated with, endorsed by, or supported by Anthology Inc. or Blackboard. "Blackboard" and "Blackboard Learn" are trademarks of their respective owners.
This tool accesses your own account with your own credentials, reading the same data your browser shows you. Your institution's acceptable-use policy still applies. Don't use it to access data that isn't yours.
MIT