Skip to main content
Glama
README.md
# sernobre-moodle-mcp

**Model Context Protocol (MCP)** server for Moodle. Lets AI agents (Claude, etc.) publish and manage pedagogical content — lessons, resources, activities, quizzes, forums — directly in Moodle via the **Web Services API**, with **guaranteed idempotency**.

This project grew out of [marcosnahuel/moodle-mcp](https://github.com/marcosnahuel/moodle-mcp). What began as a fork has since been **rebuilt from the ground up** — translated to English, hardened with bug fixes, and reshaped around our own teaching workflow — so it now stands as an independent codebase while keeping the spirit of the original:

- **Rewritten in English** — every tool, parameter and error message (the original project was in Spanish);
- **Bug fixes** — hardening across the publishing and question-editing flows;
- **A rewritten companion plugin** — the original's `local_italiciamcp` became `local_sernobre_mcp`, with its own Web Service endpoints.

> **Credits** — our thanks to [Marcos Nahuel](https://github.com/marcosnahuel) for creating the original `moodle-mcp`, which laid the groundwork for this project: https://github.com/marcosnahuel/moodle-mcp

## What is this?

`sernobre-moodle-mcp` is an MCP server in TypeScript/Node.js that exposes a set of *tools* that an AI agent can call to interact with a Moodle instance:

- create / update / duplicate / archive courses;
- manage sections and reorganize content;
- publish lessons and resources (idempotent);
- create quizzes from GIFT files;
- manage students (enrolment, groups, role changes, password resets);
- query grades, completion, submissions and attempts;
- create forums, calendar events and send messages;
- issue badges and read logs.

Besides the TS wrapper, the repository includes a **companion Moodle plugin** (`local_sernobre_mcp`) that adds extra Web Service *endpoints* (e.g. `upsert_quiz`, `upsert_page`, `add_questions_gift`) needed by some tools.

## Project status

| Component | Version |
|---|---|
| TypeScript wrapper (`sernobre-moodle-mcp`) | v0.1.0 |
| Moodle plugin (`local_sernobre_mcp`) | v0.8.8 |

See [CHANGELOG.md](CHANGELOG.md) for the full history of changes and migration notes.

## Features

- **Guaranteed idempotency** — publishing the same content twice never creates duplicates.
- **Translated to English** — all tools, parameters and error messages are in English (the original project was in Spanish).
- **Bugs fixed** — several fixes compared to the original project, notably in the `upsert_quiz`, `upsert_page`, `add_questions_gift` flows and in question editing on Moodle 5.x.
- **Configurable rate limiting, retries and timeout**.
- **Structured logging** (JSON per line) with automatic token redaction.
- **Companion Moodle plugin** with its own Web Service endpoints.

## Architecture

```
┌─────────────┐   MCP (stdio)   ┌──────────────────────┐   HTTPS JSON   ┌──────────────┐
│  AI Agent   │ ───────────────► │ sernobre-moodle-mcp │ ─────────────► │   Moodle     │
│ (Claude, …) │                  │  MCP server          │                │ Web Services │
└─────────────┘                  └──────────────────────┘                └──────────────┘
                                          │ HTTPS JSON
                                          ▼
                              ┌──────────────────────────┐
                              │  local_sernobre_mcp      │
                              │  plugin (extra endpoints)│
                              └──────────────────────────┘
```

## Requirements

- **Node.js ≥ 20**
- A **Moodle 4.x or 5.x** instance with **Web Services** enabled
- A Web Service token for a user with `editingteacher` or `manager` permissions

## Installation & configuration

### 1. Prerequisites

- **Node.js ≥ 20** (check with `node --version`)
- **npm** (comes with Node.js)
- A **Moodle 4.x or 5.x** instance that you administer
- A Moodle user with **`editingteacher`** or **`manager`** permissions on the courses you want to manage

### 2. Install the wrapper

```bash
git clone https://github.com/sernobre/moodle-mcp.git
cd moodle-mcp
npm install
npm run build
```

This produces the compiled server in `dist/` and the `sernobre-moodle-mcp` binary.

### 3. Configure environment variables

The server reads its configuration from environment variables. At minimum you must set `MOODLE_URL` and `MOODLE_WS_TOKEN`; everything else has sensible defaults.

| Variable | Required | Description |
|---|---|---|
| `MOODLE_URL` | Yes | URL of the Moodle instance (must be HTTPS, except in development) |
| `MOODLE_WS_TOKEN` | Yes | Moodle Web Service token (see step 4) |
| `MOODLE_WS_TIMEOUT_MS` | No | Timeout per request (default: `30000`) |
| `MOODLE_WS_MAX_RETRIES` | No | Number of retries (default: `3`) |
| `MOODLE_WS_RATE_LIMIT_PER_SEC` | No | Requests per second limit (default: `10`) |
| `MCP_LOG_LEVEL` | No | `error` \| `warn` \| `info` \| `debug` (default: `info`) |
| `MOODLE_ALLOW_INSECURE` | No | `true` allows HTTP URLs (development only) |

Set them in your shell before starting the server:

**PowerShell (Windows):**

```powershell
$env:MOODLE_URL = "https://your-moodle.example.com"
$env:MOODLE_WS_TOKEN = "paste-your-token-here"
$env:MCP_LOG_LEVEL = "info"
```

**bash / zsh (Linux, macOS):**

```bash
export MOODLE_URL="https://your-moodle.example.com"
export MOODLE_WS_TOKEN="paste-your-token-here"
export MCP_LOG_LEVEL="info"
```

Or create a `.env` file and load it in your shell (the project does **not** auto-load `.env` files — use a dotenv launcher or export them yourself).

### 4. Configure Moodle (Web Services) — one-time

Follow these steps on the Moodle side **before** first use:

1. **Site administration → Server → Web services** — enable web services and the **REST** protocol.
2. Install/update the companion plugin in step 6 below. During the upgrade, Moodle creates the pre-built **Sernobre MCP** external service with the MCP function allowlist.
3. Open **External services → Sernobre MCP** and keep **Authorised users only** enabled. Add only the dedicated MCP user.
4. Under **Manage tokens**, create a token for that user and select the **Sernobre MCP** service. A token created for another service is not interchangeable.

The pre-built service includes these core functions:

- `core_webservice_get_site_info`
- `core_files_upload`
- `mod_assign_save_submission`
- `mod_assign_submit_for_grading`
- `core_course_get_courses_by_field`
- `core_course_get_contents`
- `core_course_create_courses`
- `core_course_update_courses`
- `core_course_edit_section`
- `core_course_edit_module`
- `core_enrol_get_enrolled_users`
- `core_calendar_create_calendar_events`
- `core_calendar_get_calendar_events`
- `core_calendar_update_event_start_day`
- `core_calendar_get_allowed_event_types`
- `mod_forum_get_forums_by_courses`
- `mod_forum_add_discussion`
- all `local_sernobre_mcp_*` functions declared by the companion plugin

The token user should have a dedicated least-privilege role in the system or relevant course-category context with these capabilities:

- `moodle/course:create`
- `moodle/course:update`
- `moodle/course:view`
- `moodle/course:manageactivities`
- `moodle/user:manageownfiles` (needed by Moodle core_files_upload for the current token user)
- `mod/assign:submit`
- `moodle/course:movesections`
- `moodle/course:sectionvisibility` (only when calling the deprecated core section show/hide API directly)
- `moodle/question:add`
- `enrol/manual:config`
- `moodle/calendar:manageentries`
- `moodle/calendar:manageownentries`
- `moodle/calendar:managegroupentries`
- `mod/forum:viewdiscussion`
- `mod/forum:startdiscussion`
- `webservice/rest:use`


`mod/forum:pindiscussions` is additionally needed when announcements must be pinned. The companion-plugin section endpoint `local_sernobre_mcp_update_section` requires `moodle/course:update`; it is the preferred route for MCP section edits. The destructive `core_calendar_delete_calendar_events` function is intentionally not in the pre-built service.

For another existing Moodle function, open **Site administration -> Plugins -> Web services -> External services -> Sernobre MCP -> Functions -> Add functions**. Add only functions needed by the deployment; this allowlist does not replace the capability checks of the token user.

Add `moodle/category:manage` only when the MCP user must manage categories. Course deletion is deliberately not exposed by the built-in service; add `core_course_delete_courses` manually only after a separate review.

> **Audit tip:** create a dedicated user such as `moodle-mcp-bot`, assign the minimum role above only where needed, restrict the token by IP when possible, and set a validity date.

### 5. Start the server

```bash
npm start
```

The server starts, emits a `server.start` log line, and waits for MCP traffic over stdio. Logs are JSON per line on **stderr**; tokens are automatically redacted.

### 6. Install the companion plugin

Some tools need the `local_sernobre_mcp` Moodle plugin (`plugin-companion/local_sernobre_mcp.zip`):

1. Upload/unzip the archive into `<moodle-root>/local/`.
2. Go to **Site administration → Notifications** to complete the install/update. The version bump also registers or updates the pre-built **Sernobre MCP** service.
3. Use that service when creating the token. Do not create a second service unless you intentionally want a different function allowlist.
4. If the server is already running, restart it after updating the plugin.

### 7. Smoke test

Run the wrapper in a terminal and ask your MCP client to call `ws_raw` with `core_webservice_get_site_info`. If you get back your Moodle site name, the wiring is correct. A token error means step 4 needs checking; an HTTP 404 usually means `MOODLE_URL` has a trailing slash or a `/webservice/...` suffix (the wrapper appends that itself).

### 8. Connect an MCP client

**Claude Desktop** — add this to `claude_desktop_config.json` (Windows: `%APPDATA%\Claude\claude_desktop_config.json`; macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "moodle": {
      "command": "npx",
      "args": ["-y", "sernobre-moodle-mcp"],
      "env": {
        "MOODLE_URL": "https://your-moodle.example.com",
        "MOODLE_WS_TOKEN": "paste-your-token-here",
        "MCP_LOG_LEVEL": "info"
      }
    }
  }
}
```

To use your local build instead of the published package, point `command` at `node` with `args: ["K:\\Formação\\moodle\\moodle-mcp-sernobre\\dist\\index.js"]`.

Restart Claude Desktop and confirm the `moodle` server is listed as connected. See the full guide in [examples/setup-claude-desktop.md](examples/setup-claude-desktop.md).

**Codex (CLI)** — Codex configures MCP servers as TOML tables in `~/.codex/config.toml` (user-wide) or `.codex/config.toml` (project-scoped, trusted projects only). You do **not** run `npm start` yourself — Codex launches the server as a subprocess.

Run the published package from npm:

```toml
[mcp_servers.moodle]
command = "npx"
args = ["-y", "sernobre-moodle-mcp"]
env = { MOODLE_URL = "https://your-moodle.example.com", MOODLE_WS_TOKEN = "paste-your-token-here", MCP_LOG_LEVEL = "info" }
startup_timeout_sec = 20
```

Or add it with the Codex CLI (recommended — it writes the TOML for you):

```bash
codex mcp add moodle --env MOODLE_URL=https://your-moodle.example.com --env MOODLE_WS_TOKEN=your-token -- npx -y sernobre-moodle-mcp
```

To use your local build instead of the published package, set `command = "node"` with `args = ["K:\\Formação\\moodle\\moodle-mcp-sernobre\\dist\\index.js"]`.

Notes:

- Rebuild with `npm run build` after any source change if you use the local `dist/index.js` path.
- Use an absolute path to `node` if Codex cannot find it on PATH.
- Rather than hardcoding the token in `env`, you can forward it from your shell with `env_vars = ["MOODLE_WS_TOKEN"]` and export it before launching Codex.
- Verify with `codex mcp list`; raise `startup_timeout_sec` if the server boots slowly.

**Other MCP clients** — configure the client to launch `npx -y sernobre-moodle-mcp` (or `node dist/index.js` for a local build) over stdio with the same environment variables. The server exposes its tools via the standard MCP `tools/list` and `tools/call` capabilities.

## Available tools

### Courses
`create_course`, `update_course`, `duplicate_course`, `archive_course`, `list_my_courses`, `get_course_context`

### Sections
`create_section`, `update_section`, `create_sections`, `delete_sections`, `move_section`, `get_sections`, `update_sections`, `duplicate_section`, `reorder_sections`, `hide_section`, `release_section`

### Content
`publish_class_lesson`, `create_activity`, `update_activity`, `publish_preview`, `confirm_preview`, `delete_resource`, `generate_video`

### Assessment
`configure_quiz`, `create_quiz`, `update_quiz`, `import_gift`, `modify_question`, `get_quiz_questions`, `publish_exam_lesson`

### Communication
`create_forum`, `create_forum_announcement`, `send_moodle_message`, `get_site_info`, `get_course_logs`

### Gradebook
`get_grades`, `get_assign_submissions`, `get_assignment_config`, `get_completion`, `get_quiz_attempts`, `grade_manually`, `submit_assignment_file`

### Students
`list_students`, `enrol_csv`, `unenrol_student`, `change_role`, `groups`, `reset_password`

### Other
`list_user_badges`, `create_calendar_event`, `update_event`, `delete_event`, `list_calendar_events`, `ws_raw`

## Lesson example

The repository includes a Markdown lesson example in [examples/lesson-example.md](examples/lesson-example.md), used with the `publish_class_lesson` tool.

## Development

```bash
npm run dev          # watch build
npm run typecheck    # type checking
npm run test         # unit tests
npm run test:integration  # integration tests (requires Moodle)
npm run build        # production build
```

## Repository structure

```
src/                    # TypeScript wrapper (MCP server)
  tools/                # tool implementations
  client/               # Moodle Web Services API client
  schemas/              # validation (zod)
  utils/                # idempotency, logging, rate limit, markdown→HTML
plugin-companion/       # Moodle plugin local_sernobre_mcp (PHP)
docs/                   # technical docs and roadmap
examples/               # examples (lesson, Claude Desktop setup)
tests/                  # tests
```

## License

MIT — see [LICENSE](LICENSE).

The original code this project is based on belongs to [marcosnahuel/moodle-mcp](https://github.com/marcosnahuel/moodle-mcp).

TDQS

A3.5/5.0

Scored across 45 tools

Disambiguation4/5

Most tools have clear, distinct purposes with detailed descriptions. A few pairs like publish_class_lesson/publish_preview and configure_quiz/publish_exam_lesson overlap partially, but the descriptions explicitly guide which to use and when.

Naming Consistency4/5

The vast majority follow a verb_noun snake_case pattern (create_section, list_students, delete_event). Minor inconsistencies exist, such as mixing list_ and get_ prefixes for retrieval operations and the non-conforming ws_raw, but these are infrequent and do not impede readability.

Tool Count2/5

With 45 tools, the set is significantly larger than the ideal 3-15 range and exceeds the 25+ threshold where tools become unwieldy for an agent to navigate. While the Moodle domain is broad, many tools are highly granular (e.g., generate_video, reset_password) and the volume feels excessive rather than well-scoped.

Completeness4/5

The tool surface covers a wide range of Moodle operations: course CRUD, sections, quizzes, assignments, enrolments, groups, forums, calendar, and messaging. Notable gaps exist, including single-user creation (only via CSV), course deletion (only archive), assignment creation, and badge awarding (explicitly deferred), but these are workable for most core workflows.

Maintenance

ActivityMaintained
ResponsivenessNo issues