lamaku-mcp Agentic course authoring Overview Repository Security Issues

Manual

Reference for lamaku-mcp. If you have not installed it yet, start on the overview.

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.

Note

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.

  1. 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.

  2. 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 --version

    Anything from v20 up is fine. Earlier versions will not run this.

  3. 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
  4. 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.

  5. 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

Environment variables. All are optional.
VariableDefaultPurpose
LAMAKU_HOSTlamaku.hawaii.eduBrightspace hostname.
BRIGHTSPACE_HOSTunsetNeutral alias for the same setting. LAMAKU_HOST wins if both are set.
LAMAKU_FERPAstrictoff disables student pseudonymisation. See Student privacy.
LAMAKU_AUTHsessionoauth uses a registered client instead of a browser session. Unproven.
LAMAKU_BROWSERautoForce chrome, msedge, or chromium at sign-in.
LAMAKU_DOWNLOAD_DIRapp dataWhere downloaded files are written.
LAMAKU_LE_VERSIONlatestPin 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.

Everything is created hidden

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.

Deletes cannot be undone

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.

  1. Create the modules

    One create_content_module per module. They arrive in creation order; update_content_module takes a sortOrder if you need to rearrange them later.

  2. Upload media before writing pages

    create_content_file accepts video, audio, images, PDFs and caption files. Each call returns the content path and an embedAs snippet with that path filled in. Writing pages first means rewriting them once you know the paths.

  3. Write the pages

    create_content_page takes body HTML and wraps it in a template. Paste the embedAs snippets where the media belongs.

  4. Add what a package cannot carry

    create_content_link for external sources, then create_discussion_forum, create_discussion_topic and create_assignment. No import format expresses these, so they must be authored.

  5. Set module cover images

    set_module_description with an imagePath from step 2. Brightspace renders an image in a module description as that module's cover.

  6. Import quizzes

    import_course_package with a Common Cartridge. This is the only way to create quiz questions. Poll get_import_status until it returns COMPLETED.

  7. Release the course

    release_course_content walks 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.

TemplateResult
uhThe 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.
jabsomThe 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ū.
plainA bare document with no institutional styling.
jabsom · Mānoa Green headings in Inter over Source Serif 4 body, a rule above each section, a green-tinted pull quote, and the school footer. The tokens are inlined, so this is exactly what it looks like anywhere. Open full size.
uh · Shown off-instance, which is why it is bare. On Lamakū the same markup picks up the banner, the col-sm-10 content column and the seal footer from UH's own stylesheets. Open full size.
plain · No institutional styling at all. Use it when the course carries its own, or when the page is going somewhere other than Lamakū. 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.

What each package format carries
FormatCarries
Common CartridgePages, weblinks, files, QTI quizzes and question banks, discussion topics, LTI links.
Brightspace .zipEverything above, plus D2L-native objects: rubrics, release conditions, grade schemes.
NeitherGroups, 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:

Setting up a quiz

Two tools, in this order, because they do different halves of the job.

  1. 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)
  2. The settings, if the cartridge did not carry them

    create_quiz makes 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, ...)
Importing twice makes two of everything

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.

A disclosure control, not an access control

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

Verified against Lamakū at Learning Environment API version 1.96.
CapabilityStatusDetail
Quiz questions, directlyUnavailable 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 authoringVersion-blocked Requires API version 1.97 or later. Lamakū serves 1.96. A Brightspace package import carries rubrics today.
Grade value writingUntested The test sandbox has no student enrolments, and testing in a live section would alter a real record.
Grade item listingUnavailable get_grades returns the calling user's own grades. Items can be created and deleted by ID but not enumerated.
Course creationPermission Organisation-level administrative permission, not available to an instructor account. Request courses from UH ITS.
Groups, sections, attendance, awardsNot implemented Also not expressible in either import format.
OAuth on a non-UH instanceUnproven 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.

Not endorsed by the University of Hawaiʻi or D2L. MIT licensed.