moodle-ai-mcp

moodle-ai-mcp

Enables AI clients to get structured, read-only answers about a Moodle site: identity, capabilities, reachable external functions, and installed H5P libraries with JSON Schema conversions, for safe course and content inspection.

Category
访问服务器

README

moodle-ai-mcp

An AI-native MCP control plane for Moodle.

An MCP client (Claude Code, ChatGPT, Cursor, or anything else that speaks the Model Context Protocol) connects to this server and gets structured, accurate answers about a real Moodle site: what it is, who the connection is authenticated as, what it is allowed to do, which of Moodle's external functions it can reach, and — the part that makes it more than a REST wrapper — exactly which H5P libraries the site has installed and what their content schemas are.

This is not a thin wrapper around Moodle REST. The long-term goal is a control plane an AI client can use to design and build entire courses safely. This repository currently contains the first foundation of that.

Current maturity: read-side foundation, plus three narrow curated writes

Working today:

  • MCP server on stdio with ten curated tools, built on the official MCP TypeScript SDK
  • A Moodle 5.2 local plugin (local_aimcp) with thirteen external functions — ten read-declared and three narrow writes — real capability enforcement and PHPUnit coverage
  • A write-policy substrate every curated mutation passes through: plan and apply, optimistic concurrency against a locked row, durable idempotency and Moodle-native audit, all enforced inside Moodle — plus a cryptographic confirmation handshake enforced in the MCP server. Proven across three different targets in three different tables, each with its own capability
  • A capability-aware course read model: sections, activities, completion and grade configuration, reflecting what the authenticated identity may actually see rather than everything with a hidden flag attached
  • Dynamic discovery of the external functions the authenticated service can reach, with lossless signature introspection
  • Dynamic discovery of installed H5P libraries and their real installed semantics, converted to JSON Schema with explicit notes for everything JSON Schema cannot express

Not built, on purpose:

  • broad course or activity CRUD — course creation, and creating, deleting or moving sections and activities
  • arbitrary Moodle external-function execution, and any generic write executor
  • H5P authoring, and quiz or question-bank authoring
  • the Course Blueprint engine
  • browser QA and learner simulation
  • a remote HTTP transport with OAuth, and hosting infrastructure

The three writes that do exist are narrow, curated and individually allowlisted; each edits named fields of one existing object. See "Limitations" below.

Architecture

AI client  --MCP/stdio-->  apps/mcp-server (TypeScript, MIT)
                                 |
                                 |  authenticated Moodle web service call
                                 v
                           moodle/local/aimcp (Moodle plugin, GPL-3.0-or-later)
                                 |
                                 v
                           Moodle 5.2 core + H5P core

The server owns the protocol, the tool surface, orchestration and schema conversion. The plugin owns everything that only Moodle can answer: identity, context, capabilities, the external-function registry and the H5P engine. Orchestration never leaks into PHP, and the rule is that Moodle logic is not reimplemented in TypeScript — when PHP can decide something authoritatively, the server asks PHP. One place still falls short of that rule: the moodle_course_update_summary input schema caps the summary length and the format constant locally, which moodle_section_update_name deliberately does not do for its own limit.

Details, including why the tool surface is a curated handful rather than several hundred, are in docs/ARCHITECTURE.md.

Prerequisites

  • Node.js 24
  • Docker, with a Moodle 5.2 stack from moodle-docker
  • A Moodle web service token for a user authorised on an enabled external service

Local development

Full instructions: docs/LOCAL-DEV.md. The short version:

cd ~/DEV/moodle-ai/moodle-ai-mcp

# 1. Start the Moodle stack (installs the persistence override, mounts the plugin)
./scripts/stack.sh start

# 2. Register the plugin with Moodle
docker exec -u www-data -w /var/www/html moodle-ai-webserver-1 \
  php admin/cli/upgrade.php --non-interactive

# 3. Attach the plugin's functions to your external service (idempotent)
docker exec -u www-data -w /var/www/html moodle-ai-webserver-1 \
  php public/local/aimcp/cli/provision_service.php --service=moodle_ai_mcp_dev

# 4. Build and run the server
npm install
npm run build
./scripts/run-server.sh

The database, moodledata and installed H5P libraries live in named Docker volumes, so ./scripts/stack.sh recreate is safe. Only ./scripts/stack.sh reset destroys data, and it asks first. Back up any time with ./scripts/backup.sh.

Credentials come from .env.local, which is a symlink to a file outside this repository. .env* is gitignored; see docs/SECURITY.md.

Connecting an MCP client

claude mcp add moodle-ai --scope local -- \
  /absolute/path/to/moodle-ai-mcp/scripts/run-server.sh

Or with the Inspector:

npx @modelcontextprotocol/inspector ./scripts/run-server.sh

Tools

Tool What it answers
moodle_site_inspect What Moodle is this, who am I connected as, what can that identity do, what plugins and H5P are available.
moodle_course_list Which courses exist and are visible to this identity, optionally searched.
moodle_course_inspect The structure of one course: sections in order, activities in course-page order, completion configuration and grade item configuration. Omits what the caller may not see, gates course-management fields (raw availability rules, module ID numbers) behind Moodle's own editor capabilities, and says how much it withheld.
moodle_functions_search Which of Moodle's external functions can this connection reach, ranked by relevance. Discovered live, never from a built-in list.
moodle_functions_describe The full signature of one function: Moodle's own parameter and return tree, plus generated JSON Schema and conversion notes.
moodle_h5p_types Which H5P libraries are installed, at which exact versions, which are runnable content types, which are dependency-only, and which Moodle currently offers for authoring.
moodle_h5p_schema The installed semantics for one H5P library version, plus generated JSON Schema and notes for everything H5P expresses that JSON Schema cannot.
moodle_course_update_summary Writes. Replaces one course's summary. No length limit is imposed, because Moodle imposes none. The text format is Moodle's to accept or refuse, and it refuses at the planning step, so a format it could not render never receives a confirmation token.
moodle_section_update_name Writes. Renames one course section. An empty name clears the custom name, so the format default is shown again. HTML is refused rather than stripped, and refused at the planning step, so an invalid name never receives a confirmation token.
moodle_activity_set_visibility Writes. Sets one activity's structural show/hide state on the course page, taking its calendar events and grade items with it, exactly as Moodle does. Showing an activity does not by itself make it reachable — availability restrictions, dates, groups, completion prerequisites and course visibility still apply. It cannot set stealth mode, and will clear it. Permission is checked on the activity itself, so a per-activity override applies. Refuses while the containing section is hidden, because Moodle owns activity visibility there and would undo the change.

Each of the three writing tools is two phases of one tool rather than two tools. Called without a confirmation token it returns a plan and changes nothing; called with the token that plan issued, plus an idempotency key, it applies. A client cannot reach the apply phase without a token the plan phase gave it.

A plan can go stale, expire or find its target ineligible later, but it is never knowingly invalid when issued: every tool that carries a value sends it to Moodle at plan time, so a value Moodle would refuse never receives a token. What counts as refusable is Moodle's answer and not this project's — a section name has a length limit and must be plain text, a course summary has no length limit at all, and only its format can be unsupported.

Seven are annotated readOnlyHint: true. The three writes are annotated readOnlyHint: false and destructiveHint: true — MCP's false means the tool "performs only additive updates", and replacing a summary, a name or a visibility state is not additive. That is the protocol's question, and it is not the same as "can this be undone": the inverse call does restore the previous value for a summary or a rename, and does not for an activity that was in stealth mode, since this tool cannot express stealth and so cannot put it back. The plan reports this project's riskClass — destructive for activity visibility, write for the other two — and warns in words before the change is confirmed. reversible is a property of the operation registry and is documented in docs/SECURITY.md; it is not a field on the plan. All ten return structuredContent and a JSON text fallback on success; an error returns a structured JSON text block instead.

There is deliberately no generic "call any Moodle function" tool, and no generic write executor. Search and describe make the long tail discoverable and report whether Moodle declares each function read or write; nothing executes a function by name. Moodle's own write label is treated as a floor, not a safety verdict — it covers editing a description and deleting a course alike.

Tests

npm --prefix apps/mcp-server run typecheck      # TypeScript, strict
npm --prefix apps/mcp-server run test:unit      # pure logic, no Moodle needed
npm --prefix apps/mcp-server run build
npm --prefix apps/mcp-server run test:integration  # real Moodle + real MCP session

./scripts/lint-plugin.sh    # php -l over the plugin
./scripts/check-plugin.sh   # Moodle coding standard (moodle-cs)
./scripts/test-plugin.sh    # PHPUnit inside the Moodle container

The integration suite is not a mock: it spawns the built server as a child process, speaks MCP to it with the official SDK client, and asserts against the live site — including that the identity is the expected Moodle user and that no token appears in any output.

Limitations

  • moodle_activity_set_visibility needs a visible parent section. While a section is hidden Moodle forces every activity in it to hidden and restores the previous value when the section is shown again, so an activity-level change there would not last. The tool returns unsupported_target_state rather than reporting a success a later section unhide would contradict. Section visibility itself is not modelled in this milestone.
  • Three narrow writes. A course summary, a section name, and an activity's visibility. Nothing else: no create, delete, enrol, grade, role, file, H5P or quiz writes, and no way to move, add or remove a section or activity. The development fixture CLI also writes, but is not reachable from any MCP client or web service (see docs/SECURITY.md).
  • No arbitrary external-function execution. Functions are discoverable and describable; none can be invoked by name.
  • Confirmation tokens are signed with a per-process key, so they do not survive an MCP server restart. Plan again after a restart — and note that a retry through the tool is then impossible, since a retry must present the same plan.
  • Idempotency guarantees at-most-once per (user, key), not exactly-once.
  • A request's identity includes the plan it was made from, so reusing a key across two plans is a conflict even when the requested content is identical.
  • Each write locks its own target row - course, course_sections or course_modules - before re-reading and comparing state, so an ordinary Moodle edit cannot slip between the check and the write. There is deliberately no lock-any-table helper. Row locking is implemented for PostgreSQL, the MySQL family and SQL Server; only PostgreSQL is covered by tests.
  • A retry reports recorded fingerprints and the current state separately; the original content is not stored, so it cannot be reported.
  • stdio only. HTTP transport is a future addition; the domain layer is already transport-free.
  • No Course Blueprint, no diff/apply engine, no content generation.
  • No browser automation, screenshots or accessibility auditing.
  • moodle_course_inspect returns course structure, not learner performance: no grades and no per-user completion state.
  • Moodle's front page is a course row but not a teaching course, so moodle_course_inspect rejects it. moodle_course_list still reports it, flagged isSiteCourse.
  • H5P schema generation is one level deep: a nested library field fixes the wrapper shape and the allowed library versions, but its params follow that library's own semantics — fetch them with a second moodle_h5p_schema call.
  • Some H5P and Moodle constructs cannot be expressed in JSON Schema (showWhen conditions, HTML tag whitelists, PCRE patterns, PARAM cleaning rules). They are preserved as x-h5p-* / x-moodle-* annotations and reported as conversion notes rather than dropped.
  • Moodle REST cannot express an empty array or a true null; the client reports both as explicit warnings to the server log — they are not returned to the MCP client.
  • The plugin is bind-mounted into the container from this repository; the rsync copy is kept only as a fallback. A host symlink does not work, for reasons explained in docs/LOCAL-DEV.md.

Licensing

  • apps/mcp-server/ — MIT
  • moodle/local/aimcp/ — GPL-3.0-or-later (required: it is a Moodle plugin)

No GPL implementation code is copied into the MIT server. Reference projects were studied as architecture references and reimplemented clean-room; the reasoning, per project, is in docs/REFERENCE-ARCHITECTURE.md.

Documentation

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选