Skip to main content
Glama
README.md
# Moodle MCP Server

An MCP server for AI assistants (Claude Code, Cursor, Codex and other stdio clients)
on top of the [Moodle](https://moodle.org) web services REST API. It is built for
**teachers**: the assistant can see courses, assignments and student submissions,
download submission files, and save a grade with feedback, acting as the teacher
and only after the teacher confirms. Unlike student-oriented Moodle MCP servers,
the focus here is reviewing assignments and grading.

The server works with any Moodle site that has the mobile web service
(`moodle_mobile_app`) enabled: the token is issued the same way as for the Moodle
mobile app, so login through an external OAuth provider with a captcha works too.

## Prerequisites

- [Node.js](https://nodejs.org/) ≥ 22.
- Google Chrome or Chromium, for the `moodle-mcp-login` command.
- Mobile app access enabled on the Moodle site (Site administration → Mobile app).
  To check: `<site>/admin/tool/mobile/launch.php` should open rather than return
  an error.

## Quick start

The example below uses `https://moodle.example.org`; replace it with your Moodle
address exactly as it appears in the browser (scheme, host, path; a trailing
slash does not matter).

1. Log in, once per machine and again whenever a tool returns a login error
   ([Moodle login and server token](docs/features/login.md), in Russian):

   ```bash
   MOODLE_URL=https://moodle.example.org npx -y -p @av-leschinskiy/moodle-mcp moodle-mcp-login
   ```

   A Chrome window opens: log in to the site as usual and solve the captcha
   yourself. The command prints `Logged in as <name> on <site>` and closes the
   window. Without `-p`, npx would start the server instead of the login command.
   Chrome is looked up in PATH; on macOS and Windows, set its path in
   `MOODLE_CHROME`.

2. Add the server to your client. Claude Code:

   ```bash
   claude mcp add moodle -e MOODLE_URL=https://moodle.example.org -- npx -y @av-leschinskiy/moodle-mcp
   ```

   Add `-s user` to enable it for all projects; without it, only for the current one.

   Codex, with a command or a block in `~/.codex/config.toml`:

   ```bash
   codex mcp add moodle --env MOODLE_URL=https://moodle.example.org -- npx -y @av-leschinskiy/moodle-mcp
   ```

   ```toml
   [mcp_servers.moodle]
   command = "npx"
   args = ["-y", "@av-leschinskiy/moodle-mcp"]
   env = { MOODLE_URL = "https://moodle.example.org" }
   ```

   Cursor and other clients: a stdio server with the command
   `npx -y @av-leschinskiy/moodle-mcp` and the `MOODLE_URL` variable:

   ```json
   {
     "mcpServers": {
       "moodle": {
         "command": "npx",
         "args": ["-y", "@av-leschinskiy/moodle-mcp"],
         "env": { "MOODLE_URL": "https://moodle.example.org" }
       }
     }
   }
   ```

3. Check: ask the assistant "show my Moodle courses".

From a repository clone, use `node <clone>/dist/index.js` and
`node <clone>/dist/cli/login.js` instead of `npx`: `dist/` is committed, no build
is needed.

## What's inside

<!-- BEGIN:tools-table (generated by scripts/tool-docs.mjs) -->

**9 tools** in total; **3** of them require user confirmation.

| Category | Tools | Confirmation | Covers |
|---|---:|---:|---|
| **Site** | 2 | — | who I am, Moodle version, available functions, my courses |
| **Assignments and submissions** | 3 | — | course assignments, participants and submission statuses, full submission |
| **Files** | 1 | — | downloading submission files to disk |
| **Grading** | 3 | 3 | grade and feedback, submission comment, returning a submission for rework |

<details>
<summary>Full list of names</summary>

**Site**

- `moodle_list_courses`
- `moodle_site_info`

**Assignments and submissions**

- `moodle_get_submission`
- `moodle_list_assignments`
- `moodle_list_submissions`

**Files**

- `moodle_download_file`

**Grading**

- `moodle_add_submission_comment` — confirmation
- `moodle_revert_submission_to_draft` — confirmation
- `moodle_save_grade` — confirmation

</details>

<!-- END:tools-table -->

The server acts **on your behalf**: it sees and changes the same things you do in
the Moodle interface. The host asks for confirmation only for writes that students
will see: a grade with feedback, a submission comment, returning a submission to
draft (`destructiveHint: true`). Downloading a file
also writes, but only to your disk, and needs no confirmation.

Responses are trimmed for the model context: HTML in descriptions and submissions
is converted to plain text, times to ISO, a grade that is not set to `null`, and
participant emails are never returned.

## Configuration

<!-- BEGIN:env-table (generated by scripts/env-docs.mjs) -->

| Variable | Required | Default | Description |
|---|:---:|---|---|
| `MOODLE_URL` | yes | — | Moodle site address (wwwroot), e.g. `https://moodle.example.org` |
| `MOODLE_TOKEN_FILE` | — | `~/.config/mcp-moodle/token.json` | Where `moodle-mcp-login` saves the token (path is resolved via `homedir()`) |
| `MOODLE_TOKEN` | — | — | Web service token override (CI / smoke tests) |
| `MOODLE_TIMEOUT` | — | `30000` | Timeout of a single API request (ms) |
| `MOODLE_CHROME` | — | — | Chrome/Chromium binary for `moodle-mcp-login` (looked up in PATH by default) |
| `MOODLE_DEBUG` | — | `false` | `1`/`true`/`yes`/`on` enables debug output to stderr and `debug` level in the host log channel |

<!-- END:env-table -->

The token file has `0600` permissions and stores the site address it was issued
for: if it does not match `MOODLE_URL`, the server asks you to run
`moodle-mcp-login`. The server reads the file on every request, so there is no
need to restart the host after logging in again. The token is scrubbed from error
messages, logs and tool responses (including file URLs).

The Chrome profile used for login is kept separate from your main one
(`~/.config/mcp-moodle/chrome-profile`): site cookies persist, so logging in again
often works without a password.

## Development

```bash
npm install        # also enables the git hooks (.githooks)
npm run dev        # tsx, no compilation
npm run build      # native TypeScript 7 → dist/
npm run verify     # typecheck + lint + tests + docs check
MOODLE_URL=… npm run smoke   # read-only tool chain against a live Moodle
```

Build only with `npm run build`: `devDependencies` contain two compiler versions,
[details](AGENTS.md#typescript). `dist/` is committed; the pre-commit hook builds
and stages it, bumps the patch version and regenerates the tables above.
Architecture and rules are in [AGENTS.md](AGENTS.md) (in Russian).

TDQS

A4/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a distinct purpose: site info, listing courses/assignments/submissions, retrieving one submission, downloading files, and saving grades. moodle_list_submissions vs moodle_get_submission is clearly separated as summary list vs full single participant. No overlapping or ambiguous tool roles.

Naming Consistency4/5

All tools share the moodle_ prefix and snake_case, but moodle_site_info breaks the verb_noun pattern by using a noun phrase rather than an action. The rest follow list_/get_/download_/save_ consistently. Minor deviation only.

Tool Count5/5

Seven tools is well within the ideal range and matches the focused scope of an assignment-grading MCP server. Each tool covers a necessary step and no tool feels redundant.

Completeness4/5

The surface covers the main teacher workflow: identify site/user, list courses, list assignments, inspect submissions, download files, and save grades/feedback. Minor gaps exist around broader Moodle operations (e.g., course content beyond assignments, bulk grade operations), but the core lifecycle is complete.

Maintenance

ActivityMaintained
ResponsivenessNo issues