@josetra/dutic-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cli/format.js +69 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.js +426 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/setup.js +121 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/ui.js +111 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/auth.js +47 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/browser.js +56 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/config.js +34 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/dates.js +43 -0
- package/dist/core/dates.js.map +1 -0
- package/dist/core/errors.js +25 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/http.js +43 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/login.js +193 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/models.js +79 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/moodleClient.js +94 -0
- package/dist/core/moodleClient.js.map +1 -0
- package/dist/core/session.js +64 -0
- package/dist/core/session.js.map +1 -0
- package/dist/domain/assign.js +112 -0
- package/dist/domain/assign.js.map +1 -0
- package/dist/domain/concurrency.js +17 -0
- package/dist/domain/concurrency.js.map +1 -0
- package/dist/domain/courses.js +70 -0
- package/dist/domain/courses.js.map +1 -0
- package/dist/domain/documents.js +209 -0
- package/dist/domain/documents.js.map +1 -0
- package/dist/domain/grades.js +99 -0
- package/dist/domain/grades.js.map +1 -0
- package/dist/domain/people.js +161 -0
- package/dist/domain/people.js.map +1 -0
- package/dist/domain/resources.js +242 -0
- package/dist/domain/resources.js.map +1 -0
- package/dist/domain/tasks.js +191 -0
- package/dist/domain/tasks.js.map +1 -0
- package/dist/mcp/server.js +261 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +67 -0
- package/skills/dutic/SKILL.md +173 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ensureSession, withSession } from "../core/auth.js";
|
|
6
|
+
import { SessionExpiredError } from "../core/errors.js";
|
|
7
|
+
import { getSemester } from "../core/config.js";
|
|
8
|
+
import { isExpired, isValid, loadSession } from "../core/session.js";
|
|
9
|
+
import { getEnrolledCourses, getCourseContents } from "../domain/courses.js";
|
|
10
|
+
import { getAllTasks, getCourseTasks, getUpcomingTasks } from "../domain/tasks.js";
|
|
11
|
+
import { downloadFile, listCourseFiles, listCourseMaterials, pullCourseFiles, } from "../domain/resources.js";
|
|
12
|
+
import { convertLocalPdfToMarkdown, readResourceAsMarkdown, studyCourseMaterials, } from "../domain/documents.js";
|
|
13
|
+
import { getAllGrades, getCourseGrades, } from "../domain/grades.js";
|
|
14
|
+
import { getAssignDetail } from "../domain/assign.js";
|
|
15
|
+
import { findPeople, getCourseTeachers, getPersonProfile, listCourseParticipants, } from "../domain/people.js";
|
|
16
|
+
/**
|
|
17
|
+
* En contexto MCP la renovación de sesión es "headless-only": si el SSO de Google sigue
|
|
18
|
+
* vivo en el perfil persistente, renueva sola; si no, devuelve un error que le pide al
|
|
19
|
+
* usuario correr `dutic login` en una terminal (donde sí puede abrirse el navegador).
|
|
20
|
+
*/
|
|
21
|
+
const MCP_MODE = "headless-only";
|
|
22
|
+
const server = new McpServer({ name: "dutic-mcp", version: "0.1.0" });
|
|
23
|
+
/** Envuelve un handler traduciendo SessionExpiredError a un mensaje accionable. */
|
|
24
|
+
async function tool(fn) {
|
|
25
|
+
try {
|
|
26
|
+
const data = await fn();
|
|
27
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof SessionExpiredError) {
|
|
31
|
+
return {
|
|
32
|
+
isError: true,
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: "Sesión de DUTIC caducada y no se pudo renovar automáticamente. Ejecuta `dutic login` en una terminal para reautenticarte con Google.",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
isError: true,
|
|
43
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
server.registerTool("dutic_list_tasks", {
|
|
48
|
+
title: "Listar tareas DUTIC",
|
|
49
|
+
description: "Lista tus tareas del aula virtual. scope='upcoming' usa el calendario (rápido). " +
|
|
50
|
+
"scope='all' barre TODOS los cursos para incluir tareas ocultas que no aparecen en el " +
|
|
51
|
+
"calendario (marcadas con hidden=true) — útil para no perder entregas.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
scope: z.enum(["upcoming", "all"]).default("upcoming"),
|
|
54
|
+
onlyHidden: z.boolean().default(false).describe("Con scope='all', devolver sólo las ocultas."),
|
|
55
|
+
detailed: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.default(true)
|
|
58
|
+
.describe("Con scope='all', scrapear el estado de entrega y la nota de cada tarea."),
|
|
59
|
+
},
|
|
60
|
+
}, async ({ scope, onlyHidden, detailed }) => tool(async () => {
|
|
61
|
+
if (scope === "all" || onlyHidden) {
|
|
62
|
+
return withSession(async (s) => {
|
|
63
|
+
const { tasks, scanErrors } = await getAllTasks(s, { enrich: detailed });
|
|
64
|
+
return { tasks: onlyHidden ? tasks.filter((t) => t.hidden) : tasks, scanErrors };
|
|
65
|
+
}, { mode: MCP_MODE });
|
|
66
|
+
}
|
|
67
|
+
return withSession((s) => getUpcomingTasks(s), { mode: MCP_MODE });
|
|
68
|
+
}));
|
|
69
|
+
server.registerTool("dutic_list_courses", {
|
|
70
|
+
title: "Listar cursos DUTIC",
|
|
71
|
+
description: "Lista los cursos en los que estás matriculado (id, nombre, docentes).",
|
|
72
|
+
inputSchema: {},
|
|
73
|
+
}, async () => tool(() => withSession((s) => getEnrolledCourses(s), { mode: MCP_MODE })));
|
|
74
|
+
server.registerTool("dutic_get_course_contents", {
|
|
75
|
+
title: "Contenido de un curso",
|
|
76
|
+
description: "Devuelve las secciones y módulos (tareas, recursos, foros...) de un curso.",
|
|
77
|
+
inputSchema: { courseId: z.number().int().positive() },
|
|
78
|
+
}, async ({ courseId }) => tool(() => withSession((s) => getCourseContents(s, courseId), { mode: MCP_MODE })));
|
|
79
|
+
server.registerTool("dutic_get_course_tasks", {
|
|
80
|
+
title: "Tareas de un curso",
|
|
81
|
+
description: "Lista las tareas de un curso concreto, incluidas las ocultas (hidden=true).",
|
|
82
|
+
inputSchema: { courseId: z.number().int().positive() },
|
|
83
|
+
}, async ({ courseId }) => tool(() => withSession((s) => getCourseTasks(s, courseId), { mode: MCP_MODE })));
|
|
84
|
+
server.registerTool("dutic_list_course_files", {
|
|
85
|
+
title: "Recursos descargables de un curso",
|
|
86
|
+
description: "Lista los archivos/recursos descargables de un curso (con su URL de descarga).",
|
|
87
|
+
inputSchema: { courseId: z.number().int().positive() },
|
|
88
|
+
}, async ({ courseId }) => tool(() => withSession((s) => listCourseFiles(s, courseId), { mode: MCP_MODE })));
|
|
89
|
+
server.registerTool("dutic_download_file", {
|
|
90
|
+
title: "Descargar un archivo",
|
|
91
|
+
description: "Descarga un archivo del aula virtual a una ruta local. Acepta la URL de vista del " +
|
|
92
|
+
"módulo o directamente una URL de pluginfile.php.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
url: z.string().url(),
|
|
95
|
+
destPath: z.string().describe("Ruta local de destino."),
|
|
96
|
+
},
|
|
97
|
+
}, async ({ url, destPath }) => tool(() => withSession((s) => downloadFile(s, url, destPath), { mode: MCP_MODE })));
|
|
98
|
+
server.registerTool("dutic_read_resource", {
|
|
99
|
+
title: "Leer un recurso como texto/Markdown",
|
|
100
|
+
description: "Descarga un recurso del aula (PDF, texto…) y devuelve su CONTENIDO como Markdown listo " +
|
|
101
|
+
"para analizar, SIN gastar tokens en el binario. Convierte PDFs a texto automáticamente. " +
|
|
102
|
+
"Úsalo cuando el usuario quiera que analices, resumas o extraigas algo de un material del " +
|
|
103
|
+
"curso (sílabo, informe, guía, lectura). Para binarios no soportados (docx, imágenes) " +
|
|
104
|
+
"avisa y sugiere descargar. Acepta la URL de vista del módulo o de pluginfile.php.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
url: z.string().url(),
|
|
107
|
+
maxChars: z
|
|
108
|
+
.number()
|
|
109
|
+
.int()
|
|
110
|
+
.positive()
|
|
111
|
+
.default(24_000)
|
|
112
|
+
.describe("Máximo de caracteres a devolver (trunca lo demás)."),
|
|
113
|
+
},
|
|
114
|
+
}, async ({ url, maxChars }) => tool(() => withSession((s) => readResourceAsMarkdown(s, url, maxChars), { mode: MCP_MODE })));
|
|
115
|
+
server.registerTool("dutic_get_assignment_detail", {
|
|
116
|
+
title: "Detalle completo de una tarea",
|
|
117
|
+
description: "Devuelve TODO lo de una tarea: la consigna/instrucciones completas, los archivos adjuntos " +
|
|
118
|
+
"a la consigna (guías, rúbricas — puedes leerlos con dutic_read_resource), fechas oficiales " +
|
|
119
|
+
"de apertura y cierre, estado de entrega, nota y quién calificó. IMPORTANTE: incluye " +
|
|
120
|
+
"`dateConflict` y `datesInDescription` — los docentes a veces escriben en el texto una fecha " +
|
|
121
|
+
"distinta a la configurada en Moodle; si dateConflict es true, AVISA al usuario de la " +
|
|
122
|
+
"discrepancia. Úsalo cuando el usuario pregunte qué pide una tarea o cuándo se entrega.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
cmid: z.number().int().positive().describe("Course module id de la tarea."),
|
|
125
|
+
},
|
|
126
|
+
}, async ({ cmid }) => tool(() => withSession((s) => getAssignDetail(s, `${s.siteUrl}/mod/assign/view.php?id=${cmid}`), { mode: MCP_MODE })));
|
|
127
|
+
server.registerTool("dutic_list_participants", {
|
|
128
|
+
title: "Participantes de un curso",
|
|
129
|
+
description: "Lista los participantes visibles de un curso (nombre, rol, grupo, último acceso). Si el " +
|
|
130
|
+
"curso usa grupos separados, Moodle sólo muestra a los del grupo del usuario — es lo mismo " +
|
|
131
|
+
"que ve en la web. Úsalo para saber quiénes son sus compañeros o de qué grupo es alguien.",
|
|
132
|
+
inputSchema: { courseId: z.number().int().positive() },
|
|
133
|
+
}, async ({ courseId }) => tool(() => withSession((s) => listCourseParticipants(s, courseId), { mode: MCP_MODE })));
|
|
134
|
+
server.registerTool("dutic_find_person", {
|
|
135
|
+
title: "Buscar una persona por nombre o correo",
|
|
136
|
+
description: "Busca entre los participantes de TODOS los cursos del usuario por nombre o por correo " +
|
|
137
|
+
"institucional. Si la consulta contiene '@' resuelve los correos de los perfiles para poder " +
|
|
138
|
+
"buscar por correo. Devuelve nombre, curso, grupo, último acceso y correo cuando aplica.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
query: z.string().min(2).describe("Nombre (o parte) o correo a buscar."),
|
|
141
|
+
withEmail: z
|
|
142
|
+
.boolean()
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Fuerza resolver el correo de cada coincidencia (más lento)."),
|
|
145
|
+
},
|
|
146
|
+
}, async ({ query, withEmail }) => tool(() => withSession((s) => findPeople(s, query, { withEmail }), { mode: MCP_MODE })));
|
|
147
|
+
server.registerTool("dutic_get_person_profile", {
|
|
148
|
+
title: "Perfil de una persona",
|
|
149
|
+
description: "Perfil de un participante: correo institucional, zona horaria y cursos que comparte contigo. " +
|
|
150
|
+
"Necesita el userId (lo da dutic_list_participants o dutic_find_person).",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
userId: z.number().int().positive(),
|
|
153
|
+
courseId: z.number().int().positive().optional(),
|
|
154
|
+
},
|
|
155
|
+
}, async ({ userId, courseId }) => tool(() => withSession((s) => getPersonProfile(s, userId, courseId), { mode: MCP_MODE })));
|
|
156
|
+
server.registerTool("dutic_get_course_teachers", {
|
|
157
|
+
title: "Docentes de un curso",
|
|
158
|
+
description: "Identifica a los docentes del curso combinando los contactos del curso, los roles del " +
|
|
159
|
+
"listado y —lo que suele funcionar en esta aula— el nombre de quien calificó las tareas. " +
|
|
160
|
+
"Úsalo cuando el usuario pregunte quién es su profesor de un curso.",
|
|
161
|
+
inputSchema: { courseId: z.number().int().positive() },
|
|
162
|
+
}, async ({ courseId }) => tool(() => withSession((s) => getCourseTeachers(s, courseId), { mode: MCP_MODE })));
|
|
163
|
+
server.registerTool("dutic_get_grades", {
|
|
164
|
+
title: "Ver calificaciones",
|
|
165
|
+
description: "Devuelve las calificaciones del usuario. Sin courseId: resumen de TODOS los cursos (nota " +
|
|
166
|
+
"total + cuántos ítems quedan por calificar). Con courseId: detalle por ítem (nota, rango, " +
|
|
167
|
+
"porcentaje, peso). Úsalo cuando el usuario pregunte por sus notas, promedio, o cómo va en un curso.",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
courseId: z
|
|
170
|
+
.number()
|
|
171
|
+
.int()
|
|
172
|
+
.positive()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe("Si se indica, detalle de ese curso; si no, resumen de todos."),
|
|
175
|
+
},
|
|
176
|
+
}, async ({ courseId }) => tool(() => withSession((s) => (courseId ? getCourseGrades(s, courseId) : getAllGrades(s)), { mode: MCP_MODE })));
|
|
177
|
+
server.registerTool("dutic_list_course_materials", {
|
|
178
|
+
title: "Listar materiales de un curso (carpetas expandidas)",
|
|
179
|
+
description: "Lista TODOS los archivos descargables de un curso, expandiendo las carpetas a sus archivos " +
|
|
180
|
+
"reales (diapositivas, lecturas, prácticas…). Devuelve nombre, URL, sección (unidad) y carpeta. " +
|
|
181
|
+
"Úsalo para saber qué materiales hay, y por qué unidad, antes de leer o descargar. Con `section` " +
|
|
182
|
+
"filtras a una unidad concreta.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
courseId: z.number().int().positive(),
|
|
185
|
+
section: z
|
|
186
|
+
.string()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("Filtra por nombre de unidad/sección (subcadena, ignora acentos)."),
|
|
189
|
+
},
|
|
190
|
+
}, async ({ courseId, section }) => tool(() => withSession((s) => listCourseMaterials(s, courseId, { section }), { mode: MCP_MODE })));
|
|
191
|
+
server.registerTool("dutic_study_course", {
|
|
192
|
+
title: "Preparar materiales de un curso para estudiar",
|
|
193
|
+
description: "Descarga todos los materiales de un curso a un directorio y CONVIERTE los PDFs a Markdown " +
|
|
194
|
+
"(.md) organizados por carpeta, para estudiar/analizar offline sin gastar tokens en binarios. " +
|
|
195
|
+
"Devuelve el manifiesto de lo guardado. Úsalo cuando el usuario quiera 'preparar/bajar el " +
|
|
196
|
+
"material para estudiar' de un curso. Con `section` bajas sólo esa unidad — útil para no " +
|
|
197
|
+
"descargar todo de golpe cuando el usuario quiere estudiar una unidad concreta.",
|
|
198
|
+
inputSchema: {
|
|
199
|
+
courseId: z.number().int().positive(),
|
|
200
|
+
destDir: z.string().describe("Directorio local de destino."),
|
|
201
|
+
section: z
|
|
202
|
+
.string()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("Sólo materiales de esta unidad/sección (subcadena)."),
|
|
205
|
+
},
|
|
206
|
+
}, async ({ courseId, destDir, section }) => tool(() => withSession((s) => studyCourseMaterials(s, courseId, destDir, { section }), { mode: MCP_MODE })));
|
|
207
|
+
server.registerTool("dutic_pull_course_files", {
|
|
208
|
+
title: "Descargar todos los recursos de un curso",
|
|
209
|
+
description: "Descarga en bloque todos los archivos/recursos de un curso a un directorio local. " +
|
|
210
|
+
"Devuelve la lista de archivos guardados con su tamaño.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
courseId: z.number().int().positive(),
|
|
213
|
+
destDir: z.string().describe("Directorio local de destino."),
|
|
214
|
+
section: z
|
|
215
|
+
.string()
|
|
216
|
+
.optional()
|
|
217
|
+
.describe("Sólo materiales de esta unidad/sección (subcadena)."),
|
|
218
|
+
},
|
|
219
|
+
}, async ({ courseId, destDir, section }) => tool(() => withSession((s) => pullCourseFiles(s, courseId, destDir, { section }), { mode: MCP_MODE })));
|
|
220
|
+
server.registerTool("dutic_pdf_to_markdown", {
|
|
221
|
+
title: "Convertir un PDF local a Markdown",
|
|
222
|
+
description: "Convierte un PDF que ya está en disco a Markdown/texto para analizarlo sin gastar tokens " +
|
|
223
|
+
"en el binario. Opcionalmente guarda el resultado en outPath. No requiere sesión de Moodle.",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
filePath: z.string().describe("Ruta local del PDF."),
|
|
226
|
+
outPath: z.string().optional().describe("Si se indica, guarda el Markdown aquí."),
|
|
227
|
+
maxChars: z
|
|
228
|
+
.number()
|
|
229
|
+
.int()
|
|
230
|
+
.nonnegative()
|
|
231
|
+
.default(0)
|
|
232
|
+
.describe("Máximo de caracteres a devolver (0 = sin límite)."),
|
|
233
|
+
},
|
|
234
|
+
}, async ({ filePath, outPath, maxChars }) => tool(() => convertLocalPdfToMarkdown(filePath, outPath, maxChars)));
|
|
235
|
+
server.registerTool("dutic_session_status", {
|
|
236
|
+
title: "Estado de sesión DUTIC",
|
|
237
|
+
description: "Indica si hay una sesión válida, el semestre y cuándo caduca.",
|
|
238
|
+
inputSchema: {},
|
|
239
|
+
}, async () => tool(async () => {
|
|
240
|
+
const s = await loadSession();
|
|
241
|
+
return {
|
|
242
|
+
semester: getSemester(),
|
|
243
|
+
hasSession: s !== null,
|
|
244
|
+
siteUrl: s?.siteUrl ?? null,
|
|
245
|
+
valid: isValid(s),
|
|
246
|
+
expired: s ? isExpired(s) : null,
|
|
247
|
+
capturedAt: s ? new Date(s.capturedAt).toISOString() : null,
|
|
248
|
+
};
|
|
249
|
+
}));
|
|
250
|
+
server.registerTool("dutic_refresh_session", {
|
|
251
|
+
title: "Renovar sesión DUTIC",
|
|
252
|
+
description: "Intenta renovar la sesión sin interacción (si el SSO de Google sigue vivo). Si falla, " +
|
|
253
|
+
"hay que ejecutar `dutic login` en una terminal.",
|
|
254
|
+
inputSchema: {},
|
|
255
|
+
}, async () => tool(async () => {
|
|
256
|
+
const s = await ensureSession({ mode: "headless-only" });
|
|
257
|
+
return { ok: true, siteUrl: s.siteUrl, capturedAt: new Date(s.capturedAt).toISOString() };
|
|
258
|
+
}));
|
|
259
|
+
const transport = new StdioServerTransport();
|
|
260
|
+
await server.connect(transport);
|
|
261
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@josetra/dutic-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Servidor MCP + CLI para el aula virtual DUTIC (Moodle) de la UNSA: tareas ocultas, notas, materiales de estudio y personas.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dutic": "dist/cli/index.js",
|
|
8
|
+
"dutic-mcp": "dist/mcp/server.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/mcp/server.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"skills",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/JOSETRA44/DUTIC-mcp.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/JOSETRA44/DUTIC-mcp#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/JOSETRA44/DUTIC-mcp/issues"
|
|
27
|
+
},
|
|
28
|
+
"author": "JOSETRA44",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"moodle",
|
|
34
|
+
"unsa",
|
|
35
|
+
"dutic",
|
|
36
|
+
"aula-virtual",
|
|
37
|
+
"cli",
|
|
38
|
+
"tareas",
|
|
39
|
+
"claude"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc",
|
|
43
|
+
"setup": "node dist/cli/index.js setup",
|
|
44
|
+
"dev:cli": "tsx src/cli/index.ts",
|
|
45
|
+
"dev:mcp": "tsx src/mcp/server.ts",
|
|
46
|
+
"start:mcp": "node dist/mcp/server.js",
|
|
47
|
+
"prepare": "tsc",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
55
|
+
"cheerio": "^1.0.0",
|
|
56
|
+
"commander": "^12.1.0",
|
|
57
|
+
"playwright": "^1.48.0",
|
|
58
|
+
"undici": "^6.21.0",
|
|
59
|
+
"unpdf": "^1.6.2",
|
|
60
|
+
"zod": "^3.24.1"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.10.0",
|
|
64
|
+
"tsx": "^4.19.2",
|
|
65
|
+
"typescript": "^5.7.2"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dutic
|
|
3
|
+
description: >-
|
|
4
|
+
Consulta y gestiona el aula virtual DUTIC (Moodle de la UNSA) del usuario: tareas
|
|
5
|
+
pendientes, tareas OCULTAS que no salen en el calendario, cursos, notas y descarga de
|
|
6
|
+
recursos. Usa esta skill SIEMPRE que el usuario mencione DUTIC, "aula virtual", "aula
|
|
7
|
+
virtual UNSA", Moodle, sus tareas/deberes/entregas/asignaciones de la universidad,
|
|
8
|
+
"¿qué tengo que entregar?", "¿tengo algo pendiente?", "¿me falta alguna tarea?",
|
|
9
|
+
"revisa mis cursos", "descarga el material de [curso]", fechas de entrega, o cuando
|
|
10
|
+
pregunte por su situación académica — aunque no diga la palabra "DUTIC". Preferí esta
|
|
11
|
+
skill sobre respuestas genéricas: las tareas reales del usuario sólo se obtienen con las
|
|
12
|
+
herramientas del MCP `dutic` / el CLI `dutic`.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# DUTIC — Aula virtual (Moodle UNSA)
|
|
16
|
+
|
|
17
|
+
Esta skill te conecta al aula virtual DUTIC del usuario para ver sus tareas, cursos, notas y
|
|
18
|
+
recursos. Existe por un problema concreto y doloroso: **Moodle esconde tareas**. El "timeline"
|
|
19
|
+
del estudiante (lo que la mayoría de apps y el propio Moodle muestran) sólo lista tareas
|
|
20
|
+
*accionables*: futuras y sin entregar. Una tarea que el profesor crea sin fecha de calendario,
|
|
21
|
+
o una que ya venció, **desaparece de la vista** — y el usuario ha perdido notas por no verlas.
|
|
22
|
+
Tu trabajo es que eso no vuelva a pasar.
|
|
23
|
+
|
|
24
|
+
## Regla de oro: busca siempre las tareas ocultas
|
|
25
|
+
|
|
26
|
+
Cuando el usuario pregunte qué tiene pendiente, **NO te quedes con el timeline**. Usa el barrido
|
|
27
|
+
completo (`scope: "all"` en el MCP, o `dutic tasks --all` en CLI), que recorre todos los cursos
|
|
28
|
+
y revela las tareas ocultas. Es unos segundos más lento pero es justo donde están los problemas.
|
|
29
|
+
|
|
30
|
+
## Cómo actuar (flujo recomendado)
|
|
31
|
+
|
|
32
|
+
1. **Comprueba la sesión** (`dutic_session_status` o `dutic status`). Si no hay sesión válida,
|
|
33
|
+
el MCP intenta renovarla solo; si no puede, pídele al usuario que ejecute `dutic login` en
|
|
34
|
+
una terminal (el login es OAuth de Google institucional y necesita una ventana de navegador,
|
|
35
|
+
así que tú no puedes completarlo por él).
|
|
36
|
+
2. **Trae las tareas con barrido completo.** Prioriza las **SIN ENTREGAR**: ya vienen ordenadas
|
|
37
|
+
por urgencia (pendientes primero, por fecha de entrega). Fíjate en el flag `hidden: true` y en
|
|
38
|
+
`submission: "not-submitted"`.
|
|
39
|
+
3. **Resume con foco en lo accionable.** Encabeza con lo urgente ("Tienes N tareas sin entregar,
|
|
40
|
+
la más próxima vence el ..."). Señala explícitamente las ocultas y las vencidas. No entierres
|
|
41
|
+
la alerta en una lista larga.
|
|
42
|
+
4. **Ofrece el siguiente paso**: abrir la tarea (tienes su URL), ver el contenido del curso, o
|
|
43
|
+
descargar los materiales.
|
|
44
|
+
|
|
45
|
+
## Herramientas del MCP `dutic`
|
|
46
|
+
|
|
47
|
+
Si el servidor MCP `dutic` está disponible, usa estas herramientas (son la fuente de verdad):
|
|
48
|
+
|
|
49
|
+
- `dutic_list_tasks` — args: `scope` (`"upcoming"` = timeline rápido | `"all"` = barrido con
|
|
50
|
+
ocultas), `onlyHidden` (bool), `detailed` (bool, scrapea estado de entrega/nota; por defecto
|
|
51
|
+
true). **Para "¿qué tengo pendiente?" usa `scope: "all"`.**
|
|
52
|
+
- `dutic_list_courses` — cursos matriculados (id, nombre, docentes).
|
|
53
|
+
- `dutic_get_course_tasks` — args: `courseId`. Tareas de un curso concreto, incluidas ocultas.
|
|
54
|
+
- `dutic_get_course_contents` — args: `courseId`. Secciones y módulos del curso.
|
|
55
|
+
- `dutic_list_course_files` — args: `courseId`. Recursos descargables (con su URL).
|
|
56
|
+
- `dutic_download_file` — args: `url`, `destPath`. Descarga un archivo a disco.
|
|
57
|
+
- `dutic_read_resource` — args: `url`, `maxChars`. **Descarga un recurso y te devuelve su CONTENIDO
|
|
58
|
+
como texto/Markdown** (convierte PDFs automáticamente). Úsalo cuando el usuario quiera que
|
|
59
|
+
analices, resumas o extraigas algo de un material (sílabo, informe, lectura, guía): así lees el
|
|
60
|
+
texto directamente sin gastar tokens en el binario. Acepta URL de módulo o de pluginfile.php.
|
|
61
|
+
- `dutic_get_assignment_detail` — args: `cmid`. **Todo sobre una tarea**: consigna completa,
|
|
62
|
+
adjuntos de la consigna (guías/rúbricas, legibles con `dutic_read_resource`), fechas oficiales de
|
|
63
|
+
apertura y cierre, estado de entrega, nota y quién calificó. Incluye `dateConflict` y
|
|
64
|
+
`datesInDescription` (ver abajo). Úsalo cuando pregunte qué pide una tarea o cuándo se entrega.
|
|
65
|
+
- `dutic_get_grades` — args: `courseId?`. **Calificaciones**: sin `courseId`, resumen de todos los
|
|
66
|
+
cursos (nota total + cuántos ítems por calificar); con `courseId`, detalle por ítem (nota, rango, %).
|
|
67
|
+
Úsalo cuando el usuario pregunte por sus notas, promedio, o cómo va.
|
|
68
|
+
- `dutic_list_participants` — args: `courseId`. Compañeros visibles (nombre, rol, grupo, último
|
|
69
|
+
acceso). Con grupos separados Moodle sólo muestra los del grupo del usuario: es normal.
|
|
70
|
+
- `dutic_find_person` — args: `query`, `withEmail?`. Busca una persona en TODOS sus cursos por
|
|
71
|
+
nombre o **por correo** institucional.
|
|
72
|
+
- `dutic_get_person_profile` — args: `userId`, `courseId?`. Correo, zona horaria y cursos compartidos.
|
|
73
|
+
- `dutic_get_course_teachers` — args: `courseId`. Docentes del curso. En esta aula los profesores no
|
|
74
|
+
salen en participantes, así que se deducen de los contactos y de **quién calificó** las tareas.
|
|
75
|
+
- `dutic_list_course_materials` — args: `courseId`, `section?`. Lista TODOS los archivos del curso
|
|
76
|
+
**expandiendo las carpetas** a sus archivos reales, con su **sección (unidad)**. Con `section`
|
|
77
|
+
filtras a una unidad concreta.
|
|
78
|
+
- `dutic_study_course` — args: `courseId`, `destDir`, `section?`. **Descarga materiales y convierte los
|
|
79
|
+
PDFs a Markdown** organizados por carpeta, para estudiar offline. Con `section` bajas **sólo una
|
|
80
|
+
unidad** — no descargues todo de golpe si el usuario quiere estudiar una unidad concreta.
|
|
81
|
+
- `dutic_pull_course_files` — args: `courseId`, `destDir`. Descarga en bloque (expande carpetas).
|
|
82
|
+
- `dutic_pdf_to_markdown` — args: `filePath`, `outPath?`, `maxChars`. Convierte un PDF que ya está
|
|
83
|
+
en disco a Markdown (sin sesión). Útil tras descargar, o para PDFs locales del usuario.
|
|
84
|
+
- `dutic_session_status` / `dutic_refresh_session` — estado y renovación de sesión.
|
|
85
|
+
|
|
86
|
+
### Analizar materiales sin gastar tokens
|
|
87
|
+
|
|
88
|
+
Cuando el usuario pida "analiza/resume/qué dice este material/PDF del curso", **no descargues el
|
|
89
|
+
binario y lo pases crudo** (desperdicia tokens y no es legible). Usa `dutic_read_resource` con la
|
|
90
|
+
URL del recurso: te devuelve texto limpio en Markdown que puedes leer y razonar directamente. Para
|
|
91
|
+
PDFs ya descargados o del sistema de archivos del usuario, usa `dutic_pdf_to_markdown`. Las **carpetas**
|
|
92
|
+
(mod/folder) se expanden solas a sus archivos; muchas están vacías (el profe creó la estructura sin subir
|
|
93
|
+
nada) — eso es normal, no es un error.
|
|
94
|
+
|
|
95
|
+
### Estudiar por unidad (no bajar todo de golpe)
|
|
96
|
+
|
|
97
|
+
Cuando el usuario quiera estudiar "la unidad 2" o "el tema de X", sé selectivo en vez de descargar el
|
|
98
|
+
curso entero:
|
|
99
|
+
|
|
100
|
+
1. Llama a `dutic_list_course_materials` (sin filtro) para ver las **secciones/unidades** del curso.
|
|
101
|
+
2. Si no está claro cómo se divide el temario, busca un material cuyo nombre contenga "sílabo"/"silabus"
|
|
102
|
+
y léelo con `dutic_read_resource`: el sílabo trae el temario dividido en unidades (típicamente 3) con
|
|
103
|
+
los temas de cada una. Con eso mapeas qué secciones corresponden a qué unidad.
|
|
104
|
+
3. Descarga **sólo** esa unidad con `dutic_study_course(..., section: "<nombre o nº de la sección>")`.
|
|
105
|
+
El filtro `section` compara por subcadena ignorando acentos, así que "tema 2", "Unidad II" o "semana 5"
|
|
106
|
+
funcionan según cómo el profe haya nombrado las secciones.
|
|
107
|
+
|
|
108
|
+
Así preparas justo lo que el usuario va a estudiar, sin descargar (ni convertir) material de más.
|
|
109
|
+
|
|
110
|
+
## CLI `dutic` (alternativa / uso directo del usuario)
|
|
111
|
+
|
|
112
|
+
Si no hay MCP pero sí el CLI instalado, o el usuario prefiere la terminal:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
dutic tasks # tareas próximas del timeline (rápido)
|
|
116
|
+
dutic tasks --all # + barrido de cursos → incluye OCULTAS (usa esto para "pendientes")
|
|
117
|
+
dutic tasks --hidden # sólo las ocultas
|
|
118
|
+
dutic tasks --all --fast # sin scrapear estado de entrega (más rápido, menos detalle)
|
|
119
|
+
dutic grades # resumen de notas de todos los cursos
|
|
120
|
+
dutic grades <id> # detalle de notas de un curso
|
|
121
|
+
dutic task <cmid> # detalle de una tarea: consigna, fechas, adjuntos, conflictos
|
|
122
|
+
dutic people <id> [--email] # compañeros del curso (con su correo)
|
|
123
|
+
dutic person <texto> # busca a alguien por nombre o correo en todos tus cursos
|
|
124
|
+
dutic teachers <id> # docentes del curso
|
|
125
|
+
dutic courses # cursos matriculados
|
|
126
|
+
dutic course tasks <id> # tareas de un curso
|
|
127
|
+
dutic course files <id> # recursos de un curso
|
|
128
|
+
dutic materials <id> [--section "Tema 2"] # archivos del curso, agrupados por unidad
|
|
129
|
+
dutic study <id> [--section "Tema 2"] # baja + convierte a Markdown (por unidad)
|
|
130
|
+
dutic read <url> # lee un recurso (PDF→Markdown) para analizarlo sin gastar tokens
|
|
131
|
+
dutic md <archivo.pdf> # convierte un PDF local a Markdown
|
|
132
|
+
dutic pull <id> --dest ./x # descarga todos los recursos de un curso
|
|
133
|
+
dutic status # estado de sesión
|
|
134
|
+
dutic login # reautenticación (abre navegador; sólo el usuario puede completarla)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Fechas contradictorias: la trampa que hay que vigilar
|
|
138
|
+
|
|
139
|
+
La fecha real de una entrega no siempre es la que Moodle tiene configurada. A veces el docente
|
|
140
|
+
escribe **otra fecha dentro del texto de la consigna** ("entregar hasta el 12 de julio"), y el
|
|
141
|
+
estudiante se guía por una mientras el sistema cierra en la otra.
|
|
142
|
+
|
|
143
|
+
`dutic_get_assignment_detail` devuelve `closeDate` (la oficial), `datesInDescription` (las que
|
|
144
|
+
aparecen escritas en la consigna) y `dateConflict: true` cuando difieren en más de un día. **Si
|
|
145
|
+
`dateConflict` es true, avísalo de forma destacada** y recomienda confirmar con el docente: es
|
|
146
|
+
justo el escenario que cuesta notas. (Las fechas de subida de los archivos adjuntos ya se excluyen,
|
|
147
|
+
así que no hay falsas alarmas.)
|
|
148
|
+
|
|
149
|
+
Cuando el usuario pregunte "¿qué tengo que hacer en esta tarea?", da la consigna **y** revisa si
|
|
150
|
+
hay adjuntos: suelen traer la guía o rúbrica con los criterios reales de calificación, y puedes
|
|
151
|
+
leerlos con `dutic_read_resource`.
|
|
152
|
+
|
|
153
|
+
## Interpretar los datos de una tarea
|
|
154
|
+
|
|
155
|
+
Cada tarea trae: `name`, `courseName`, `dueDate` (epoch s, o null si no tiene fecha), `hidden`
|
|
156
|
+
(true = no aparece en el timeline del estudiante), `submission` (`not-submitted` | `submitted` |
|
|
157
|
+
`graded` | `unknown`), `grade`, `timeRemaining` (texto de Moodle, p. ej. "retrasada por 8 días"),
|
|
158
|
+
`url`.
|
|
159
|
+
|
|
160
|
+
Lo que importa señalar al usuario, en orden:
|
|
161
|
+
1. `submission: "not-submitted"` con `dueDate` cercano o pasado → **alerta máxima**.
|
|
162
|
+
2. `hidden: true` + `not-submitted` → tarea que probablemente no sabía que existía.
|
|
163
|
+
3. `timeRemaining` que diga "retrasada"/"vencida" → ya se pasó la fecha.
|
|
164
|
+
|
|
165
|
+
## Notas de contexto
|
|
166
|
+
|
|
167
|
+
- El semestre (p. ej. `2026A`) va en la URL del aula y cambia cada período; el sistema lo
|
|
168
|
+
auto-detecta tras el login, no necesitas gestionarlo.
|
|
169
|
+
- Algunos cursos aparecen **duplicados** con nombres casi iguales (uno con acentos, otro sin):
|
|
170
|
+
es un error de registro de la OTI (la oficina de TI de la UNSA), no un fallo de la herramienta.
|
|
171
|
+
Trátalos como el mismo curso; no alarmes al usuario por ello.
|
|
172
|
+
- Las tareas ocultas suelen salir "sin fecha" porque efectivamente no tienen fecha de entrega en
|
|
173
|
+
Moodle (por eso no generan evento de calendario). El `submission` te dice igual si ya cumplió.
|