Skip to main content
Glama

campus-unr-mcp

Servidor MCP + CLI para el Campus Virtual FCEIA UNR (Moodle).

El cliente no está atado a una sola URL: apunta a la instancia que pongas en CAMPUS_BASE_URL. Confirmado contra el campus de grado https://campusv.fceia.unr.edu.ar/ (Moodle: cookie MoodleSession, login via login/token.php, Web Services REST) y contra el de posgrado https://posgrado.campusvirtual.fceia.unr.edu.ar/ (mismo protocolo; la cantidad de funciones WS puede diferir entre instancias).

Agente Plugins spec 1.0.0 compliant (plugin.json + mcp.json en la raíz).

Estructura

src/campus_unr_mcp/
├── __init__.py
├── client.py      # CampusClient: login token + Moodle Web Services (read + write)
├── cli.py         # CLI con click + rich (22 subcomandos)
└── mcp_server.py  # Servidor MCP stdio (22 tools)
bin/campus-server  # Launcher portable para Agent Plugins
plugin.json        # Manifest Agent Plugins 1.0.0
mcp.json           # Config MCP stdio

Related MCP server: dutic-mcp

Instalación

cd campus-unr-mcp
uv sync

Configuración

Crear .env (ya existe):

CAMPUS_USER=tu_usuario
CAMPUS_PASS=tu_password
CAMPUS_BASE_URL=https://campusv.fceia.unr.edu.ar/

CAMPUS_BASE_URL es la raíz de la instancia Moodle (con / final). Credenciales y token son por instancia: el usuario de grado no tiene por qué existir en posgrado.

Requisito de la instancia destino: login/token.php y webservice/rest/server.php habilitados, con el servicio moodle_mobile_app (si el login dummy responde invalidlogin y no servicenotavailable, el servicio está activo).

Uso CLI — Lectura

# Info del sitio y usuario
uv run campus site-info

# Listar cursadas
uv run campus courses
uv run campus courses --json

# Categorías (períodos lectivos / carreras / áreas)
uv run campus categories

# Contenido de un curso (secciones y actividades)
uv run campus contents 442

# Usuarios enrolados (estudiantes)
uv run campus users 442 --role student

# Grupos (comisiones)
uv run campus groups 442

# Miembros de grupos con lista de estudiantes
uv run campus group-members 442

# Actividades agrupadas por tipo
uv run campus activities 442

# Entregas (assignments) con conteo de envíos y calificaciones
uv run campus assignments 442

# Foros de un curso
uv run campus forums 503

# Discusiones (temas) de un foro
uv run campus discussions 7119

# Calificaciones del curso (quizzes + assignments)
uv run campus course-grades 442

# Intentos de quiz (parcial/examen)
uv run campus quiz-attempts 9417

# Iniciar servidor MCP
uv run campus serve

Uso CLI — Escritura

Todas las operaciones de escritura tienen --dry-run por defecto. Pasar --no-dry-run para ejecutar.

# Renombrar un foro y definir su visibilidad (dry-run por defecto)
uv run campus update-forum 121831 "Avisos 2026 C1" --hidden
uv run campus update-forum 121831 "Avisos 2026 C1" --hidden --no-dry-run

# Cambiar el destino de una actividad URL, conservando nombre y visibilidad
uv run campus update-url 94155 "https://drive.google.com/file/d/ID/view?usp=sharing"
uv run campus update-url 94155 "https://drive.google.com/file/d/ID/view?usp=sharing" --no-dry-run

# Crear tema en foro (dry-run por defecto, valida permisos)
uv run campus post-discussion 7119 "Aviso importante" "<p>Mensaje HTML</p>"
uv run campus post-discussion 7119 "Aviso" "Mensaje" --no-dry-run   # ejecutar

# Responder a un post
uv run campus reply-post 113371 "Re: Aviso" "Respuesta" --no-dry-run

# Cargar nota de TP (0-10, con feedback opcional)
uv run campus save-grade 16255 13341 8.5 --feedback "Buen trabajo"
uv run campus save-grade 16255 13341 8.5 --feedback "Buen trabajo" --no-dry-run

# Enviar mensaje a un usuario
uv run campus send-message 13341 "Recordatorio entrega TP"

# Crear evento de calendario (fecha de examen, entrega, etc.)
uv run campus create-event "Parcial" 503 "2026-12-15T10:00:00" --description "Aula 3"

Uso como servidor MCP en Hermes

Las credenciales viven en el .env del repo (no en config.yaml). Con uv --directory <ruta-del-repo>, el cwd del proceso es el repo y se lee ese .env.

En ~/.hermes/config.yaml (ajustá la ruta al clone local):

mcp_servers:
  campus_unr:
    command: uv
    args:
      - --directory
      - /path/to/campus-unr-mcp
      - run
      - campus
      - serve
    connect_timeout: 90.0
    enabled: true
    timeout: 120

O por CLI (sin pasar flags de Hermes como --connect-timeout en --args; si no, caen como argumentos de campus serve y el server falla):

uv sync
printf 'Y\n' | hermes mcp add campus_unr --command uv \
  --args --directory /path/to/campus-unr-mcp run campus serve
hermes config set mcp_servers.campus_unr.timeout 120
hermes config set mcp_servers.campus_unr.connect_timeout 90
hermes config set mcp_servers.campus_unr.enabled true
# Si args quedó como string JSON, dejalo como lista YAML real (como arriba).
hermes mcp test campus_unr

Reiniciá la sesión CLI / el gateway de Hermes para descubrir las tools (en Telegram hace falta reiniciar el gateway).

Varias instancias Moodle (grado + posgrado)

Un proceso MCP = un CampusClient = un .env. Las credenciales se leen del cwd del proceso (el clone), no de config.yaml. Para usar dos campus a la vez hace falta dos clones (cada uno con su .env) y dos entradas en mcp_servers.

git clone https://github.com/jpmanson/campus-unr-mcp campus-unr-mcp
git clone https://github.com/jpmanson/campus-unr-mcp campus-unr-posgrado-mcp
cd campus-unr-mcp && uv sync
cd ../campus-unr-posgrado-mcp && uv sync

.env del clone de grado:

CAMPUS_USER=tu_usuario_grado
CAMPUS_PASS=tu_password_grado
CAMPUS_BASE_URL=https://campusv.fceia.unr.edu.ar/

.env del clone de posgrado:

CAMPUS_USER=tu_usuario_posgrado
CAMPUS_PASS=tu_password_posgrado
CAMPUS_BASE_URL=https://posgrado.campusvirtual.fceia.unr.edu.ar/

Verificar cada uno antes de registrar el MCP:

cd /path/to/campus-unr-mcp && uv run campus site-info
cd /path/to/campus-unr-posgrado-mcp && uv run campus site-info

En ~/.hermes/config.yaml:

mcp_servers:
  campus_unr:
    command: uv
    args:
      - --directory
      - /path/to/campus-unr-mcp
      - run
      - campus
      - serve
    connect_timeout: 90.0
    enabled: true
    timeout: 120
  campus_posgrado:
    command: uv
    args:
      - --directory
      - /path/to/campus-unr-posgrado-mcp
      - run
      - campus
      - serve
    connect_timeout: 90.0
    enabled: true
    timeout: 120

O por CLI (confirmar el prompt de guardar config si la conexión inicial falla; campus serve habla stdio y el probe de hermes mcp add a veces cierra antes):

printf 'Y\n' | hermes mcp add campus_unr --command uv \
  --args --directory /path/to/campus-unr-mcp run campus serve
printf 'Y\n' | hermes mcp add campus_posgrado --command uv \
  --args --directory /path/to/campus-unr-posgrado-mcp run campus serve
hermes config set mcp_servers.campus_unr.timeout 120
hermes config set mcp_servers.campus_posgrado.timeout 120
hermes mcp test campus_unr
hermes mcp test campus_posgrado

Las tools quedan prefijadas por el nombre del server (mcp_campus_unr_* vs mcp_campus_posgrado_*). Después de agregar servers: /reload-mcp en la sesión abierta, o una sesión nueva.

No hace falta un repo GitHub distinto por instancia: el segundo directorio es otro clone del mismo código, solo cambia el .env (que está en .gitignore).

Tools MCP disponibles (22 total)

Lectura (15)

Tool

Descripción

get_site_info

Info del sitio y usuario actual

list_courses

Cursos donde el usuario está enrolado

list_categories

Categorías (jerarquía académica)

get_course_contents

Secciones y actividades de un curso

list_enrolled_users

Usuarios enrolados con filtro por rol

list_groups

Grupos (comisiones) de un curso

list_group_members

Grupos con lista de miembros y roles

list_activities

Actividades agrupadas por tipo

list_assignments

Entregas con stats de envíos/calificaciones

get_grades_report

Reporte de calificaciones

get_assignment_submissions

Detalle de entregas de un TP

list_forums

Foros de un curso con conteo de discusiones

list_forum_discussions

Temas de un foro con contenido

get_course_grades

Todos los items de calificación del curso

list_quiz_attempts

Intentos de un quiz (parcial/examen)

Escritura (7) — dry_run=True por defecto

Tool

Descripción

update_forum

Renombrar un foro y mostrarlo/ocultarlo

update_url

Cambiar el destino de una actividad URL

create_forum_discussion

Crear tema en foro (valida permisos en dry-run)

reply_forum_post

Responder a un post existente

save_assignment_grade

Cargar nota de TP (0-10) con feedback opcional

send_message_to_user

Enviar mensaje instantáneo a un usuario

create_calendar_event

Crear evento (examen, entrega, etc.)

Cada tool de escritura acepta dry_run: bool = True. En modo dry-run valida permisos y parámetros sin ejecutar la operación. Pasar dry_run=False para ejecutar realmente.

Notas

  • El cliente usa Web Services de Moodle (REST + token), no scraping HTML. Es más rápido y estable.

  • Autenticación: login/token.php con servicio moodle_mobile_app obtiene un token de WS automáticamente.

  • SSL auto-firmado: verify_ssl=False por defecto (común en deployments universitarios).

  • El cliente httpx se cachea y persiste toda la sesión MCP, reutilizando conexiones.

  • Todos los datos de estudiantes son PII: no commitear exports ni dumps.

Available Tools

20 tools
create_calendar_eventA

Create a calendar event (fecha de examen, entrega, etc.).

By default runs in dry_run mode. Set dry_run=False to actually create.

Args: name: Event title. course_id: Course ID. timestart: ISO datetime (e.g. "2026-12-15T10:00:00") or unix timestamp. description: Optional event description. eventtype: Event type (course, user, group). Default: course. dry_run: If True (default), validate without creating.

Returns: JSON with validated status and eventid if created.

Inputs: name (required), course_id (required), timestart (required), dry_run, +2 more. Returns: result

create_forum_discussionA

Create a new discussion (tema) in a forum.

By default runs in dry_run mode (validates permissions only). Set dry_run=False to actually post.

Args: forum_id: Forum instance ID (use list_forums to get it). subject: Discussion title. message: Body text (HTML allowed). dry_run: If True (default), validate without posting.

Returns: JSON with validated status, and discussionid if posted.

Inputs: message (required), subject (required), forum_id (required), dry_run. Returns: result

get_assignment_submissionsA

List submissions for a specific assignment.

Args: assignment_id: The assignment instance ID (not course module ID).

Returns: JSON array of submissions with status, dates, and content.

Inputs: assignment_id (required). Returns: result

get_course_contentsA

Get all sections and activities/resources in a course.

Args: course_id: The Moodle course ID.

Returns: JSON array of sections, each with a list of modules (activities and resources) including type, name, and URL.

Inputs: course_id (required). Returns: result

get_course_gradesA

Get all grade items for a course (quizzes, assignments, etc.).

Returns grade data for every enrolled student, organized by grade item.

Args: course_id: The Moodle course ID.

Returns: JSON array of user grade entries with grade items and values.

Inputs: course_id (required). Returns: result

get_grades_reportA

Get grade report for a course (all users or a specific user).

Args: course_id: The Moodle course ID. user_id: Optional user ID to get grades for one student only. 0 or omitted = all enrolled users.

Returns: JSON with grade items and grades per user.

Inputs: course_id (required), user_id. Returns: result

get_site_infoA

Get site information and current user details from the campus.

Returns: JSON with site name, URL, user id, username, full name, and available web service functions count.

Inputs: no named parameters. Returns: result

list_activitiesA

List all activities in a course, grouped by type (quiz, assign, forum, etc.).

Args: course_id: The Moodle course ID.

Returns: JSON object mapping activity type -> list of activities.

Inputs: course_id (required). Returns: result

list_assignmentsB

List assignments (trabajos prácticos/entregas) with submission stats.

Args: course_id: The Moodle course ID.

Returns: JSON array of assignments with submission and grade counts.

Inputs: course_id (required). Returns: result

list_categoriesA

List course categories (períodos lectivos, carreras, áreas).

In Moodle, categories organize courses hierarchically by faculty, degree program, year, or academic period.

Returns: JSON array of categories with id, name, parent, depth, and course count.

Inputs: no named parameters. Returns: result

list_coursesA

List all courses where the current user is enrolled (cursadas).

Returns: JSON array of courses with id, shortname, fullname, category, start/end dates, and the user's roles in each.

Inputs: no named parameters. Returns: result

list_enrolled_usersA

List users enrolled in a course with their roles.

Args: course_id: The Moodle course ID. role: Optional role filter (e.g. 'student', 'editingteacher', 'teacher', 'manager'). Empty = all.

Returns: JSON array of users with id, fullname, email, and roles.

Inputs: course_id (required), role. Returns: result

list_forum_discussionsA

List discussions (temas) in a forum, including message content.

Args: forum_id: The forum instance ID (use list_forums to get it).

Returns: JSON array of discussions with subject, author, message, reply count, and creation/modification dates.

Inputs: forum_id (required). Returns: result

list_forumsA

List forums in a course with discussion counts.

Args: course_id: The Moodle course ID.

Returns: JSON array of forums with id, name, cmid, and discussion count.

Inputs: course_id (required). Returns: result

list_group_membersA

List groups (comisiones) with their member lists.

Args: course_id: The Moodle course ID.

Returns: JSON array of groups, each with name, member count, and a list of members (userid, fullname, email, roles).

Inputs: course_id (required). Returns: result

list_groupsA

List groups (comisiones) in a course.

Args: course_id: The Moodle course ID.

Returns: JSON array of groups with id, name, and description.

Inputs: course_id (required). Returns: result

list_quiz_attemptsA

List attempts for a quiz (parcial/examen).

Args: quiz_id: The quiz instance ID. user_id: Optional user filter (0 = current user only).

Returns: JSON array of attempts with state, score, and timestamps.

Inputs: quiz_id (required), user_id. Returns: result

reply_forum_postA

Reply to an existing forum post.

By default runs in dry_run mode. Set dry_run=False to actually post.

Args: post_id: The ID of the post being replied to (parent). subject: Reply subject. message: Reply body text. dry_run: If True (default), validate without posting.

Returns: JSON with validated status, and postid if posted.

Inputs: message (required), post_id (required), subject (required), dry_run. Returns: result

save_assignment_gradeA

Save a grade for a student's assignment (TP).

By default runs in dry_run mode. Set dry_run=False to actually save.

Args: assignment_id: Assignment instance ID. user_id: Student user ID. grade: Numeric grade (0-10). feedback: Optional feedback comment text. dry_run: If True (default), validate without saving.

Returns: JSON with validated status and confirmation.

Inputs: grade (required), user_id (required), assignment_id (required), dry_run, +1 more. Returns: result

send_message_to_userA

Send an instant message to a user.

By default runs in dry_run mode. Set dry_run=False to actually send.

Args: user_id: Recipient user ID. message: Message text. dry_run: If True (default), validate without sending.

Returns: JSON with validated status and msgid if sent.

Inputs: message (required), user_id (required), dry_run. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 20 tools

    Disambiguation2/5

    Several tools overlap significantly: get_grades_report and get_course_grades both return course grades per user with nearly identical outputs, and list_groups vs list_group_members and list_activities vs get_course_contents provide redundant views. This creates selection ambiguity for an agent.

    Naming Consistency4/5

    Most tools follow a verb_noun pattern with snake_case, but the choice of verb is inconsistent (get_ vs list_ for similar read operations). This is a minor deviation from an otherwise predictable scheme.

    Tool Count4/5

    At 20 tools, the server is on the higher end but each tool covers a distinct Moodle feature (forums, quizzes, grades, activities, users). The count is appropriate for a campus integration.

    Completeness4/5

    The read side is comprehensive, covering courses, categories, contents, users, groups, forums, assignments, quizzes, and grades. The write side is more limited but includes the key actions (posting, grading, messaging, calendar events), covering most common workflows.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for Canvas LMS with automatic OAuth authentication. Enables interaction with courses, assignments, grades, modules, discussions, quizzes, files, calendar, messaging, and more without manual API token management.
      1,589 npm
      1
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      MCP server and CLI for EVA FING (Moodle of Facultad de Ingeniería, UdelaR) that authenticates headlessly via Shibboleth, enabling AI agents and users to query courses, announcements, activities, calendar, and study material.
      8
      MIT