Skip to main content
Glama
README.md
# UFSC Moodle MCP Server (`ufsc-moodle-mcp` v1.3.0)

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Node.js](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
[![MCP](https://img.shields.io/badge/MCP-2.0-orange.svg)](https://modelcontextprotocol.io/)

Servidor **Model Context Protocol (MCP)** em TypeScript desenvolvido para o **Moodle da UFSC** (`https://presencial.moodle.ufsc.br`), com suporte a **Streamable HTTP** (para integração direta com **ChatGPT**, **Codex**, **Claude**, **Orca/Antigravity** e agentes autônomos) e **Stdio**.

## Novidades da Versão 1.3.0

1. **Atualização Atômica e Verificada de Recursos Didáticos em PDF (`mod_resource`):**
   - **Integração sem Navegador com Google Drive MCP:** Permite ao ChatGPT ou agente de IA exportar uma apresentação do Google Drive para `application/pdf` e substituir diretamente o arquivo em um recurso de disciplina no Moodle (`mod_resource`), sem qualquer necessidade de automação de navegador (Playwright/Puppeteer).
   - **Preservação Rígida da Identidade do Módulo:** O `courseModuleId` (`cmid`), a URL pública de acesso (`https://presencial.moodle.ufsc.br/mod/resource/view.php?id=...`), a seção e as configurações de visibilidade do recurso são rigorosamente preservadas.
   - **Estratégia Híbrida de Upload e Atualização:** Utiliza a rota oficial `/webservice/upload.php` para envio seguro à área de rascunhos (`draftfile`) associada ao token e atualiza o recurso via controlador nativo `/course/modedit.php`, invalidando caches e registrando o histórico.
   - **Validação Estrutural e Criptográfica de PDFs:** Valida assinatura de bytes mágica (`%PDF-`), marcador de término (`%%EOF`), parse sintático e contagem de páginas via `unpdf`, ausência de criptografia/senha e calcula o hash SHA-256 canônico.
   - **Trava de Proteção contra Troca Incorreta (`expectedCurrentFilename`):** Parâmetro de salvaguarda que impede a substituição se o nome do arquivo atual no Moodle divergir do esperado pelo usuário.
   - **Simulação com `dryRun: true`:** Executa todas as validações de autorização, integridade do PDF e inspeção do recurso sem realizar nenhuma gravação no Moodle.
   - **Garantia de Idempotência:** Chave `operationId` para prevenir gravações repetidas em retentativas de rede.
   - **Ferramenta Independente `verify_course_resource_file`:** Baixa o arquivo do Moodle de forma isolada, confere integridade do PDF e valida o hash SHA-256 e tamanho com os valores esperados.
   - **Trilha de Auditoria Auditável:** Log persistente em `/config/resource-audit.jsonl` com timestamp, actor, cmid, hashes antes/depois e confirmação de integridade.

---

## Novidades da Versão 1.2.1

1. **OCR Dinâmico Proporcional com Fallback de Idiomas e `textExtractionComplete`:**
   - **Budget Proporcional ao Documento:** O timeout global de OCR é calculado dinamicamente com base nas páginas elegíveis (`numPagesToOcr * pageTimeoutMs`), garantindo no mínimo 60 segundos por página (ex.: até 120s para 2 páginas escaneadas, até 300s para 5 páginas), sem abortar prematuramente documentos digitalizados densos.
   - **Timeout por Página Configurável (`ocrPageTimeoutMs`):** Parâmetro explícito nas ferramentas `read_moodle_file` e `get_submission_content`, com valor padrão de 60.000 ms e variável de ambiente `MOODLE_OCR_PAGE_TIMEOUT_MS`.
   - **Fallback Controlado de Idiomas:** Se o OCR falhar ou sofrer timeout com o conjunto multi-idioma (ex.: `eng+por`), o servidor executa retry automático utilizando o idioma primário (`eng`) caso ainda reste budget útil (≥ 10s), reutilizando a imagem PNG renderizada sem custo de novo `pdftoppm`.
   - **Sinalização Explícita de Completude (`textExtractionComplete`):** Campo booleano de alto nível que indica se todas as páginas do documento foram lidas com sucesso (seja via texto nativo ou via OCR), permitindo ao cliente distinguir instantaneamente extrações completas de parciais.
   - **Reuso de Arquivos em Disco (`fileId`):** Permite reprocessar arquivos temporários com outros parâmetros de OCR sem refazer o download a partir do Moodle.

2. **Tratamento Otimizado de Avaliações Sem Alterações:**
   - **Detecção Rigorosa de No-Op:** Na ferramenta `prepare_grading_batch`, se os valores propostos (nota e/ou feedback) forem equivalentes aos já registrados no Moodle (ou se um campo for omitido mantendo o valor existente), o plano marca as ações como `preserve` (`gradeAction: 'preserve'`, `feedbackAction: 'preserve'`, `gradeChanged: false`, `feedbackChanged: false`, `anyEffectiveChange: false`).
   - **Status `prepared_no_changes`:** Quando nenhuma alteração efetiva for detectada no lote, `status` é retornado como `"prepared_no_changes"` e `readyToCommit` como `false`.
   - **Commit Protegido sem Escrita (`unchanged_no_action_required`):** Se `commit_grading_batch` for acionado com um plano sem mudanças efetivas, o servidor não executa chamadas Web Service de escrita (`mod_assign_save_grade`), registra a auditoria com `operationStatus: 'unchanged_no_action_required'` e retorna `writesPerformed: 0` de forma segura e idempotente.

3. **Garantia Transparente de Autorização Humana em 4 Níveis:**
   - A resposta de `prepare_grading_batch` expõe a estrutura `humanApprovalRequired.levels`, esclarecendo categoricamente a separação entre:
     - `technicalValidation`: Validação estrutural de tipos, limites e integridade de identificadores.
     - `planIntegrity`: Integridade criptográfica do plano aprovado via `approvedPlanHash` (SHA-256 canônico).
     - `clientConfirmation`: Flag declarada pelo cliente (`confirmedByReviewer: true`) que atesta que o chamador revisou o plano.
     - `humanAuthorization`: Prerrogativa indelegável e exclusiva do docente/professor humano. O agente de IA **não pode se autoautorizar** a publicar notas ou feedbacks.

---

## Funcionalidades Principais da Série 1.2.x

1. **Resolução Definitiva de Grupos de Tarefas Coletivas:**
   - Separa explicitamente agrupamentos gerais de turma (ex.: `62318` - `"INE5458-08208 (20262)"`, que contêm docentes e monitores) dos grupos reais de projeto (`assignmentGroupId`, `assignmentGroupName`, `assignmentGroupMembers`).
   - Nunca inclui professores ou estudantes de outras turmas entre os integrantes da entrega coletiva.
   - Detecta e reporta deterministicamente ambiguidade quando um estudante participa de múltiplos grupos na tarefa (`groupResolutionStatus: 'ambiguous'`, `ambiguousGroupCandidates`), sem escolher silenciosamente o primeiro grupo retornado pelo Moodle.
   - Suporta consulta transparente por `studentId` ou diretamente por `groupId`.
   - Trata o retorno numérico `lastattempt.submissiongroup: 67199` do Moodle 4.1.5+.

2. **Representação Fiel de Avaliações em Grupo:**
   - Separa o estado da linha coletiva com `userid=0` (onde o Moodle reporta `gradingstatus: "notgraded"`) do estado agregado real dos integrantes (`aggregatedGradingStatus: 'fully_graded' | 'partially_graded' | 'not_graded' | 'inconsistent'`).
   - Expõe nota comum (`commonGroupGrade`), alerta de divergência de notas entre integrantes (`hasGradeDivergence`), workflow e visibilidade.

3. **OCR Local Automático no Servidor com Layout Visual Estruturado:**
   - Integra Poppler (`pdftoppm`) e Tesseract (`tesseract`) locais em container Alpine sem envio de documentos acadêmicos para terceiros.
   - Modos: `off` (padrão compatível), `auto` (preserva texto nativo e executa OCR apenas nas páginas escaneadas/sem texto), `force` (OCR em todas as páginas) e `status` (diagnóstico sem execução).
   - Suporta documentos mistos (páginas nativas + páginas escaneadas) sem duplicação de texto.
   - Extrai blocos visuais posicionados (`bbox`, coordenadas normalizadas `normX`/`normY`, região espacial 3x3 na página, ordem de leitura e confiança).
   - Protegido por semáforo de concorrência, clamping de DPI contra canvas bombs, timeouts com `SIGKILL` e limpeza garantida de diretórios temporários `0700`.

4. **Visão Consolidada em Lote (`get_assignment_grading_overview`):**
   - Agrupa em poucas chamadas Web Service todas as informações da turma: grupos, membros, arquivos deduplicados por `fileKey`, notas, comentários de avaliação, workflow, publicação e inconsistências, com paginação (`limit`/`cursor`) e proteção de PII.

5. **Fluxo Auditado e Controlado de Avaliações (`prepare` -> Revisão Docente -> `commit` -> `verify`):**
   - `prepare_grading_batch`: Operação estritamente **somente-leitura**, zero escritas no Moodle. Calcula diff, preserva notas quando apenas feedback é enviado, preserva feedback quando apenas nota é enviada, detecta conflitos e gera `planId` + `planHash` (SHA-256).
   - `commit_grading_batch`: Exige `planId`, `approvedPlanHash`, `confirmedByReviewer: true`, `operationId`, `publicationState` e `notificationPolicy`. Bloqueia `stale_plan_conflict` se houve alteração no Moodle pós-preparação. Grava exclusivamente em `assignfeedbackcomments_editor` (`gradingFeedbackComments`). Idempotência garantida e recuperação de falha parcial com `resumeFailedOnly`.
   - `verify_grading_batch`: Reconsulta o Moodle em tempo real (`fresh: true`) e audita se notas, feedbacks, workflow e visibilidade conferem rigorosamente com o plano aprovado.
   - Trilha de auditoria persistente redigida em JSONL (`MOODLE_GRADING_AUDIT_LOG_PATH`), registrando hashes SHA-256 e sem vazar segredos ou tokens.

6. **Descoberta e Portabilidade MCP (`get_moodle_capabilities`):**
   - Schemas portáveis sem `format: "uri"` (evitando que conectores ChatGPT/OpenAI filtrem parâmetros de URL).
   - Notificações de mudança de ferramentas ativas (`listChanged: true`).
   - Ferramenta de introspecção `get_moodle_capabilities`.

---

## Catálogo Completo de Ferramentas (30 Tools)

| # | Ferramenta | Descrição |
| :- | :--- | :--- |
| 1 | `get_moodle_capabilities` | Metadados do servidor, versão (v1.3.0), suporte a OCR, limites, capacidades e guia de refresh. |
| 2 | `get_site_info` | Dados do usuário autenticado no Moodle (`userid`, nome, `siteurl`, release). |
| 3 | `get_courses` | Lista disciplinas acessíveis com filtro por código/semestre (ex.: `"INE5458"`, `"20262"`). |
| 4 | `get_course_contents` | Lista seções, tópicos, arquivos (PDFs, slides) e módulos da disciplina. |
| 5 | `get_students` | Lista participantes da disciplina com matrícula, papéis e grupos. |
| 6 | `get_assignments` | Lista tarefas da disciplina com enunciado, anexos, datas e flags de workflow/teamsubmission. |
| 7 | `get_submissions` | Lista submissões individuais e coletivas em lote, com notas e grupos resolvidos. |
| 8 | `get_submission_content` | Conteúdo detalhado da entrega por `studentId` ou `groupId`, com OCR automático opcional e layout visual. |
| 9 | `download_moodle_file` | Baixa e preserva os bytes originais do arquivo com URL temporária autenticada por token de capacidade de 256 bits e URI MCP. |
| 10 | `read_moodle_file` | Baixa e extrai texto nativo ou executa OCR local (por `fileurl` ou reusando `fileId` temporário sem novo download). |
| 11 | `get_assignment_grading_overview` | Visão consolidada em lote de entregas, grupos, notas, feedbacks, workflow e inconsistências. |
| 12 | `prepare_grading_batch` | **Somente-leitura.** Valida e prepara plano de avaliação em lote com diff, cálculo de hashes e detecção de conflitos. |
| 13 | `commit_grading_batch` | Publica o plano aprovado pelo professor com concorrência otimista, idempotência e controle de workflow/notificação. |
| 14 | `verify_grading_batch` | Consulta ao vivo o Moodle para auditar notas, feedbacks, workflow e visibilidade pós-publicação. |
| 15 | `provide_feedback` | Avaliação individual pontual segura, preservando campos omitidos (`grade` ou `feedback`). |
| 16 | `get_quizzes` | Lista questionários da disciplina. |
| 17 | `get_quiz_grade` | Consulta notas e tentativas de um aluno em um questionário. |
| 18 | `send_message` | Envia mensagem instantânea direta para um aluno no Moodle. |
| 19 | `get_messages` | Lista mensagens diretas e notificações recebidas/enviadas. |
| 20 | `get_forums` | Lista fóruns da disciplina (*Avisos*, *Dúvidas*). |
| 21 | `get_forum_discussions` | Lista tópicos de um fórum ou lê postagens de uma discussão. |
| 22 | `post_forum_discussion` | Publica aviso/tópico ou responde a postagem em fórum. |
| 23 | `get_course_groups` | Lista grupos da disciplina, participantes, agrupamentos e regras `mod_groupselect`. |
| 24 | `get_group_members` | Consulta participantes confirmados e vagas de um grupo. |
| 25 | `create_course_group` | Cria novo grupo na disciplina (com nome escolhido ou único gerado automaticamente). |
| 26 | `add_group_member` | Adiciona participante(s) a um grupo com validação de matrícula e capacidade. |
| 27 | `remove_group_member` | Remove participante(s) de um grupo de forma idempotente e auditada. |
| 28 | `create_group_with_members` | Operação composta para criar grupo e matricular alunos em uma única chamada. |
| 29 | `replace_course_resource_file` | Substitui com segurança e validação atômica o PDF de um recurso `mod_resource` existente (ex.: exportado pelo Google Drive MCP). Preserva cmid e URL pública, suporta `dryRun`, verificação e idempotência. |
| 30 | `verify_course_resource_file` | Reconsulta ao vivo o Moodle, efetua download independente, valida integridade do PDF e confere tamanho e hash SHA-256 com os esperados. |

---

## Fluxo Recomendado para Avaliação de Trabalhos

```mermaid
flowchart TD
    A["1. Identificar Tarefas e Grupos<br/>(get_assignments, get_assignment_grading_overview)"] --> B["2. Obter Entregas e OCR Visual<br/>(get_submission_content com ocrMode='auto')"]
    B --> C["3. Análise Pedagógica pelo Agente/Professor"]
    C --> D["4. Preparar Lote (SOMENTE-LEITURA)<br/>(prepare_grading_batch)"]
    D --> E["5. Revisão Humana Obrigatória<br/>(Apresentação da prévia ao Professor)"]
    E --> F["6. Publicação Autorizada<br/>(commit_grading_batch com approvedPlanHash)"]
    F --> G["7. Verificação Independente ao Vivo<br/>(verify_grading_batch)"]
```

---

## Variáveis de Ambiente

| Variável | Padrão | Descrição |
| :--- | :--- | :--- |
| `MOODLE_URL` | `https://presencial.moodle.ufsc.br` | URL base do Moodle da UFSC. |
| `MOODLE_API_TOKEN` | — | Token Web Service estático (`moodle_mobile_app`). |
| `MOODLE_USERNAME` / `MOODLE_PASSWORD` | — | Credenciais CAS UFSC para obtenção dinâmica de token. |
| `PORT` | `8088` | Porta do servidor HTTP MCP. |
| `MCP_AUTH_TOKEN` | — | Token Bearer obrigatório para autenticar clientes MCP. |
| `PUBLIC_BASE_URL` | — | URL pública externa do proxy (ex.: `https://n8n.labsec.ufsc.br/mcp/moodle`). |
| `MOODLE_OCR_ENABLED` | `true` | Habilita o processamento local de OCR. |
| `MOODLE_OCR_LANGUAGES` | `eng,por` | Idiomas padrão do Tesseract. |
| `MOODLE_OCR_MAX_PAGES` | `30` | Limite máximo de páginas por arquivo para OCR. |
| `MOODLE_OCR_DPI` | `200` | Resolução de rasterização Poppler. |
| `MOODLE_OCR_TIMEOUT_MS` | `120000` | Timeout global base de OCR por documento (ajustado dinamicamente pelo número de páginas). |
| `MOODLE_OCR_PAGE_TIMEOUT_MS` | `60000` | Timeout individual de OCR por página renderizada. |
| `MOODLE_OCR_MAX_CONCURRENCY` | `2` | Limite de processos de OCR concorrentes. |
| `MOODLE_GRADING_AUDIT_LOG_PATH` | `/config/grading-audit.jsonl` | Caminho do log JSONL de auditoria de avaliações. |
| `MOODLE_GRADING_STATE_DIR` | `/config/grading-state` | Diretório para persistência de operações em lote. |

---

## Instalação e Execução

### Pré-requisitos
- **Node.js**: Versão 22.x ou superior
- **Poppler & Tesseract** (opcionais, necessários apenas para OCR local de PDFs escaneados):
  - Ubuntu/Debian: `sudo apt-get install poppler-utils tesseract-ocr tesseract-ocr-por tesseract-ocr-eng`
  - macOS: `brew install poppler tesseract tesseract-lang`

### 1. Execução Local via Stdio (Claude Desktop / Cursor)

```bash
# Clonar o repositório
git clone https://github.com/jeanmartina/ufsc-moodle-mcp.git
cd ufsc-moodle-mcp

# Instalar dependências e compilar
npm install
npm run build

# Executar via Stdio
node dist/index.js
```

### 2. Execução como Servidor Streamable HTTP / SSE (ChatGPT / Webhooks / Agentes)

```bash
# Definir variáveis de ambiente no arquivo .env
cp .env.example .env

# Iniciar o servidor HTTP na porta 8088
npm run serve
```

---

## Guia de Configuração com Clientes MCP

### Claude Desktop

Adicione ao seu `claude_desktop_config.json` (no macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; no Linux/Windows: `~/.config/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "moodle-ufsc": {
      "command": "node",
      "args": ["/caminho/absoluto/para/ufsc-moodle-mcp/dist/index.js"],
      "env": {
        "MOODLE_URL": "https://presencial.moodle.ufsc.br",
        "MOODLE_API_TOKEN": "seu_token_webservice_moodle",
        "MOODLE_USERNAME": "seu.usuario",
        "MOODLE_PASSWORD": "sua_senha"
      }
    }
  }
}
```

### Cursor (`.cursor/mcp.json`)

```json
{
  "mcpServers": {
    "moodle-ufsc": {
      "command": "node",
      "args": ["/caminho/absoluto/para/ufsc-moodle-mcp/dist/index.js"],
      "env": {
        "MOODLE_URL": "https://presencial.moodle.ufsc.br",
        "MOODLE_API_TOKEN": "seu_token_webservice_moodle"
      }
    }
  }
}
```

### ChatGPT (Streamable HTTP Conector)

1. Inicie o servidor HTTP com uma chave secreta:
   ```bash
   PORT=8088 MCP_AUTH_TOKEN=seu_token_bearer node http-server.mjs
   ```
2. No ChatGPT, configure uma Custom Action ou MCP Connector apontando para o endpoint HTTPS do seu proxy (ou tunnel):
   - **URL:** `https://seu-servidor.dominio.br/mcp/moodle`
   - **Authentication:** Bearer Token (`seu_token_bearer`)

---

## Execução via Docker

O repositório já inclui um `Dockerfile` multi-stage otimizado baseado em Alpine Linux com Poppler e Tesseract pré-instalados:

```bash
# Construir a imagem Docker
docker build -t ufsc-moodle-mcp:latest .

# Executar o container HTTP
docker run -d \
  --name ufsc-moodle-mcp \
  -p 8088:8088 \
  -e MOODLE_URL=https://presencial.moodle.ufsc.br \
  -e MOODLE_API_TOKEN=seu_token \
  -e MCP_AUTH_TOKEN=seu_bearer_token \
  ufsc-moodle-mcp:latest
```

---

## Atualização de PDFs em Recursos Didáticos (`mod_resource`) via Google Drive MCP

Para substituir arquivos de aula (slides, apostilas, ementas) no Moodle sem automação de navegador:

```mermaid
flowchart LR
    A["1. Google Drive MCP<br/>(export_file -> PDF URI)"] --> B["2. Simulação Moodle MCP<br/>(replace_course_resource_file com dryRun=true)"]
    B --> C["3. Substituição Atômica<br/>(replace_course_resource_file com dryRun=false)"]
    C --> D["4. Verificação Independente<br/>(verify_course_resource_file)"]
```

### Exemplo de Chamada via ChatGPT / Agente:

```json
{
  "name": "replace_course_resource_file",
  "arguments": {
    "courseId": 49476,
    "courseModuleId": 1564872,
    "expectedCurrentFilename": "INE5458 -  DeFi and On Chain Finance.pdf",
    "sourceFileUri": "https://docs.google.com/presentation/d/12ETsNXe2fZLpThBLwy0EucWfC8rjtccnNwQsFWfeCaA/export/pdf",
    "dryRun": false,
    "operationId": "replace-defi-slides-20261107"
  }
}
```

O Moodle MCP realiza:
1. Validação do papel docente de edição do usuário no curso (`checkCourseEditingPermission`).
2. Validação se o módulo existe e é do tipo `mod_resource`.
3. Validação do arquivo atual contra a trava de segurança `expectedCurrentFilename`.
4. Resolução da origem (Google Drive HTTPS export, Data URI, Base64 ou arquivo local).
5. Validação estrutural do PDF (magic bytes `%PDF-`, finalizador `%%EOF`, integridade e contagem de páginas com `unpdf`, conferência de ausência de senha/criptografia).
6. Upload do novo arquivo para a área de rascunho (`draftfile`) do Moodle.
7. Atualização atômica do módulo via `/course/modedit.php`, preservando intactos o `courseModuleId` e o link público de acesso.
8. Reconsulta e conferência em tempo real dos metadados e hash SHA-256 no Moodle.
9. Registro da operação na trilha de auditoria `/config/resource-audit.jsonl`.

---

## Como Atualizar o Catálogo em Clientes MCP (ChatGPT Connectors)

Clientes como o ChatGPT mantêm cache local das ferramentas descobertas via `tools/list`:
1. Acesse **Configurações -> Conectores / Apps -> UFSC Moodle MCP**.
2. Clique em **Atualizar ferramentas** (*Refresh tools*) ou reconecte o conector.
3. Inicie uma **nova conversa** para que o assistente consulte o catálogo v1.3.0 atualizado.

---

## 🧪 Testes Automatizados

O repositório conta com suíte abrangente de testes unitários e de integração utilizando [Vitest](https://vitest.dev/):

```bash
npm test
```

---

## 📄 Licença

Distribuído sob a licença **MIT**. Consulte o arquivo [LICENSE](LICENSE) para mais informações.

TDQS

A3.6/5.0

Scored across 28 tools

Disambiguation4/5

Most tools have clearly distinct resource+action targets, and the read/write boundary is well documented (e.g. prepare vs commit vs verify in the grading flow). A few clusters still overlap: get_submissions, get_submission_content and get_assignment_grading_overview all return submission/grade data at different granularities, and get_course_groups vs get_group_members both return member lists. provide_feedback also overlaps in outcome with the batch grading tools, though its description explicitly redirects bulk cases to prepare/commit.

Naming Consistency4/5

Nearly all names follow a snake_case verb_noun pattern (get_courses, create_course_group, add_group_member, post_forum_discussion, download_moodle_file), which is predictable and readable. Minor deviations exist: «provide_feedback» uses a less standard verb than send_message/post_forum_discussion for what is effectively an update operation, and some get_* names are long compound phrases (get_assignment_grading_overview, get_moodle_capabilities) breaking the simple verb_noun shape.

Tool Count3/5

At 28 tools the surface sits above the comfortable 3-15 band and feels heavy, with six group-management tools and seven grading/submission tools. The breadth is partly justified because the server spans genuinely separate domains (grading workflows, group management, files/OCR, forums, messaging), but a few read variants (get_submissions vs get_assignment_grading_overview vs get_submission_content) inflate the count more than necessary.

Completeness4/5

The core teaching lifecycle is well covered: discover courses/contents/students, read assignments/quizzes/submissions, grade individually or via a guarded batch flow, manage groups and members, post to forums, send messages, and fetch/read files with OCR. The main gaps are the absence of a delete/update group operation (create/add/remove exist but no remove_course_group) and no edit/delete for forum posts or messages, which could dead-end some management workflows.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues