utn-mcp 0.9.1 → 0.9.2

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 CHANGED
@@ -1,15 +1,15 @@
1
- ISC License
2
-
3
- Copyright (c) 2026 Joaquín Peñalva
4
-
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Joaquín Peñalva
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -19,6 +19,7 @@ El agente recorre el aula como lo harías vos: entra al curso, mira los foros y
19
19
  | `aula_list_courses` | Lista tus cursos. Es el punto de partida. |
20
20
  | `aula_open` | Entra a un curso, un módulo, un foro o una discusión. Cada respuesta trae las direcciones de lo que hay adentro y de un nivel más arriba, así el agente navega sin perderse. |
21
21
  | `aula_read_file` | Lee un archivo del aula, por partes cuando es largo. |
22
+ | `aula_grades` | Tus calificaciones en un curso, incluidas las que el docente carga a mano. |
22
23
  | `aula_study_pack` | Une en un solo PDF con índice los apuntes que elijas de una materia (un 'machacado'), guardado en tu compu. Solo en modo local. |
23
24
  | `aula_get_site_info` | Datos de tu usuario y de tu regional en el aula virtual. |
24
25
  | `aula_forum_post` | Abre un hilo en un foro o responde en una discusión. |
@@ -34,7 +35,7 @@ El agente recorre el aula como lo harías vos: entra al curso, mira los foros y
34
35
  | `autogestion_correlatividades` | Las correlatividades de tu carrera según la autogestión, para cursar y para rendir, en vez de datos fijos del repo. Aparece solo si tu regional tiene un conector de autogestión. |
35
36
  | `autogestion_examenes` | Tu historial de finales según la autogestión: fecha, materia, nota o estado, plan y código. Aparece solo si tu regional tiene un conector de autogestión. |
36
37
 
37
- Al abrir una tarea, `aula_open` muestra si la entregaste, cuándo vence y la devolución con la nota. Las tools que escriben le piden al agente que te muestre lo que va a hacer y espere tu confirmación.
38
+ Al abrir una tarea, `aula_open` muestra si la entregaste, cuándo vence y la devolución con la nota. Al abrir un cuestionario, muestra tus intentos y la nota final. Las tools que escriben le piden al agente que te muestre lo que va a hacer y espere tu confirmación.
38
39
 
39
40
  ### Archivos que puede leer
40
41
  - **PDF**: el texto, página por página. Los **escaneados** se mandan como imagen de cada página, y el agente los lee igual.
@@ -1,5 +1,5 @@
1
1
  import type { AulaSession } from "./session.js";
2
- import type { Assignment, CourseModuleContent, AssignSubmissionStatus, Choice, ChoiceOption, Course, CourseSection, ForumDiscussion, ForumPost, SiteInfo } from "./types.js";
2
+ import type { Assignment, CourseModuleContent, AssignSubmissionStatus, Choice, ChoiceOption, Course, CourseSection, ForumDiscussion, ForumPost, GradeItem, Quiz, QuizAttempt, SiteInfo } from "./types.js";
3
3
  export declare function getSiteInfo(session: AulaSession): Promise<SiteInfo>;
4
4
  /** El usuario dueño del token, con su email institucional (site_info no lo trae). */
5
5
  export declare function getOwnUser(session: AulaSession): Promise<{
@@ -72,6 +72,18 @@ export declare function submitForGrading(session: AulaSession, assignmentid: num
72
72
  * mod_assign_save_submission rechaza una entrega vacía.
73
73
  */
74
74
  export declare function removeSubmission(session: AulaSession, assignid: number): Promise<void>;
75
+ export declare function getQuizzes(session: AulaSession, courseId: number): Promise<Quiz[]>;
76
+ /** Los intentos del usuario, terminados o no, sin las vistas previas. */
77
+ export declare function getQuizAttempts(session: AulaSession, quizid: number): Promise<QuizAttempt[]>;
78
+ /** La nota final del cuestionario, ya en su escala (`Quiz.grade`); null si todavía no hay o está oculta. */
79
+ export declare function getQuizBestGrade(session: AulaSession, quizid: number): Promise<number | null>;
80
+ /** El comentario general que el docente configuró para el rango de esa nota. */
81
+ export declare function getQuizFeedback(session: AulaSession, quizid: number, grade: number): Promise<string>;
82
+ /**
83
+ * El libro de calificaciones del usuario en un curso, como lo ve en "Calificaciones". Trae también
84
+ * las notas que el docente carga a mano, que no están en ningún módulo.
85
+ */
86
+ export declare function getCourseGradeItems(session: AulaSession, courseid: number): Promise<GradeItem[]>;
75
87
  export declare function getChoices(session: AulaSession, courseId: number): Promise<Choice[]>;
76
88
  export declare function getChoiceOptions(session: AulaSession, choiceid: number): Promise<ChoiceOption[]>;
77
89
  export declare function submitChoiceResponse(session: AulaSession, choiceid: number, optionIds: number[]): Promise<void>;
@@ -149,6 +149,45 @@ export async function removeSubmission(session, assignid) {
149
149
  });
150
150
  throwOnWarnings(warnings, "Moodle no quitó la entrega.");
151
151
  }
152
+ // --- Cuestionarios ---
153
+ export async function getQuizzes(session, courseId) {
154
+ const { quizzes } = await callWs(session, "mod_quiz_get_quizzes_by_courses", {
155
+ "courseids[0]": courseId,
156
+ });
157
+ return quizzes;
158
+ }
159
+ /** Los intentos del usuario, terminados o no, sin las vistas previas. */
160
+ export async function getQuizAttempts(session, quizid) {
161
+ const { attempts } = await callWs(session, "mod_quiz_get_user_attempts", {
162
+ quizid,
163
+ status: "all",
164
+ includepreviews: 0,
165
+ });
166
+ return attempts;
167
+ }
168
+ /** La nota final del cuestionario, ya en su escala (`Quiz.grade`); null si todavía no hay o está oculta. */
169
+ export async function getQuizBestGrade(session, quizid) {
170
+ const { hasgrade, grade } = await callWs(session, "mod_quiz_get_user_best_grade", { quizid });
171
+ return hasgrade ? (grade ?? null) : null;
172
+ }
173
+ /** El comentario general que el docente configuró para el rango de esa nota. */
174
+ export async function getQuizFeedback(session, quizid, grade) {
175
+ const { feedbacktext } = await callWs(session, "mod_quiz_get_quiz_feedback_for_grade", {
176
+ quizid,
177
+ grade,
178
+ });
179
+ return feedbacktext;
180
+ }
181
+ // --- Calificaciones ---
182
+ /**
183
+ * El libro de calificaciones del usuario en un curso, como lo ve en "Calificaciones". Trae también
184
+ * las notas que el docente carga a mano, que no están en ningún módulo.
185
+ */
186
+ export async function getCourseGradeItems(session, courseid) {
187
+ const { userid } = await getSiteInfo(session);
188
+ const { usergrades } = await callWs(session, "gradereport_user_get_grade_items", { courseid, userid });
189
+ return usergrades[0]?.gradeitems ?? [];
190
+ }
152
191
  // --- Elecciones (choice) y selección de grupos (choicegroup) ---
153
192
  export async function getChoices(session, courseId) {
154
193
  const { choices } = await callWs(session, "mod_choice_get_choices_by_courses", {
@@ -171,3 +171,38 @@ export interface ChoiceOption {
171
171
  checked?: boolean;
172
172
  disabled?: boolean;
173
173
  }
174
+ export interface Quiz {
175
+ id: number;
176
+ coursemodule: number;
177
+ /** Nota máxima del cuestionario: a esta escala se lleva el puntaje de los intentos. */
178
+ grade: number;
179
+ /** Puntaje máximo sumando las preguntas; un intento suma sobre esta escala. */
180
+ sumgrades: number;
181
+ decimalpoints: number;
182
+ /** 0: intentos ilimitados. */
183
+ attempts: number;
184
+ /** En segundos; 0 es sin límite. */
185
+ timelimit: number;
186
+ hasfeedback: number;
187
+ }
188
+ export interface QuizAttempt {
189
+ attempt: number;
190
+ state: "inprogress" | "overdue" | "finished" | "abandoned" | string;
191
+ timestart: number;
192
+ timefinish: number;
193
+ /** Puntaje crudo; null si todavía no está corregido o las opciones de revisión lo ocultan. */
194
+ sumgrades: number | null;
195
+ }
196
+ export interface GradeItem {
197
+ itemname: string | null;
198
+ /** "mod", "manual", "category" o "course" (el total del curso). */
199
+ itemtype: string;
200
+ itemmodule: string | null;
201
+ cmid?: number;
202
+ /** Ya formateada por Moodle con la configuración del curso ("4,00", "-" si no hay nota). */
203
+ gradeformatted: string;
204
+ grademax: number;
205
+ percentageformatted?: string;
206
+ gradeishidden: boolean;
207
+ feedback?: string;
208
+ }
@@ -51,6 +51,20 @@ export declare function viewSubmission(session: AulaSession, courseId: number, m
51
51
  }[] | undefined;
52
52
  texto: string | undefined;
53
53
  }>;
54
+ /** Los intentos del usuario y la nota final del cuestionario, en su escala ("0,00 / 10,00"). */
55
+ export declare function viewQuiz(session: AulaSession, courseId: number, module: CourseModule): Promise<{
56
+ calificacionFinal: string | undefined;
57
+ devolucion: string | undefined;
58
+ intentosPermitidos: string | number | undefined;
59
+ limiteTiempoMinutos: number | undefined;
60
+ intentos: {
61
+ numero: number;
62
+ estado: string;
63
+ comienzo: string | null;
64
+ fin: string | undefined;
65
+ calificacion: string | undefined;
66
+ }[];
67
+ }>;
54
68
  export declare function viewChoice(session: AulaSession, courseId: number, module: CourseModule): Promise<{
55
69
  multiple: true | undefined;
56
70
  puedeCambiar: boolean | undefined;
@@ -1,5 +1,5 @@
1
1
  import { htmlToText } from "../../shared/html.js";
2
- import { getAssignments, getChoiceOptions, getChoicegroupOptions, getChoices, getSubmissionStatus, } from "../client/client.js";
2
+ import { getAssignments, getChoiceOptions, getChoicegroupOptions, getChoices, getQuizAttempts, getQuizBestGrade, getQuizFeedback, getQuizzes, getSubmissionStatus, } from "../client/client.js";
3
3
  import { isoDate, toFile } from "./fields.js";
4
4
  const SUBMISSION_STATUS_LABELS = {
5
5
  new: "sin entregar",
@@ -71,6 +71,41 @@ function pluginContent(plugins = []) {
71
71
  texto: texts.length ? texts.join("\n\n") : undefined,
72
72
  };
73
73
  }
74
+ const QUIZ_STATE_LABELS = {
75
+ inprogress: "en curso",
76
+ overdue: "vencido sin enviar",
77
+ finished: "finalizado",
78
+ abandoned: "abandonado",
79
+ };
80
+ /** Una nota con los decimales del cuestionario y coma decimal, como la muestra el aula ("0,00"). */
81
+ const formatGrade = (grade, decimals) => grade.toFixed(decimals).replace(".", ",");
82
+ /** Los intentos del usuario y la nota final del cuestionario, en su escala ("0,00 / 10,00"). */
83
+ export async function viewQuiz(session, courseId, module) {
84
+ const [quizzes, attempts, best] = await Promise.all([
85
+ getQuizzes(session, courseId),
86
+ getQuizAttempts(session, module.instance),
87
+ getQuizBestGrade(session, module.instance),
88
+ ]);
89
+ const quiz = quizzes.find((q) => q.coursemodule === module.id);
90
+ const decimals = quiz?.decimalpoints ?? 2;
91
+ const outOf = (grade) => quiz ? `${formatGrade(grade, decimals)} / ${formatGrade(quiz.grade, decimals)}` : formatGrade(grade, decimals);
92
+ // El intento suma sobre el puntaje de las preguntas; la nota se lleva a la escala del cuestionario.
93
+ const attemptGrade = (sumgrades) => sumgrades === null || !quiz?.sumgrades ? undefined : outOf((sumgrades / quiz.sumgrades) * quiz.grade);
94
+ const feedback = quiz?.hasfeedback && best !== null ? htmlToText(await getQuizFeedback(session, quiz.id, best)) : "";
95
+ return {
96
+ calificacionFinal: best !== null ? outOf(best) : undefined,
97
+ devolucion: feedback || undefined,
98
+ intentosPermitidos: quiz ? quiz.attempts || "ilimitados" : undefined,
99
+ limiteTiempoMinutos: quiz?.timelimit ? Math.round(quiz.timelimit / 60) : undefined,
100
+ intentos: attempts.map((a) => ({
101
+ numero: a.attempt,
102
+ estado: QUIZ_STATE_LABELS[a.state] ?? a.state,
103
+ comienzo: isoDate(a.timestart),
104
+ fin: isoDate(a.timefinish) || undefined,
105
+ calificacion: attemptGrade(a.sumgrades),
106
+ })),
107
+ };
108
+ }
74
109
  export async function viewChoice(session, courseId, module) {
75
110
  const [choices, options] = await Promise.all([
76
111
  getChoices(session, courseId),
@@ -0,0 +1,18 @@
1
+ import type { AulaSession } from "../client/session.js";
2
+ /**
3
+ * Las calificaciones del usuario en un curso, con las que el docente carga a mano. Las de un módulo
4
+ * traen su `direccion` para abrirlo; las categorías y el total del curso vienen marcados en `tipo`.
5
+ */
6
+ export declare function viewGrades(session: AulaSession, rawAddress: string): Promise<{
7
+ direccion: string;
8
+ calificaciones: {
9
+ nombre: string;
10
+ tipo: string;
11
+ direccion: string | undefined;
12
+ calificacion: string | undefined;
13
+ maximo: number;
14
+ porcentaje: string | undefined;
15
+ oculta: true | undefined;
16
+ devolucion: string | undefined;
17
+ }[];
18
+ }>;
@@ -0,0 +1,31 @@
1
+ import { htmlToText } from "../../shared/html.js";
2
+ import { getCourseGradeItems } from "../client/client.js";
3
+ import { courseAddr, moduleAddr, parseAddress } from "./address.js";
4
+ /** Sin nota, Moodle formatea "-": eso no le dice nada al modelo. */
5
+ const NO_GRADE = "-";
6
+ /** Moodle deja `itemname` vacío en los totales; sin esto, el modelo no sabe de qué es la nota. */
7
+ const UNNAMED_ITEMS = { course: "Total del curso", category: "Total de categoría" };
8
+ /**
9
+ * Las calificaciones del usuario en un curso, con las que el docente carga a mano. Las de un módulo
10
+ * traen su `direccion` para abrirlo; las categorías y el total del curso vienen marcados en `tipo`.
11
+ */
12
+ export async function viewGrades(session, rawAddress) {
13
+ const address = parseAddress(rawAddress);
14
+ if (address.level !== "course") {
15
+ throw new Error(`Pasá la dirección de un curso, como "/curso/691"; "${rawAddress}" no lo es.`);
16
+ }
17
+ const items = await getCourseGradeItems(session, address.courseId);
18
+ return {
19
+ direccion: courseAddr(address.courseId),
20
+ calificaciones: items.map((i) => ({
21
+ nombre: i.itemname || UNNAMED_ITEMS[i.itemtype],
22
+ tipo: i.itemmodule ?? i.itemtype,
23
+ direccion: i.cmid ? moduleAddr(address.courseId, i.cmid) : undefined,
24
+ calificacion: i.gradeformatted && i.gradeformatted !== NO_GRADE ? i.gradeformatted : undefined,
25
+ maximo: i.grademax,
26
+ porcentaje: i.percentageformatted && i.percentageformatted !== NO_GRADE ? i.percentageformatted : undefined,
27
+ oculta: i.gradeishidden || undefined,
28
+ devolucion: htmlToText(i.feedback) || undefined,
29
+ })),
30
+ };
31
+ }
@@ -184,6 +184,45 @@ export declare function open(session: AulaSession, rawAddress: string, page?: nu
184
184
  anotados: number | undefined;
185
185
  }[];
186
186
  };
187
+ } | {
188
+ direccion: string;
189
+ padre: string | null;
190
+ seccion: string;
191
+ modulo: {
192
+ direccion: string;
193
+ id: number;
194
+ nombre: string;
195
+ tipo: string;
196
+ url: string | undefined;
197
+ descripcion: string | undefined;
198
+ restriccion: string | undefined;
199
+ fechas: {
200
+ etiqueta: string;
201
+ fecha: string | null;
202
+ }[] | undefined;
203
+ archivos: {
204
+ nombre: string;
205
+ ruta: string | undefined;
206
+ tamanio: number;
207
+ mimetype: string | undefined;
208
+ modificado: string | null;
209
+ url: string;
210
+ }[] | undefined;
211
+ enlaces: string[] | undefined;
212
+ };
213
+ cuestionario: {
214
+ calificacionFinal: string | undefined;
215
+ devolucion: string | undefined;
216
+ intentosPermitidos: string | number | undefined;
217
+ limiteTiempoMinutos: number | undefined;
218
+ intentos: {
219
+ numero: number;
220
+ estado: string;
221
+ comienzo: string | null;
222
+ fin: string | undefined;
223
+ calificacion: string | undefined;
224
+ }[];
225
+ };
187
226
  } | {
188
227
  direccion: string;
189
228
  padre: string | null;
@@ -1,7 +1,7 @@
1
1
  import { htmlToSnippet, htmlToText } from "../../shared/html.js";
2
2
  import { courseAddr, discussionAddr, formatAddress, moduleAddr, parentOf, parseAddress, } from "./address.js";
3
3
  import { canAddDiscussion, getCourseSections, getDiscussionPosts, getForumDiscussions, getUserCourses, PAGE_SIZE, } from "../client/client.js";
4
- import { viewChoice, viewChoicegroup, viewSubmission } from "./activities.js";
4
+ import { viewChoice, viewChoicegroup, viewQuiz, viewSubmission } from "./activities.js";
5
5
  import { isoDate, toFile } from "./fields.js";
6
6
  /**
7
7
  * Entra a una dirección del aula y devuelve su contenido, el padre y las direcciones de lo que hay
@@ -58,6 +58,8 @@ async function viewModule(session, address, page) {
58
58
  return { ...base, eleccion: await viewChoice(session, address.courseId, module) };
59
59
  case "choicegroup":
60
60
  return { ...base, eleccion: await viewChoicegroup(session, module) };
61
+ case "quiz":
62
+ return { ...base, cuestionario: await viewQuiz(session, address.courseId, module) };
61
63
  default:
62
64
  return base;
63
65
  }
@@ -3,6 +3,7 @@ import { getSiteInfo } from "./client/client.js";
3
3
  import { addCalendarEvent, answerChoice, deleteForumPost, editForumPost, postToForum, sendMessage, } from "./format/actions.js";
4
4
  import { ROOT } from "./format/address.js";
5
5
  import { readAulaFile } from "./format/files.js";
6
+ import { viewGrades } from "./format/grades.js";
6
7
  import { buildStudyPack } from "./format/study-pack.js";
7
8
  import { open } from "./format/navigate.js";
8
9
  import { createUploadLink, submitAssignment } from "./format/submission.js";
@@ -50,7 +51,9 @@ export function registerAulaVirtualTools(server, session, files) {
50
51
  "y módulos, un módulo con sus archivos y fechas, un foro con el índice de sus discusiones, o " +
51
52
  "una discusión con sus mensajes completos.\n\n" +
52
53
  "Según el tipo de módulo trae además: en una tarea (assign), `entrega` con el estado, el " +
53
- "vencimiento, lo que se subió y la devolución con la nota; en una elección (choice) o " +
54
+ "vencimiento, lo que se subió y la devolución con la nota; en un cuestionario (quiz), " +
55
+ "`cuestionario` con la `calificacionFinal` (\"0,00 / 10,00\") y cada intento con su estado, " +
56
+ "comienzo, fin y nota; en una elección (choice) o " +
54
57
  "selección de grupos (choicegroup), `eleccion` con las opciones y sus ids; en un foro, " +
55
58
  "`puedeAbrirHilo`; en una discusión, el `id`, el `autorId` y las `acciones` permitidas de " +
56
59
  "cada publicación. Es lo que piden las tools que escriben en el aula.\n\n" +
@@ -126,6 +129,27 @@ export function registerAulaVirtualTools(server, session, files) {
126
129
  return failure(e);
127
130
  }
128
131
  });
132
+ server.registerTool("aula_grades", {
133
+ description: "Las calificaciones del usuario en un curso, las mismas que ve en \"Calificaciones\" del aula: " +
134
+ "tareas, cuestionarios y también las notas que el docente carga a mano (un TP corregido " +
135
+ "en papel, un parcial), que no aparecen en ningún módulo. Usala para saber cómo va en una " +
136
+ "materia o si aprobó algo.\n\n" +
137
+ "Cada ítem trae `calificacion` (como la formatea el aula, sin nota si todavía no hay), " +
138
+ "`maximo`, `porcentaje` y, si es de un módulo, su `direccion` para abrirlo con aula_open. " +
139
+ "`tipo` dice de dónde sale: el módulo (quiz, assign), \"manual\", \"category\" o " +
140
+ "\"course\" (el total).",
141
+ inputSchema: z.object({
142
+ direccion: z.string().describe('Dirección del curso, de aula_list_courses. Ej: "/curso/691".'),
143
+ }),
144
+ annotations: READ_ONLY,
145
+ }, async ({ direccion }) => {
146
+ try {
147
+ return json(await viewGrades(session, direccion));
148
+ }
149
+ catch (e) {
150
+ return failure(e);
151
+ }
152
+ });
129
153
  // El pack se guarda en el disco: solo en STDIO, donde el servidor corre en la compu del usuario.
130
154
  if (files.local) {
131
155
  server.registerTool("aula_study_pack", {
package/build/server.js CHANGED
@@ -12,7 +12,7 @@ import { logger } from "./shared/logger.js";
12
12
  export async function createServer(aula, files, autogestion) {
13
13
  const server = new McpServer({
14
14
  name: "UTN-MCP",
15
- version: "0.9.1",
15
+ version: "0.9.2",
16
16
  });
17
17
  registerAulaVirtualTools(server, aula, files);
18
18
  if (autogestion?.systems.length) {
package/package.json CHANGED
@@ -1,66 +1,66 @@
1
- {
2
- "name": "utn-mcp",
3
- "version": "0.9.1",
4
- "description": "Este es el Model Context Protocol server NO OFICIAL de la UTN para buscar informacion o archivos en el aula virtual.",
5
- "main": "build/index.js",
6
- "bin": {
7
- "utn-mcp": "build/index.js"
8
- },
9
- "files": [
10
- "build",
11
- "vendor"
12
- ],
13
- "scripts": {
14
- "prebuild": "node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"",
15
- "build": "tsc",
16
- "prepublishOnly": "npm run build",
17
- "prepare": "node scripts/vendor-pdfjs.mjs",
18
- "start": "node build/index.js",
19
- "dev": "tsx src/index.ts",
20
- "test": "echo \"Error: no test specified\" && exit 1",
21
- "login": "tsx src/index.ts login"
22
- },
23
- "repository": {
24
- "type": "git",
25
- "url": "git+https://github.com/nialvape/utn-mcp.git"
26
- },
27
- "keywords": [
28
- "mcp",
29
- "model-context-protocol",
30
- "utn",
31
- "aula-virtual"
32
- ],
33
- "author": "Joaquín Peñalva (https://github.com/nialvape)",
34
- "license": "ISC",
35
- "type": "module",
36
- "bugs": {
37
- "url": "https://github.com/nialvape/utn-mcp/issues"
38
- },
39
- "homepage": "https://utnmcp.com.ar",
40
- "dependencies": {
41
- "@modelcontextprotocol/node": "^2.1.1",
42
- "@modelcontextprotocol/server": "^2.3.0",
43
- "@napi-rs/keyring": "^2.1.0",
44
- "express": "^5.2.1",
45
- "pdf-lib": "^1.17.1",
46
- "tough-cookie": "^6.0.2",
47
- "zod": "^4.6.5"
48
- },
49
- "devDependencies": {
50
- "@types/express": "^5.0.6",
51
- "@types/node": "^26.6.4",
52
- "pdfjs-dist": "^6.4.299",
53
- "tsx": "^4.23.15",
54
- "typescript": "^7.0.2"
55
- },
56
- "engines": {
57
- "node": ">=20"
58
- },
59
- "exports": {
60
- ".": "./build/index.js",
61
- "./autogestion": {
62
- "types": "./build/autogestion/client/connector.d.ts",
63
- "default": "./build/autogestion/client/connector.js"
64
- }
65
- }
66
- }
1
+ {
2
+ "name": "utn-mcp",
3
+ "version": "0.9.2",
4
+ "description": "Este es el Model Context Protocol server NO OFICIAL de la UTN para buscar informacion o archivos en el aula virtual.",
5
+ "main": "build/index.js",
6
+ "bin": {
7
+ "utn-mcp": "build/index.js"
8
+ },
9
+ "files": [
10
+ "build",
11
+ "vendor"
12
+ ],
13
+ "scripts": {
14
+ "prebuild": "node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"",
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build",
17
+ "prepare": "node scripts/vendor-pdfjs.mjs",
18
+ "start": "node build/index.js",
19
+ "dev": "tsx src/index.ts",
20
+ "test": "echo \"Error: no test specified\" && exit 1",
21
+ "login": "tsx src/index.ts login"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nialvape/utn-mcp.git"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "utn",
31
+ "aula-virtual"
32
+ ],
33
+ "author": "Joaquín Peñalva (https://github.com/nialvape)",
34
+ "license": "ISC",
35
+ "type": "module",
36
+ "bugs": {
37
+ "url": "https://github.com/nialvape/utn-mcp/issues"
38
+ },
39
+ "homepage": "https://utnmcp.com.ar",
40
+ "dependencies": {
41
+ "@modelcontextprotocol/node": "^2.1.1",
42
+ "@modelcontextprotocol/server": "^2.3.0",
43
+ "@napi-rs/keyring": "^2.1.0",
44
+ "express": "^5.2.1",
45
+ "pdf-lib": "^1.17.1",
46
+ "tough-cookie": "^6.0.2",
47
+ "zod": "^4.6.5"
48
+ },
49
+ "devDependencies": {
50
+ "@types/express": "^5.0.6",
51
+ "@types/node": "^26.6.4",
52
+ "pdfjs-dist": "^6.4.299",
53
+ "tsx": "^4.23.15",
54
+ "typescript": "^7.0.2"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "exports": {
60
+ ".": "./build/index.js",
61
+ "./autogestion": {
62
+ "types": "./build/autogestion/client/connector.d.ts",
63
+ "default": "./build/autogestion/client/connector.js"
64
+ }
65
+ }
66
+ }