@wilm-ai/wilma-cli 2.3.2 → 2.4.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/README.md +1 -1
- package/dist/access.d.ts +18 -0
- package/dist/access.js +48 -3
- package/dist/agent-data.d.ts +184 -14
- package/dist/agent-data.js +195 -16
- package/dist/cli-args.js +1 -0
- package/dist/cli-help.js +23 -12
- package/dist/cli-output.d.ts +4 -1
- package/dist/cli-output.js +19 -5
- package/dist/config.d.ts +11 -4
- package/dist/config.js +4 -0
- package/dist/credentials.js +2 -2
- package/dist/index.js +37 -42
- package/dist/login-server.d.ts +26 -6
- package/dist/login-server.js +444 -227
- package/dist/mcp-tools.d.ts +2 -2
- package/dist/mcp-tools.js +46 -15
- package/dist/mcp.js +2 -14
- package/package.json +2 -2
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import type
|
|
4
|
-
export declare const INSTRUCTIONS = "Read-only access to Finland's Wilma school system for a parent/guardian, or for a student with their own login.\n- Start with wilma_summary: today's and tomorrow's lessons, upcoming exams, recent homework, news and messages for every child in one call.\n- Tools cover all children by default (a student's own login has just that student); pass \"student\" (name or student number) to narrow to one child.\n- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish \"ke 7.10.\" (with the final period), English \"Wed 7 Oct\".\n- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.\n- Teachers' feedback (\"forgot books\", \"did well\") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook.\n- If a tool says the user isn't logged in, relay the login link or instructions to the user exactly; never ask the user to type their Wilma password into the chat.\n- Wilma allows one session per account, so each WilmAI login logs the user out of Wilma in their browser. If they want to use Wilma in the browser, call wilma_logout and then call Wilma tools only when they ask again.\n- If a result ends with an \"Update note\", answer the question first, then pass the note on in one sentence.";
|
|
3
|
+
import { type FetchedAttachment, type WilmaAccess } from "./agent-data.js";
|
|
4
|
+
export declare const INSTRUCTIONS = "Read-only access to Finland's Wilma school system for a parent/guardian, or for a student with their own login.\n- Start with wilma_summary: today's and tomorrow's lessons, upcoming exams, recent homework, news and messages for every child in one call.\n- Tools cover all children by default (a student's own login has just that student); pass \"student\" (name or student number) to narrow to one child.\n- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish \"ke 7.10.\" (with the final period), English \"Wed 7 Oct\".\n- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.\n- Teachers' feedback (\"forgot books\", \"did well\") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook. The class, class teacher and teachers are in wilma_contacts.\n- If a tool says the user isn't logged in, relay the login link or instructions to the user exactly; never ask the user to type their Wilma password into the chat.\n- Wilma allows one session per account, so each WilmAI login logs the user out of Wilma in their browser. If they want to use Wilma in the browser, call wilma_logout and then call Wilma tools only when they ask again.\n- If a result ends with an \"Update note\", answer the question first, then pass the note on in one sentence.";
|
|
5
5
|
export declare const READ_ONLY: {
|
|
6
6
|
readonly readOnlyHint: true;
|
|
7
7
|
readonly destructiveHint: false;
|
package/dist/mcp-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { SCHEDULE_MAX_DAYS } from "./agent-data.js";
|
|
2
3
|
import { normalizeResourceId } from "./downloads.js";
|
|
3
4
|
import { toAgentJson } from "./output-json.js";
|
|
4
5
|
/*
|
|
@@ -10,7 +11,7 @@ export const INSTRUCTIONS = `Read-only access to Finland's Wilma school system f
|
|
|
10
11
|
- Tools cover all children by default (a student's own login has just that student); pass "student" (name or student number) to narrow to one child.
|
|
11
12
|
- Wilma content is usually in Finnish. Answer in the user's language and translate as needed. Write dates the way that language does: Finnish "ke 7.10." (with the final period), English "Wed 7 Oct".
|
|
12
13
|
- Message and news lists return ids; use wilma_read_message / wilma_read_news for full text. Bulletins can link attachments; fetch them with wilma_get_news_attachment.
|
|
13
|
-
- Teachers' feedback ("forgot books", "did well") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook.
|
|
14
|
+
- Teachers' feedback ("forgot books", "did well") and absences are lesson notes: wilma_lesson_notes for recent days, wilma_lesson_notes_summary for counts. Course and report-card grades are in wilma_gradebook. The class, class teacher and teachers are in wilma_contacts.
|
|
14
15
|
- If a tool says the user isn't logged in, relay the login link or instructions to the user exactly; never ask the user to type their Wilma password into the chat.
|
|
15
16
|
- Wilma allows one session per account, so each WilmAI login logs the user out of Wilma in their browser. If they want to use Wilma in the browser, call wilma_logout and then call Wilma tools only when they ask again.
|
|
16
17
|
- If a result ends with an "Update note", answer the question first, then pass the note on in one sentence.`;
|
|
@@ -74,7 +75,7 @@ function attachmentResult(fetched, savedPath) {
|
|
|
74
75
|
export function registerWilmaTools(server, ctx) {
|
|
75
76
|
server.registerTool("wilma_summary", {
|
|
76
77
|
title: "School summary",
|
|
77
|
-
description: "Daily briefing per child: today's and the next school day's lessons, upcoming exams (with start times when given), recent homework, lesson notes since the previous school day (teachers' feedback and absences), recent bulletins, and recent and unread messages (ids, unread, replyCount). What Wilma didn't give is listed in unavailable (\"news\": this Wilma doesn't show this account its bulletins). The best starting point for any question about school.",
|
|
78
|
+
description: "Daily briefing per child: today's and the next school day's lessons, upcoming exams (with start times when given), recent homework, lesson notes since the previous school day (teachers' feedback and absences), recent bulletins, and recent and unread messages (ids, unread, replyCount; event invitations with invitationStatus, as in wilma_list_messages). What Wilma didn't give is listed in unavailable (\"news\": this Wilma doesn't show this account its bulletins). The best starting point for any question about school.",
|
|
78
79
|
inputSchema: {
|
|
79
80
|
student: studentArg,
|
|
80
81
|
days: z.number().int().min(1).max(60).optional().describe("How many days back to include news and messages (default 7)."),
|
|
@@ -87,7 +88,7 @@ export function registerWilmaTools(server, ctx) {
|
|
|
87
88
|
}, async ({ student, days, since }) => ctx.withAccess(async (a) => json(await a.summary({ student, days, since }))));
|
|
88
89
|
server.registerTool("wilma_schedule", {
|
|
89
90
|
title: "Lesson schedule",
|
|
90
|
-
description: "
|
|
91
|
+
description: "List each child's lessons for one day or a school week: today, the next school day, this or next week (Monday to Friday), a date, or the next given weekday; or any other week or period with from/to (e.g. a week further ahead, or several weeks to see the usual school days). Lessons come in time order with the date, start and end time, subject, teacher and room (when Wilma gives one); a day without school has no lessons. Exams and homework are in wilma_upcoming_exams and wilma_homework.",
|
|
91
92
|
inputSchema: {
|
|
92
93
|
student: studentArg,
|
|
93
94
|
when: z.enum(["today", "tomorrow", "week", "next-week"]).optional().describe("Default: week. 'tomorrow' means the next school day."),
|
|
@@ -96,37 +97,61 @@ export function registerWilmaTools(server, ctx) {
|
|
|
96
97
|
.string()
|
|
97
98
|
.optional()
|
|
98
99
|
.describe("Next occurrence of a weekday: mon..sun (Finnish ma..su also work). Overrides 'when'."),
|
|
100
|
+
from: z.string().optional().describe("Start of a period, YYYY-MM-DD. Overrides 'when'."),
|
|
101
|
+
to: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe(`End of the period, YYYY-MM-DD (default: the Friday of from's week). At most ${SCHEDULE_MAX_DAYS} days.`),
|
|
99
105
|
},
|
|
100
106
|
annotations: { title: "Lesson schedule", ...READ_ONLY },
|
|
101
|
-
}, async ({ student, when, date, weekday }) => ctx.withAccess(async (a) => json(await a.schedule({ student, when, date, weekday }))));
|
|
107
|
+
}, async ({ student, when, date, weekday, from, to }) => ctx.withAccess(async (a) => json(await a.schedule({ student, when, date, weekday, from, to }))));
|
|
102
108
|
server.registerTool("wilma_homework", {
|
|
103
109
|
title: "Homework",
|
|
104
|
-
description: "
|
|
105
|
-
inputSchema: {
|
|
110
|
+
description: "List the homework teachers have entered for each child, newest first: the date, subject, teacher and what to do. It reads the homework Wilma's front page lists (Wilma decides how far back that goes). wilma_summary already includes the last few days' homework.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
student: studentArg,
|
|
113
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many entries per child, newest first (default 10)."),
|
|
114
|
+
},
|
|
106
115
|
annotations: { title: "Homework", ...READ_ONLY },
|
|
107
116
|
}, async ({ student, limit }) => ctx.withAccess(async (a) => json(await a.homework({ student, limit }))));
|
|
108
117
|
server.registerTool("wilma_upcoming_exams", {
|
|
109
118
|
title: "Upcoming exams",
|
|
110
|
-
description: "
|
|
111
|
-
inputSchema: {
|
|
119
|
+
description: "List each child's exams from today on that have no grade yet, soonest first: the date, start time (when the school gives one), exam name, subject, topic (what to study) and teacher. Graded exams are in wilma_grades.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
student: studentArg,
|
|
122
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many exams per child, soonest first (default 20)."),
|
|
123
|
+
},
|
|
112
124
|
annotations: { title: "Upcoming exams", ...READ_ONLY },
|
|
113
125
|
}, async ({ student, limit }) => ctx.withAccess(async (a) => json(await a.upcomingExams({ student, limit }))));
|
|
114
126
|
server.registerTool("wilma_grades", {
|
|
115
127
|
title: "Exam grades",
|
|
116
|
-
description: "
|
|
117
|
-
inputSchema: {
|
|
128
|
+
description: "List each child's graded exams, newest first: the date, exam name, subject, grade as Wilma shows it (e.g. 8+, 9½, S), the teacher's notes and when a guardian marked it seen. It reads the exams Wilma's front page lists (Wilma decides how far back that goes); with from, Wilma's list of past exams for that period instead, as far back as the school keeps them, including exams still waiting for a grade (grade: null). For course, term and report-card grades use wilma_gradebook.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
student: studentArg,
|
|
131
|
+
limit: z
|
|
132
|
+
.number()
|
|
133
|
+
.int()
|
|
134
|
+
.min(1)
|
|
135
|
+
.max(100)
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("How many grades per child, newest first (default 20; with from, every exam of the period)."),
|
|
138
|
+
from: z.string().optional().describe("YYYY-MM-DD: past exams from this day on, e.g. 1 August for this school year."),
|
|
139
|
+
to: z.string().optional().describe("YYYY-MM-DD, with from. Default: today."),
|
|
140
|
+
},
|
|
118
141
|
annotations: { title: "Exam grades", ...READ_ONLY },
|
|
119
|
-
}, async ({ student, limit }) => ctx.withAccess(async (a) => json(await a.grades({ student, limit }))));
|
|
142
|
+
}, async ({ student, limit, from, to }) => ctx.withAccess(async (a) => json(await a.grades({ student, limit, from, to }))));
|
|
120
143
|
server.registerTool("wilma_lesson_notes", {
|
|
121
144
|
title: "Lesson notes and absences",
|
|
122
|
-
description: "Lesson notes (merkinnät) teachers logged: absences, lateness, and feedback such as praise or missing books or homework, with the teacher's own words when given. One day (default today)
|
|
145
|
+
description: "Lesson notes (merkinnät) teachers logged: absences, lateness, and feedback such as praise or missing books or homework, with the teacher's own words when given. One day (default today), the last N days, or a period with from/to, which can reach ahead (a teacher may note a make-up exam on a coming lesson).",
|
|
123
146
|
inputSchema: {
|
|
124
147
|
student: studentArg,
|
|
125
148
|
date: z.string().optional().describe("YYYY-MM-DD, default today."),
|
|
126
149
|
days: z.number().int().min(1).max(365).optional().describe("The last N days up to today, instead of one date."),
|
|
150
|
+
from: z.string().optional().describe("Start of a period, YYYY-MM-DD, instead of a date or days."),
|
|
151
|
+
to: z.string().optional().describe("End of the period, YYYY-MM-DD (default today; may be after today)."),
|
|
127
152
|
},
|
|
128
153
|
annotations: { title: "Lesson notes and absences", ...READ_ONLY },
|
|
129
|
-
}, async ({ student, date, days }) => ctx.withAccess(async (a) => json(await a.lessonNotes({ student, date, days }))));
|
|
154
|
+
}, async ({ student, date, days, from, to }) => ctx.withAccess(async (a) => json(await a.lessonNotes({ student, date, days, from, to }))));
|
|
130
155
|
server.registerTool("wilma_lesson_notes_summary", {
|
|
131
156
|
title: "Absences and feedback summary",
|
|
132
157
|
description: "Lesson notes counted by kind (e.g. absences for health reasons, unexplained absences, lateness, praise, missing study materials): this school year by default, or from a date.",
|
|
@@ -139,10 +164,16 @@ export function registerWilmaTools(server, ctx) {
|
|
|
139
164
|
}, async ({ student, from, to }) => ctx.withAccess(async (a) => json(await a.lessonNotesSummary({ student, from, to }))));
|
|
140
165
|
server.registerTool("wilma_gradebook", {
|
|
141
166
|
title: "Gradebook",
|
|
142
|
-
description: "
|
|
167
|
+
description: "Get each child's gradebook (Suoritukset): completed courses and their grades, grouped by subject (subject > syllabus > course), with course codes, credits and completion dates, plus term and school-year (report card) grades. Use it for course and report-card grades; single exam grades are in wilma_grades.",
|
|
143
168
|
inputSchema: { student: studentArg },
|
|
144
169
|
annotations: { title: "Gradebook", ...READ_ONLY },
|
|
145
170
|
}, async ({ student }) => ctx.withAccess(async (a) => json(await a.gradebook({ student }))));
|
|
171
|
+
server.registerTool("wilma_contacts", {
|
|
172
|
+
title: "Class and teachers",
|
|
173
|
+
description: "Each child's class (e.g. 9A), class teacher and other supervisors with their role as Wilma names it (e.g. Luokanvalvoja), and the child's own teachers as Wilma's message recipients list them, with the subjects each teaches the child in this and next week's timetable (empty when none). Other school staff aren't included. Lessons with times and rooms are in wilma_schedule.",
|
|
174
|
+
inputSchema: { student: studentArg },
|
|
175
|
+
annotations: { title: "Class and teachers", ...READ_ONLY },
|
|
176
|
+
}, async ({ student }) => ctx.withAccess(async (a) => json(await a.contacts({ student }))));
|
|
146
177
|
server.registerTool("wilma_list_printouts", {
|
|
147
178
|
title: "List printouts",
|
|
148
179
|
description: "PDF documents the school offers (Tulosteet), such as report cards or absence reports. Fetch one with wilma_get_printout.",
|
|
@@ -172,7 +203,7 @@ export function registerWilmaTools(server, ctx) {
|
|
|
172
203
|
}));
|
|
173
204
|
server.registerTool("wilma_list_messages", {
|
|
174
205
|
title: "List messages",
|
|
175
|
-
description: "Wilma messages, newest first (subject, sender, date, id). Use wilma_read_message for the full text.",
|
|
206
|
+
description: "Wilma messages, newest first (subject, sender, date, id, unread, replyCount). The appointments folder holds event invitations (parents' evenings, learning discussions) with the family's answer as Wilma lists it: invitationStatus (\"Applied\": signed up, the time in invitationInfo; \"ExpiredReg\": sign-up closed; null: no answer shown). Use wilma_read_message for the full text, and for an invitation's time slots.",
|
|
176
207
|
inputSchema: {
|
|
177
208
|
student: studentArg,
|
|
178
209
|
folder: z.enum(["inbox", "archive", "outbox", "drafts", "appointments"]).optional().describe("Default: inbox."),
|
package/dist/mcp.js
CHANGED
|
@@ -121,7 +121,7 @@ function createWilmaMcpServer(version) {
|
|
|
121
121
|
});
|
|
122
122
|
server.registerTool("wilma_account", {
|
|
123
123
|
title: "Wilma account status",
|
|
124
|
-
description: "Which Wilma logins are connected and
|
|
124
|
+
description: "Which Wilma logins are connected: each one's Wilma, username, role (\"guardian\", or \"student\" for a student's own login) and the children it lists. Families with children on different Wilmas connect one login per Wilma (wilma_login adds another). When two logins on one Wilma list the same child (a parent's and the child's own), tools read that child through the guardian's login, and the other login's entry for the child says readVia. A child unticked on the login page (a profile Wilma lists twice) has hidden: true and isn't read.",
|
|
125
125
|
inputSchema: {},
|
|
126
126
|
annotations: { title: "Wilma account status", ...READ_ONLY },
|
|
127
127
|
}, async () => {
|
|
@@ -129,19 +129,7 @@ function createWilmaMcpServer(version) {
|
|
|
129
129
|
const accounts = await resolveAccounts(config);
|
|
130
130
|
if (!accounts.length)
|
|
131
131
|
return json({ connected: false, hint: "Call wilma_login to connect." });
|
|
132
|
-
return withAccess(async (a) => {
|
|
133
|
-
const students = await a.students();
|
|
134
|
-
return json({
|
|
135
|
-
connected: true,
|
|
136
|
-
source: accounts[0].source,
|
|
137
|
-
wilmas: accounts.map((account, index) => ({
|
|
138
|
-
wilma: account.stored?.tenantName ?? account.profile.baseUrl,
|
|
139
|
-
username: account.profile.username,
|
|
140
|
-
children: students.filter((s) => s.account === index && s.name).map((s) => ({ name: s.name, studentNumber: s.studentNumber })),
|
|
141
|
-
})),
|
|
142
|
-
...(a.problems.length ? { problems: a.problems } : {}),
|
|
143
|
-
});
|
|
144
|
-
});
|
|
132
|
+
return withAccess(async (a) => json({ connected: true, source: accounts[0].source, wilmas: await a.logins() }));
|
|
145
133
|
});
|
|
146
134
|
server.registerTool("wilma_find_school", {
|
|
147
135
|
title: "Find a school's Wilma",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wilm-ai/wilma-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Read your children's Wilma (Finnish school system) from the terminal or an AI assistant: schedule, homework, exams, messages and news. Students can use their own login too.",
|
|
5
5
|
"mcpName": "ai.wilm/wilma",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc -p tsconfig.json",
|
|
24
24
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
25
|
-
"test": "pnpm --filter @wilm-ai/wilma-client build && pnpm run build && node test/news-resources.mjs && node test/mcp-login.mjs && node test/mfa.mjs && node test/logout.mjs && node test/shared-session.mjs && node test/audit.mjs && node test/update-notice.mjs && node test/registry.mjs",
|
|
25
|
+
"test": "pnpm --filter @wilm-ai/wilma-client build && pnpm run build && node test/news-resources.mjs && node test/mcp-login.mjs && node test/mfa.mjs && node test/logout.mjs && node test/shared-session.mjs && node test/audit.mjs && node test/family.mjs && node test/update-notice.mjs && node test/registry.mjs",
|
|
26
26
|
"start": "node dist/index.js",
|
|
27
27
|
"prepack": "pnpm --filter @wilm-ai/wilma-client build && pnpm --filter @wilm-ai/wilma-cli build",
|
|
28
28
|
"test:live": "tsc -p tsconfig.json && node test/live.spec.mjs",
|