It reads your courses, and it writes to them: content modules, authored HTML pages, uploaded video and images, links, discussions, assignments, grade items, and whole course packages. Every write is previewed before it lands, and student identities are withheld from the model by default.
# sign in, then register with your assistant
npx -y lamaku-mcp login
claude mcp add lamaku -- npx -y lamaku-mcp
What it is
MCP is the Model Context Protocol: a way for an AI assistant such as Claude to use external tools. Installing this server gives your assistant 54 tools for working with Brightspace, so you can describe a course in conversation and have it built.
Other Brightspace MCP servers are read-only. They check grades and due dates, which is a useful and much simpler job. This one authors, which is why most of its design is about not damaging a live course.
This is not endorsed by the University of Hawaiʻi or D2L. Check UH's acceptable-use policy before connecting it to your account.
Requirements
- Node.js 20 or later
- Node 22 is also tested. Earlier versions will not run it.
- A Lamakū account
- Any UH account with course access. Sign-in goes through UH SSO and Duo.
- A sandbox course
- Strongly recommended before touching a live section. Request one from UH ITS.
- An MCP client
- Claude Code, Claude Desktop, or anything that speaks MCP over stdio.
Setting up from scratch
For a machine with none of this on it yet. If node --version already prints
v20 or higher, skip to Installation.
- Open a terminal
On macOS, Terminal, in Applications then Utilities. On Windows, Windows Terminal or PowerShell from the Start menu. Everything below is typed there, one line at a time, pressing Enter after each.
- Install Node.js
Download the LTS installer from nodejs.org and run it, accepting the defaults. Close the terminal and open a new one afterwards, then check it took:
node --versionAnything from
v20up is fine. Earlier versions will not run this. - Install an MCP client
Claude Code is the one these instructions assume. Claude Desktop works too and is installed as an ordinary application instead.
npm install -g @anthropic-ai/claude-code - Ask UH ITS for a sandbox course
A sandbox is an empty course only you can see. Build there before going near a section students are enrolled in. Nothing in this server can undo a delete, and a mistake in a live course is a mistake in front of real students.
- Carry on
Everything is in place. Installation is two commands from here.
Installation
The package is on npm, so npx fetches and runs it with nothing to clone or
build. The first run downloads it; after that it starts from the cache.
Signing in
npx -y lamaku-mcp login
A browser window opens. Complete the UH sign-in and approve the Duo prompt yourself; the window closes once the session is captured. The session is stored encrypted under your user profile.
Sessions last about a day of idleness and expire after a few days. Re-running login is routine rather than a sign that something is wrong. Check the current state with npx -y lamaku-mcp status.
Registering with Claude Code
claude mcp add lamaku -- npx -y lamaku-mcp
Registering with Gemini CLI
gemini mcp add lamaku npx -y lamaku-mcp
If the entry it writes fails to load, which recent releases have done, add it to
~/.gemini/settings.json by hand instead:
{
"mcpServers": {
"lamaku": {
"command": "npx",
"args": ["-y", "lamaku-mcp"]
}
}
}
/mcp inside Gemini lists what loaded.
Registering with Codex CLI
codex mcp add lamaku -- npx -y lamaku-mcp
Or the same thing by hand in ~/.codex/config.toml:
[mcp_servers.lamaku]
command = "npx"
args = ["-y", "lamaku-mcp"]
Registering with another MCP client
{
"mcpServers": {
"lamaku": {
"command": "npx",
"args": ["-y", "lamaku-mcp"],
"env": { "LAMAKU_HOST": "lamaku.hawaii.edu" }
}
}
}
From source
For working on the server itself rather than using it:
git clone https://github.com/JesseTho/lamaku-mcp
cd lamaku-mcp && pnpm install && pnpm build
claude mcp add lamaku-dev -- node "$PWD/dist/index.js"
The committed lockfile is pnpm's. npm works locally if you prefer it, but do
not commit the package-lock.json it generates.
Configuration
| Variable | Default | Purpose |
|---|---|---|
LAMAKU_HOST | lamaku.hawaii.edu | Brightspace hostname. |
BRIGHTSPACE_HOST | unset | Neutral alias for the same setting. LAMAKU_HOST wins if both are set. |
LAMAKU_FERPA | strict | off disables student pseudonymisation. See Student privacy. |
LAMAKU_AUTH | session | oauth uses a registered client instead of a browser session. Unproven. |
LAMAKU_BROWSER | auto | Force chrome, msedge, or chromium at sign-in. |
LAMAKU_DOWNLOAD_DIR | app data | Where downloaded files are written. |
LAMAKU_LE_VERSION | latest | Pin a Learning Environment API version. |
How writes work
Two checks run before anything changes in Brightspace.
Role preflight
The same person holds different roles in different courses, and Brightspace enforces them inconsistently. An account may be Instructor on a sandbox, Designer on one section, and Instructor-Content Copy Only on a template, where announcements succeed but assignments are refused. Each authoring tool checks your role before spending the call, so you get an explanation rather than a bare 403.
Confirmation token
Calling a write tool returns a preview. Nothing has been sent. The response describes what would happen and carries a token:
{
"status": "confirmation_required",
"action": "delete_discussion_forum",
"willDo": {
"course": "Sandbox 2",
"name": "Module 3: diagnosing failed feeds",
"topicCount": 1,
"warning": "Deletes the forum, its 1 topic(s), and every post inside them."
},
"confirmToken": "0vVbBxC1c2gq",
"expiresAt": "2026-08-24T20:03:50Z"
}
Calling the same tool again with that token performs the action. Tokens are single use, expire after five minutes, and are scoped to the action they were issued for, so a token approved for one preview cannot authorise a different operation.
New modules, pages, files and links are hidden from students on creation, so a half-built course is never briefly visible. release_course_content publishes them as a deliberate final step, after showing you exactly what will become visible.
There is no recovery through this API. Delete previews name what goes with the object: a forum preview lists every topic that will be removed with it. Read the preview.
Building a course
Order matters, because the content path for uploaded media is derived from the course code and does not exist until the upload happens.
-
Create the modules
One
create_content_moduleper module. They arrive in creation order;update_content_moduletakes asortOrderif you need to rearrange them later. -
Upload media before writing pages
create_content_fileaccepts video, audio, images, PDFs and caption files. Each call returns the content path and anembedAssnippet with that path filled in. Writing pages first means rewriting them once you know the paths. -
Write the pages
create_content_pagetakes body HTML and wraps it in a template. Paste theembedAssnippets where the media belongs. -
Add what a package cannot carry
create_content_linkfor external sources, thencreate_discussion_forum,create_discussion_topicandcreate_assignment. No import format expresses these, so they must be authored. -
Set module cover images
set_module_descriptionwith animagePathfrom step 2. Brightspace renders an image in a module description as that module's cover. -
Import quizzes
import_course_packagewith a Common Cartridge. This is the only way to create quiz questions. Pollget_import_statusuntil it returnsCOMPLETED. -
Release the course
release_course_contentwalks the content tree and unhides everything in one pass, after listing what it will make visible.
Page templates
create_content_page and update_content_page accept a template.
| Template | Result |
|---|---|
uh | The UH shared HTML Template Library: banner, content column, seal footer. The stylesheets are same-origin, so this is correct on a UH instance and unstyled anywhere else. Default. |
jabsom | The JABSOM Design System with its tokens inlined. Mānoa Green headings in Inter, Source Serif 4 body. Self-contained, so it also renders correctly outside Lamakū. |
plain | A bare document with no institutional styling. |
col-sm-10 content column and the seal footer
from UH's own stylesheets. Open full size.See course-style.md and jabsom-style.md for the components each template provides and the accessibility obligations attached to them.
Course imports
import_course_package accepts an IMS Common Cartridge (.imscc) or a Brightspace course package (.zip), up to 2 GB. It returns a job token; poll get_import_status until the status is COMPLETED or IMPORTFAILED.
| Format | Carries |
|---|---|
| Common Cartridge | Pages, weblinks, files, QTI quizzes and question banks, discussion topics, LTI links. |
Brightspace .zip | Everything above, plus D2L-native objects: rubrics, release conditions, grade schemes. |
| Neither | Groups, sections, attendance, awards. |
The Brightspace format is the practical way to move a rubric between courses while the rubric API remains unavailable at this Lamakū version.
Where a package comes from
import_course_package reads a file from the machine the server runs on, which
is your machine, the same one your assistant is working on. So the assistant can write a
package and then import it, and for quizzes that is the normal route rather than a
workaround.
This is how quiz questions get made at all. Brightspace exposes no question-creation route, so the assistant writes a Common Cartridge carrying QTI and imports it. Verified on a real course: four quizzes, seventeen questions, every answer key and every feedback string intact. Nothing was handed over by hand.
Setting up a quiz below walks the sequence.
Two other sources, for packages you are not authoring:
- Brightspace's own export, from another course you have access to. This
is the route for rubrics: they ride in the Brightspace
.zipformat, and generating that format from scratch is not something this project has tested. - Another LMS or a publisher. Common Cartridge is the interchange format, so a package exported from Canvas or Moodle imports here.
Setting up a quiz
Two tools, in this order, because they do different halves of the job.
- The questions
Your assistant writes a Common Cartridge containing the quiz and its QTI questions, then imports it. The questions arrive with their answer keys and their per-answer feedback.
import_course_package(course, filePath) - The settings, if the cartridge did not carry them
create_quizmakes a quiz shell and everything around it: open and close dates, attempts allowed, a time limit, shuffling, hints, whether points are shown. It cannot add questions, which is the whole reason step one exists.create_quiz(course, name, timeLimitMinutes, attemptsAllowed, ...)
Import adds, it never replaces. Import into an empty course, or check that the package does not overlap what is already there.
Tool index
Run pnpm tools for the current list with full signatures.
Session
auth_status whoami check_capabilities
Reading
list_courses list_modules get_module get_topic download_topic_file list_assignments get_assignment list_my_submissions download_submission_file get_grades get_final_grade get_upcoming_deadlines get_announcements list_forums list_topics read_posts list_quizzes list_checklists
Content authoring
create_content_module update_content_module delete_content_module create_content_page update_content_page create_content_file create_content_link update_content_topic delete_content_topic set_module_description release_course_content import_course_package get_import_status
Course objects
create_announcement delete_announcement create_assignment create_assignment_category delete_assignment create_grade_item create_grade_category delete_grade_item create_discussion_forum create_discussion_topic delete_discussion_forum delete_discussion_topic create_quiz delete_quiz create_checklist add_checklist_item delete_checklist
Student side
submit_assignment create_discussion_post reply_to_post
Student privacy
Student names, usernames, email addresses and institutional IDs are protected education records under FERPA. This server passes course data to a language model, and from there into a vendor's logs, so by default it does not emit them.
Each student becomes a stable handle such as student:4f2a91. The handle is an HMAC under a salt generated on your machine and never transmitted, so it is not reversible and not comparable between installations. It remains stable across calls, so an assistant can still reason about the same student who missed a particular lab without knowing who that is.
The raw user ID is dropped as well, because it is a direct key back into Brightspace and into any system sharing the same institutional identifier.
Course staff are not redacted; a co-instructor's name is not a protected record. Passing revealStudents: true on a call returns real names when you have deliberately asked for them.
You can read your roster in Brightspace at any time. The purpose is keeping it out of prompts and model retention unless you intend otherwise. SECURITY.md describes what this does and does not protect.
Can a student use this to cheat?
Not by gaining anything their account does not already have. The server signs in as the user, and Brightspace enforces permissions on its side, so a student role cannot write grades, read hidden content, or use the instructor tools. There is deliberately no quiz-taking tool: quizzes can be listed, and nothing starts an attempt or reads a question.
What it changes is friction. A student account can submit assignments and post to discussions, so an agent can write and submit work end to end, and an API submission looks identical to one made in the browser. That is the same academic-integrity exposure as any chatbot plus a paste, automated; it belongs to the account's own permissions, not to a hole this server could close while legitimate self-submission exists.
Known limitations
| Capability | Status | Detail |
|---|---|---|
| Quiz questions, directly | Unavailable | Brightspace exposes GET and no create route, so create_quiz makes the shell and its settings only. The questions come from a cartridge your assistant writes and imports, which is verified. See Setting up a quiz. |
| Rubric authoring | Version-blocked | Requires API version 1.97 or later. Lamakū serves 1.96. A Brightspace package import carries rubrics today. |
| Grade value writing | Untested | The test sandbox has no student enrolments, and testing in a live section would alter a real record. |
| Grade item listing | Unavailable | get_grades returns the calling user's own grades. Items can be created and deleted by ID but not enumerated. |
| Course creation | Permission | Organisation-level administrative permission, not available to an instructor account. Request courses from UH ITS. |
| Groups, sections, attendance, awards | Not implemented | Also not expressible in either import format. |
| OAuth on a non-UH instance | Unproven | Implemented against auth.brightspace.com but never exercised outside UH. The session path requires an interactive browser, so neither path suits headless use. |
Troubleshooting
A call fails with 403
Usually a role problem rather than an expired session. The same account holds different roles per course. Run whoami and check your role in that specific course.
A call fails with 400 and an empty error array
Brightspace returns 400 {"Errors":[]} for a malformed body and for an unknown parent object, naming no field. Check the IDs first: list_modules shows valid module IDs.
Reading a 404 against a 400
These distinguish two different failures. A 404 means the route does not exist at this API version. A 400 means it exists and rejected your input. A 400 does not prove you have permission, because several routes validate the body before checking the role.
Sign-in does not complete
The window can open behind others; check the taskbar before assuming it failed. Some execution policies block the browser launcher, in which case set LAMAKU_BROWSER to a browser you already have installed.
Every tool disappeared mid-session
The server process died. A stdio server has nowhere to report that, and
claude mcp list will still say it is connected, because checking spawns a fresh
process rather than inspecting the one your session is talking to.
Restart your client to get the tools back. Nothing needs re-authenticating; the stored session is a file and survives independently. The server survives an unhandled rejection rather than exiting on it now, and logs the cause to stderr, so a repeat leaves a trail.
Content is missing after building a course
Everything is created hidden. Run release_course_content, or check hiddenCount in a listing.