infinitecampus-mcp 0.1.2 → 2.0.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/dist/bundle.js +574 -75
- package/dist/client.js +9 -2
- package/dist/index.js +13 -1
- package/dist/tools/_shared.js +31 -0
- package/dist/tools/assessments.js +50 -0
- package/dist/tools/assignments.js +21 -11
- package/dist/tools/attendance.js +58 -12
- package/dist/tools/attendance_events.js +86 -0
- package/dist/tools/behavior.js +4 -5
- package/dist/tools/calendar.js +68 -0
- package/dist/tools/districts.js +2 -1
- package/dist/tools/documents.js +24 -12
- package/dist/tools/fees.js +43 -0
- package/dist/tools/foodservice.js +4 -5
- package/dist/tools/grades.js +2 -1
- package/dist/tools/messages.js +101 -13
- package/dist/tools/recent_grades.js +39 -0
- package/dist/tools/schedule.js +2 -1
- package/dist/tools/students.js +2 -1
- package/dist/tools/teachers.js +46 -0
- package/package.json +1 -1
package/dist/bundle.js
CHANGED
|
@@ -29746,11 +29746,11 @@ var McpServer = class {
|
|
|
29746
29746
|
}
|
|
29747
29747
|
return registeredResourceTemplate;
|
|
29748
29748
|
}
|
|
29749
|
-
_createRegisteredPrompt(name, title, description,
|
|
29749
|
+
_createRegisteredPrompt(name, title, description, argsSchema14, callback) {
|
|
29750
29750
|
const registeredPrompt = {
|
|
29751
29751
|
title,
|
|
29752
29752
|
description,
|
|
29753
|
-
argsSchema:
|
|
29753
|
+
argsSchema: argsSchema14 === void 0 ? void 0 : objectFromShape(argsSchema14),
|
|
29754
29754
|
callback,
|
|
29755
29755
|
enabled: true,
|
|
29756
29756
|
disable: () => registeredPrompt.update({ enabled: false }),
|
|
@@ -29776,8 +29776,8 @@ var McpServer = class {
|
|
|
29776
29776
|
}
|
|
29777
29777
|
};
|
|
29778
29778
|
this._registeredPrompts[name] = registeredPrompt;
|
|
29779
|
-
if (
|
|
29780
|
-
const hasCompletable = Object.values(
|
|
29779
|
+
if (argsSchema14) {
|
|
29780
|
+
const hasCompletable = Object.values(argsSchema14).some((field) => {
|
|
29781
29781
|
const inner = field instanceof ZodOptional2 ? field._def?.innerType : field;
|
|
29782
29782
|
return isCompletable(inner);
|
|
29783
29783
|
});
|
|
@@ -29884,12 +29884,12 @@ var McpServer = class {
|
|
|
29884
29884
|
if (typeof rest[0] === "string") {
|
|
29885
29885
|
description = rest.shift();
|
|
29886
29886
|
}
|
|
29887
|
-
let
|
|
29887
|
+
let argsSchema14;
|
|
29888
29888
|
if (rest.length > 1) {
|
|
29889
|
-
|
|
29889
|
+
argsSchema14 = rest.shift();
|
|
29890
29890
|
}
|
|
29891
29891
|
const cb = rest[0];
|
|
29892
|
-
const registeredPrompt = this._createRegisteredPrompt(name, void 0, description,
|
|
29892
|
+
const registeredPrompt = this._createRegisteredPrompt(name, void 0, description, argsSchema14, cb);
|
|
29893
29893
|
this.setPromptRequestHandlers();
|
|
29894
29894
|
this.sendPromptListChanged();
|
|
29895
29895
|
return registeredPrompt;
|
|
@@ -29901,8 +29901,8 @@ var McpServer = class {
|
|
|
29901
29901
|
if (this._registeredPrompts[name]) {
|
|
29902
29902
|
throw new Error(`Prompt ${name} is already registered`);
|
|
29903
29903
|
}
|
|
29904
|
-
const { title, description, argsSchema:
|
|
29905
|
-
const registeredPrompt = this._createRegisteredPrompt(name, title, description,
|
|
29904
|
+
const { title, description, argsSchema: argsSchema14 } = config2;
|
|
29905
|
+
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema14, cb);
|
|
29906
29906
|
this.setPromptRequestHandlers();
|
|
29907
29907
|
this.sendPromptListChanged();
|
|
29908
29908
|
return registeredPrompt;
|
|
@@ -30320,7 +30320,8 @@ var ICClient = class {
|
|
|
30320
30320
|
if (!account2) throw new UnknownDistrictError(district, [...this.accounts.keys()]);
|
|
30321
30321
|
await this.ensureSession(account2);
|
|
30322
30322
|
const session = this.sessions.get(account2.name);
|
|
30323
|
-
const
|
|
30323
|
+
const url2 = /^https?:\/\//i.test(path) ? path : `${account2.baseUrl}${path}`;
|
|
30324
|
+
const res = await fetch(url2, {
|
|
30324
30325
|
headers: {
|
|
30325
30326
|
Cookie: session.cookie,
|
|
30326
30327
|
...session.xsrfToken ? { "X-XSRF-TOKEN": session.xsrfToken } : {}
|
|
@@ -30337,11 +30338,12 @@ var ICClient = class {
|
|
|
30337
30338
|
}
|
|
30338
30339
|
async doRequest(account2, path, opts, isRetry) {
|
|
30339
30340
|
const session = this.sessions.get(account2.name);
|
|
30341
|
+
const accept = opts.responseType === "text" ? "text/html, text/plain, */*" : "application/json";
|
|
30340
30342
|
const res = await fetch(`${account2.baseUrl}${path}`, {
|
|
30341
30343
|
method: opts.method ?? "GET",
|
|
30342
30344
|
headers: {
|
|
30343
30345
|
Cookie: session.cookie,
|
|
30344
|
-
Accept:
|
|
30346
|
+
Accept: accept,
|
|
30345
30347
|
...session.xsrfToken ? { "X-XSRF-TOKEN": session.xsrfToken } : {},
|
|
30346
30348
|
...opts.headers ?? {}
|
|
30347
30349
|
},
|
|
@@ -30366,6 +30368,9 @@ var ICClient = class {
|
|
|
30366
30368
|
if (res.status >= 500) throw new PortalUnreachableError(account2.name, res.status);
|
|
30367
30369
|
if (!res.ok) throw new Error(`IC ${res.status} ${res.statusText} for ${path}`);
|
|
30368
30370
|
const text = await res.text();
|
|
30371
|
+
if (opts.responseType === "text") {
|
|
30372
|
+
return text;
|
|
30373
|
+
}
|
|
30369
30374
|
return text ? JSON.parse(text) : null;
|
|
30370
30375
|
}
|
|
30371
30376
|
};
|
|
@@ -30453,6 +30458,28 @@ var FileExistsError = class extends Error {
|
|
|
30453
30458
|
path;
|
|
30454
30459
|
};
|
|
30455
30460
|
|
|
30461
|
+
// src/tools/_shared.ts
|
|
30462
|
+
function textContent(data) {
|
|
30463
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
30464
|
+
}
|
|
30465
|
+
function is404(e) {
|
|
30466
|
+
return e instanceof Error && e.message.startsWith("IC 404 ");
|
|
30467
|
+
}
|
|
30468
|
+
function featureDisabled(feature, district, data = []) {
|
|
30469
|
+
return textContent({ warning: "FeatureDisabled", feature, district, data });
|
|
30470
|
+
}
|
|
30471
|
+
async function findStudent(client2, district, studentId) {
|
|
30472
|
+
const students = await client2.request(district, "/campus/api/portal/students");
|
|
30473
|
+
return students.find((s) => String(s.personID) === studentId) ?? null;
|
|
30474
|
+
}
|
|
30475
|
+
function studentNotFound(studentId) {
|
|
30476
|
+
return textContent({ error: "StudentNotFound", studentId });
|
|
30477
|
+
}
|
|
30478
|
+
function toArray(value) {
|
|
30479
|
+
if (value === null || value === void 0) return [];
|
|
30480
|
+
return Array.isArray(value) ? value : [value];
|
|
30481
|
+
}
|
|
30482
|
+
|
|
30456
30483
|
// src/tools/districts.ts
|
|
30457
30484
|
function registerDistrictTools(server2, client2) {
|
|
30458
30485
|
server2.registerTool("ic_list_districts", {
|
|
@@ -30461,7 +30488,7 @@ function registerDistrictTools(server2, client2) {
|
|
|
30461
30488
|
}, async () => {
|
|
30462
30489
|
await client2.ensureDiscovery();
|
|
30463
30490
|
const data = client2.listDistricts();
|
|
30464
|
-
return
|
|
30491
|
+
return textContent(data);
|
|
30465
30492
|
});
|
|
30466
30493
|
}
|
|
30467
30494
|
|
|
@@ -30477,7 +30504,7 @@ function registerStudentTools(server2, client2) {
|
|
|
30477
30504
|
}, async (rawArgs) => {
|
|
30478
30505
|
const args = argsSchema.parse(rawArgs);
|
|
30479
30506
|
const data = await client2.request(args.district, "/campus/api/portal/students");
|
|
30480
|
-
return
|
|
30507
|
+
return textContent(data);
|
|
30481
30508
|
});
|
|
30482
30509
|
}
|
|
30483
30510
|
|
|
@@ -30497,7 +30524,7 @@ function registerScheduleTools(server2, client2) {
|
|
|
30497
30524
|
const args = argsSchema2.parse(rawArgs);
|
|
30498
30525
|
const params = new URLSearchParams({ personID: args.studentId });
|
|
30499
30526
|
const data = await client2.request(args.district, `/campus/resources/portal/roster?${params}`);
|
|
30500
|
-
return
|
|
30527
|
+
return textContent(data);
|
|
30501
30528
|
});
|
|
30502
30529
|
}
|
|
30503
30530
|
|
|
@@ -30505,28 +30532,37 @@ function registerScheduleTools(server2, client2) {
|
|
|
30505
30532
|
var argsSchema3 = external_exports3.object({
|
|
30506
30533
|
district: external_exports3.string(),
|
|
30507
30534
|
studentId: external_exports3.string(),
|
|
30508
|
-
courseId: external_exports3.string().optional(),
|
|
30509
|
-
since: external_exports3.string().describe("YYYY-MM-DD").optional(),
|
|
30510
|
-
until: external_exports3.string().describe("YYYY-MM-DD").optional(),
|
|
30511
|
-
missingOnly: external_exports3.boolean().optional()
|
|
30535
|
+
courseId: external_exports3.string().describe("sectionID (optional, from ic_get_schedule). The endpoint supports server-side filtering by sectionID only.").optional(),
|
|
30536
|
+
since: external_exports3.string().describe("YYYY-MM-DD; filters dueDate >= since (client-side)").optional(),
|
|
30537
|
+
until: external_exports3.string().describe("YYYY-MM-DD; filters dueDate <= until (client-side)").optional(),
|
|
30538
|
+
missingOnly: external_exports3.boolean().describe("Only return assignments flagged missing by the teacher").optional()
|
|
30512
30539
|
});
|
|
30513
30540
|
function registerAssignmentTools(server2, client2) {
|
|
30514
30541
|
server2.registerTool("ic_list_assignments", {
|
|
30515
|
-
description: "List a student's assignments.
|
|
30542
|
+
description: "List a student's assignments. The IC endpoint returns the full term history (~hundreds of items); date and missing filters are applied client-side. For a single course, pass courseId (the sectionID from ic_get_schedule).",
|
|
30516
30543
|
annotations: { readOnlyHint: true },
|
|
30517
30544
|
inputSchema: argsSchema3.shape
|
|
30518
30545
|
}, async (rawArgs) => {
|
|
30519
30546
|
const args = argsSchema3.parse(rawArgs);
|
|
30520
30547
|
const params = new URLSearchParams({ personID: args.studentId });
|
|
30521
30548
|
if (args.courseId) params.set("sectionID", args.courseId);
|
|
30522
|
-
if (args.since) params.set("startDate", args.since);
|
|
30523
|
-
if (args.until) params.set("endDate", args.until);
|
|
30524
30549
|
const raw = await client2.request(
|
|
30525
30550
|
args.district,
|
|
30526
30551
|
`/campus/api/portal/assignment/listView?${params}`
|
|
30527
30552
|
);
|
|
30528
|
-
|
|
30529
|
-
|
|
30553
|
+
let data = raw;
|
|
30554
|
+
if (args.since) {
|
|
30555
|
+
const since = args.since;
|
|
30556
|
+
data = data.filter((a) => typeof a.dueDate === "string" && a.dueDate >= since);
|
|
30557
|
+
}
|
|
30558
|
+
if (args.until) {
|
|
30559
|
+
const until = args.until;
|
|
30560
|
+
data = data.filter((a) => typeof a.dueDate === "string" && a.dueDate.substring(0, 10) <= until);
|
|
30561
|
+
}
|
|
30562
|
+
if (args.missingOnly) {
|
|
30563
|
+
data = data.filter((a) => a.missing);
|
|
30564
|
+
}
|
|
30565
|
+
return textContent(data);
|
|
30530
30566
|
});
|
|
30531
30567
|
}
|
|
30532
30568
|
|
|
@@ -30546,7 +30582,7 @@ function registerGradeTools(server2, client2) {
|
|
|
30546
30582
|
const params = new URLSearchParams({ personID: args.studentId });
|
|
30547
30583
|
if (args.termId) params.set("termID", args.termId);
|
|
30548
30584
|
const data = await client2.request(args.district, `/campus/resources/portal/grades?${params}`);
|
|
30549
|
-
return
|
|
30585
|
+
return textContent(data);
|
|
30550
30586
|
});
|
|
30551
30587
|
}
|
|
30552
30588
|
|
|
@@ -30557,24 +30593,64 @@ var argsSchema5 = external_exports3.object({
|
|
|
30557
30593
|
since: external_exports3.string().describe("YYYY-MM-DD").optional(),
|
|
30558
30594
|
until: external_exports3.string().describe("YYYY-MM-DD").optional()
|
|
30559
30595
|
});
|
|
30596
|
+
function inRange(date5, since, until) {
|
|
30597
|
+
if (typeof date5 !== "string") return true;
|
|
30598
|
+
const d = date5.substring(0, 10);
|
|
30599
|
+
if (since && d < since) return false;
|
|
30600
|
+
if (until && d > until) return false;
|
|
30601
|
+
return true;
|
|
30602
|
+
}
|
|
30603
|
+
function trimSectionPlacement(sp) {
|
|
30604
|
+
const out = {};
|
|
30605
|
+
if (sp.periodName !== void 0) out.periodName = sp.periodName;
|
|
30606
|
+
if (sp.startTime !== void 0) out.startTime = sp.startTime;
|
|
30607
|
+
if (sp.endTime !== void 0) out.endTime = sp.endTime;
|
|
30608
|
+
return out;
|
|
30609
|
+
}
|
|
30610
|
+
function trimEntry(e) {
|
|
30611
|
+
if (e.sectionPlacements === void 0) return e;
|
|
30612
|
+
const sps = toArray(e.sectionPlacements);
|
|
30613
|
+
return { ...e, sectionPlacements: sps.map(trimSectionPlacement) };
|
|
30614
|
+
}
|
|
30615
|
+
function processList(list, since, until) {
|
|
30616
|
+
if (list === void 0) return list;
|
|
30617
|
+
return toArray(list).filter((e) => inRange(e.date, since, until)).map(trimEntry);
|
|
30618
|
+
}
|
|
30560
30619
|
function registerAttendanceTools(server2, client2) {
|
|
30561
30620
|
server2.registerTool("ic_list_attendance", {
|
|
30562
|
-
description: "List a student's absences and tardies
|
|
30621
|
+
description: "List a student's absences and tardies (per-course summary grouped by term). Auto-resolves enrollmentID from the student record.",
|
|
30563
30622
|
annotations: { readOnlyHint: true },
|
|
30564
30623
|
inputSchema: argsSchema5.shape
|
|
30565
30624
|
}, async (rawArgs) => {
|
|
30566
30625
|
const args = argsSchema5.parse(rawArgs);
|
|
30567
|
-
const
|
|
30568
|
-
if (
|
|
30569
|
-
|
|
30626
|
+
const student = await findStudent(client2, args.district, args.studentId);
|
|
30627
|
+
if (!student) return studentNotFound(args.studentId);
|
|
30628
|
+
const enrollments = student.enrollments ?? [];
|
|
30629
|
+
const results = [];
|
|
30570
30630
|
try {
|
|
30571
|
-
const
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30631
|
+
for (const enr of enrollments) {
|
|
30632
|
+
const data = await client2.request(
|
|
30633
|
+
args.district,
|
|
30634
|
+
`/campus/resources/portal/attendance/${enr.enrollmentID}?courseSummary=true&personID=${encodeURIComponent(args.studentId)}`
|
|
30635
|
+
);
|
|
30636
|
+
const entries = toArray(data);
|
|
30637
|
+
for (const entry of entries) {
|
|
30638
|
+
const trimmedTerms = toArray(entry.terms).map((t) => ({
|
|
30639
|
+
...t,
|
|
30640
|
+
courses: toArray(t.courses).map((c) => ({
|
|
30641
|
+
...c,
|
|
30642
|
+
absentList: processList(c.absentList, args.since, args.until),
|
|
30643
|
+
tardyList: processList(c.tardyList, args.since, args.until),
|
|
30644
|
+
presentList: processList(c.presentList, args.since, args.until),
|
|
30645
|
+
earlyReleaseList: processList(c.earlyReleaseList, args.since, args.until)
|
|
30646
|
+
}))
|
|
30647
|
+
}));
|
|
30648
|
+
results.push({ ...entry, terms: trimmedTerms });
|
|
30649
|
+
}
|
|
30577
30650
|
}
|
|
30651
|
+
return textContent(results);
|
|
30652
|
+
} catch (e) {
|
|
30653
|
+
if (is404(e)) return featureDisabled("attendance", args.district);
|
|
30578
30654
|
throw e;
|
|
30579
30655
|
}
|
|
30580
30656
|
});
|
|
@@ -30599,12 +30675,9 @@ function registerBehaviorTools(server2, client2) {
|
|
|
30599
30675
|
if (args.until) params.set("endDate", args.until);
|
|
30600
30676
|
try {
|
|
30601
30677
|
const data = await client2.request(args.district, `/campus/resources/portal/behavior?${params}`);
|
|
30602
|
-
return
|
|
30678
|
+
return textContent(data);
|
|
30603
30679
|
} catch (e) {
|
|
30604
|
-
if (e
|
|
30605
|
-
const warn = { warning: "FeatureDisabled", feature: "behavior", district: args.district, data: [] };
|
|
30606
|
-
return { content: [{ type: "text", text: JSON.stringify(warn, null, 2) }] };
|
|
30607
|
-
}
|
|
30680
|
+
if (is404(e)) return featureDisabled("behavior", args.district);
|
|
30608
30681
|
throw e;
|
|
30609
30682
|
}
|
|
30610
30683
|
});
|
|
@@ -30629,12 +30702,9 @@ function registerFoodServiceTools(server2, client2) {
|
|
|
30629
30702
|
if (args.until) params.set("endDate", args.until);
|
|
30630
30703
|
try {
|
|
30631
30704
|
const data = await client2.request(args.district, `/campus/resources/portal/foodService?${params}`);
|
|
30632
|
-
return
|
|
30705
|
+
return textContent(data);
|
|
30633
30706
|
} catch (e) {
|
|
30634
|
-
if (e
|
|
30635
|
-
const warn = { warning: "FeatureDisabled", feature: "foodService", district: args.district, data: { balance: null, transactions: [] } };
|
|
30636
|
-
return { content: [{ type: "text", text: JSON.stringify(warn, null, 2) }] };
|
|
30637
|
-
}
|
|
30707
|
+
if (is404(e)) return featureDisabled("foodService", args.district, { balance: null, transactions: [] });
|
|
30638
30708
|
throw e;
|
|
30639
30709
|
}
|
|
30640
30710
|
});
|
|
@@ -30643,40 +30713,111 @@ function registerFoodServiceTools(server2, client2) {
|
|
|
30643
30713
|
// src/tools/messages.ts
|
|
30644
30714
|
var listArgs = external_exports3.object({
|
|
30645
30715
|
district: external_exports3.string(),
|
|
30646
|
-
limit: external_exports3.number().int().positive().describe("Number of notifications to retrieve (default 20)").optional()
|
|
30647
|
-
});
|
|
30648
|
-
var countArgs = external_exports3.object({
|
|
30649
|
-
district: external_exports3.string()
|
|
30716
|
+
limit: external_exports3.number().int().positive().describe("Number of prism notifications to retrieve (default 20). Does not affect inbox or announcements.").optional()
|
|
30650
30717
|
});
|
|
30651
30718
|
var getArgs = external_exports3.object({
|
|
30652
30719
|
district: external_exports3.string(),
|
|
30653
|
-
|
|
30654
|
-
});
|
|
30720
|
+
messageUrl: external_exports3.string().describe("The `url` field from an inbox item returned by ic_list_messages (e.g. 'portal/messageView.xsl?x=...&messageID=...'). Accepts relative or /campus/-prefixed paths.")
|
|
30721
|
+
});
|
|
30722
|
+
var NOTIFICATION_KEEP = [
|
|
30723
|
+
"notificationID",
|
|
30724
|
+
"creationTimestamp",
|
|
30725
|
+
"read",
|
|
30726
|
+
"notificationText",
|
|
30727
|
+
"notificationTypeText",
|
|
30728
|
+
"displayedDate"
|
|
30729
|
+
];
|
|
30730
|
+
var INBOX_KEEP = [
|
|
30731
|
+
"messageID",
|
|
30732
|
+
"date",
|
|
30733
|
+
"name",
|
|
30734
|
+
"sender",
|
|
30735
|
+
"messageType",
|
|
30736
|
+
"courseName",
|
|
30737
|
+
"studentName",
|
|
30738
|
+
"newMessage",
|
|
30739
|
+
"actionRequired",
|
|
30740
|
+
"dueDate",
|
|
30741
|
+
"url"
|
|
30742
|
+
];
|
|
30743
|
+
function pick2(obj, keys) {
|
|
30744
|
+
const out = {};
|
|
30745
|
+
for (const k of keys) {
|
|
30746
|
+
if (k in obj) out[k] = obj[k];
|
|
30747
|
+
}
|
|
30748
|
+
return out;
|
|
30749
|
+
}
|
|
30750
|
+
function normalizeMessageUrl(input) {
|
|
30751
|
+
if (input.startsWith("/campus/")) return input;
|
|
30752
|
+
if (input.startsWith("/")) return `/campus${input}`;
|
|
30753
|
+
return `/campus/${input}`;
|
|
30754
|
+
}
|
|
30755
|
+
function parseMessageHtml(html, url2) {
|
|
30756
|
+
const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
|
|
30757
|
+
let subject = titleMatch ? titleMatch[1].trim() : "";
|
|
30758
|
+
subject = subject.replace(/^Message\s*--\s*/i, "");
|
|
30759
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ");
|
|
30760
|
+
text = text.replace(/<[^>]+>/g, " ");
|
|
30761
|
+
text = text.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
30762
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
30763
|
+
const dateMatch = text.match(/Date:\s*(\d{1,2}\/\d{1,2}\/\d{2,4})/);
|
|
30764
|
+
const date5 = dateMatch ? dateMatch[1] : null;
|
|
30765
|
+
let body = text;
|
|
30766
|
+
if (dateMatch) {
|
|
30767
|
+
const idx = text.indexOf(dateMatch[0]);
|
|
30768
|
+
body = text.substring(idx + dateMatch[0].length).trim();
|
|
30769
|
+
}
|
|
30770
|
+
return { subject, date: date5, body, url: url2 };
|
|
30771
|
+
}
|
|
30655
30772
|
function registerMessageTools(server2, client2) {
|
|
30656
30773
|
server2.registerTool("ic_list_messages", {
|
|
30657
|
-
description: "List
|
|
30774
|
+
description: "List all parent-visible messages from three IC sources combined: (1) prism notifications (assignment alerts, grade postings, attendance alerts), (2) Messenger 2.0 inbox (teacher messages, district announcements with newMessage/actionRequired flags), and (3) portal userNotice announcements. Each section has its own count and items; if any source errors, that section contains an error field and the others still return normally. The `limit` arg caps the prism notifications only (the high-volume source). Note: listing inbox messages does not mark them as read in normal portal behavior, but some district configurations may update read-tracking; use ic_get_message for the full HTML body.",
|
|
30658
30775
|
annotations: { readOnlyHint: true },
|
|
30659
30776
|
inputSchema: listArgs.shape
|
|
30660
30777
|
}, async (rawArgs) => {
|
|
30661
30778
|
const args = listArgs.parse(rawArgs);
|
|
30662
30779
|
const limit = args.limit ?? 20;
|
|
30663
|
-
const
|
|
30780
|
+
const prismPromise = client2.request(
|
|
30664
30781
|
args.district,
|
|
30665
30782
|
`/campus/prism?x=notifications.Notification-retrieve&limitCount=${limit}`
|
|
30666
|
-
)
|
|
30667
|
-
|
|
30783
|
+
).then((raw) => {
|
|
30784
|
+
const items = toArray(raw?.data?.NotificationList?.Notification);
|
|
30785
|
+
const trimmed = items.map((n) => pick2(n, NOTIFICATION_KEEP));
|
|
30786
|
+
return { count: trimmed.length, items: trimmed };
|
|
30787
|
+
}).catch((e) => {
|
|
30788
|
+
return { count: 0, items: [], error: e instanceof Error ? e.message : String(e) };
|
|
30789
|
+
});
|
|
30790
|
+
const inboxPromise = client2.request(
|
|
30791
|
+
args.district,
|
|
30792
|
+
"/campus/api/portal/process-message"
|
|
30793
|
+
).then((raw) => {
|
|
30794
|
+
const items = toArray(raw).map((m) => pick2(m, INBOX_KEEP));
|
|
30795
|
+
return { count: items.length, items };
|
|
30796
|
+
}).catch((e) => {
|
|
30797
|
+
return { count: 0, items: [], error: e instanceof Error ? e.message : String(e) };
|
|
30798
|
+
});
|
|
30799
|
+
const noticePromise = client2.request(
|
|
30800
|
+
args.district,
|
|
30801
|
+
"/campus/resources/portal/userNotice"
|
|
30802
|
+
).then((raw) => {
|
|
30803
|
+
const items = toArray(raw);
|
|
30804
|
+
return { count: items.length, items };
|
|
30805
|
+
}).catch((e) => {
|
|
30806
|
+
return { count: 0, items: [], error: e instanceof Error ? e.message : String(e) };
|
|
30807
|
+
});
|
|
30808
|
+
const [notifications, inbox, announcements] = await Promise.all([prismPromise, inboxPromise, noticePromise]);
|
|
30809
|
+
return textContent({ notifications, inbox, announcements });
|
|
30668
30810
|
});
|
|
30669
30811
|
server2.registerTool("ic_get_message", {
|
|
30670
|
-
description: "
|
|
30812
|
+
description: "Fetch the HTML body of an inbox message and return it parsed into { subject, date, body, url }. Takes a `messageUrl` which is the `url` field from an item returned by ic_list_messages' inbox section (e.g. 'portal/messageView.xsl?x=messenger.MessengerEngine-getMessageRecipientView&messageID=...'). Relative and /campus/-prefixed URLs are both accepted. Note: fetching the HTML body may mark the message as read on some district configurations; probe against an empty inbox could not confirm the side effect.",
|
|
30671
30813
|
annotations: { readOnlyHint: true },
|
|
30672
|
-
inputSchema:
|
|
30814
|
+
inputSchema: getArgs.shape
|
|
30673
30815
|
}, async (rawArgs) => {
|
|
30674
|
-
const args =
|
|
30675
|
-
const
|
|
30676
|
-
|
|
30677
|
-
|
|
30678
|
-
);
|
|
30679
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
30816
|
+
const args = getArgs.parse(rawArgs);
|
|
30817
|
+
const path = normalizeMessageUrl(args.messageUrl);
|
|
30818
|
+
const html = await client2.request(args.district, path, { responseType: "text" });
|
|
30819
|
+
const parsed = parseMessageHtml(html ?? "", path);
|
|
30820
|
+
return textContent(parsed);
|
|
30680
30821
|
});
|
|
30681
30822
|
}
|
|
30682
30823
|
|
|
@@ -30687,30 +30828,39 @@ var listArgs2 = external_exports3.object({
|
|
|
30687
30828
|
});
|
|
30688
30829
|
var downloadArgs = external_exports3.object({
|
|
30689
30830
|
district: external_exports3.string(),
|
|
30690
|
-
documentId: external_exports3.string().describe("The
|
|
30831
|
+
documentId: external_exports3.string().describe("The url field returned by ic_list_documents"),
|
|
30691
30832
|
destinationPath: external_exports3.string().describe("Absolute path where the PDF should be written"),
|
|
30692
30833
|
overwrite: external_exports3.boolean().optional()
|
|
30693
30834
|
});
|
|
30694
30835
|
function registerDocumentTools(server2, client2) {
|
|
30695
30836
|
server2.registerTool("ic_list_documents", {
|
|
30696
|
-
description: "List a student's available documents (report cards, transcripts,
|
|
30837
|
+
description: "List a student's available documents (report cards, transcripts, schedules). Returns metadata only \u2014 use ic_download_document to fetch the file. Returns FeatureDisabled if the district has the module turned off.",
|
|
30697
30838
|
annotations: { readOnlyHint: true },
|
|
30698
30839
|
inputSchema: listArgs2.shape
|
|
30699
30840
|
}, async (rawArgs) => {
|
|
30700
30841
|
const args = listArgs2.parse(rawArgs);
|
|
30701
30842
|
try {
|
|
30702
|
-
const
|
|
30703
|
-
|
|
30843
|
+
const raw = await client2.request(
|
|
30844
|
+
args.district,
|
|
30845
|
+
`/campus/resources/portal/report/all?personID=${encodeURIComponent(args.studentId)}`
|
|
30846
|
+
);
|
|
30847
|
+
const trimmed = toArray(raw).map((d) => {
|
|
30848
|
+
const out = {};
|
|
30849
|
+
if (d.name !== void 0) out.name = d.name;
|
|
30850
|
+
if (d.type !== void 0) out.type = d.type;
|
|
30851
|
+
if (d.url !== void 0) out.url = d.url;
|
|
30852
|
+
if (d.moduleLabel !== void 0) out.moduleLabel = d.moduleLabel;
|
|
30853
|
+
if (d.endYear !== void 0) out.endYear = d.endYear;
|
|
30854
|
+
return out;
|
|
30855
|
+
});
|
|
30856
|
+
return textContent(trimmed);
|
|
30704
30857
|
} catch (e) {
|
|
30705
|
-
if (e
|
|
30706
|
-
const warn = { warning: "FeatureDisabled", feature: "documents", district: args.district, data: [] };
|
|
30707
|
-
return { content: [{ type: "text", text: JSON.stringify(warn, null, 2) }] };
|
|
30708
|
-
}
|
|
30858
|
+
if (is404(e)) return featureDisabled("documents", args.district);
|
|
30709
30859
|
throw e;
|
|
30710
30860
|
}
|
|
30711
30861
|
});
|
|
30712
30862
|
server2.registerTool("ic_download_document", {
|
|
30713
|
-
description: "Download a student's document (PDF) to disk. documentId is the
|
|
30863
|
+
description: "Download a student's document (PDF) to disk. documentId is the url field returned by ic_list_documents. Returns FeatureDisabled if the district has the module turned off.",
|
|
30714
30864
|
annotations: { destructiveHint: true },
|
|
30715
30865
|
inputSchema: downloadArgs.shape
|
|
30716
30866
|
}, async (rawArgs) => {
|
|
@@ -30719,17 +30869,360 @@ function registerDocumentTools(server2, client2) {
|
|
|
30719
30869
|
const meta3 = await client2.download(args.district, args.documentId, args.destinationPath, {
|
|
30720
30870
|
overwrite: args.overwrite ?? false
|
|
30721
30871
|
});
|
|
30722
|
-
return
|
|
30872
|
+
return textContent(meta3);
|
|
30723
30873
|
} catch (e) {
|
|
30724
30874
|
if (e instanceof Error && e.message.startsWith("IC download 404")) {
|
|
30725
|
-
|
|
30726
|
-
|
|
30875
|
+
return textContent({ warning: "FeatureDisabled", feature: "documents", district: args.district });
|
|
30876
|
+
}
|
|
30877
|
+
throw e;
|
|
30878
|
+
}
|
|
30879
|
+
});
|
|
30880
|
+
}
|
|
30881
|
+
|
|
30882
|
+
// src/tools/calendar.ts
|
|
30883
|
+
var argsSchema8 = external_exports3.object({
|
|
30884
|
+
district: external_exports3.string(),
|
|
30885
|
+
studentId: external_exports3.string().describe("Student personID from ic_list_students"),
|
|
30886
|
+
since: external_exports3.string().describe("YYYY-MM-DD; include only days on or after this date").optional(),
|
|
30887
|
+
until: external_exports3.string().describe("YYYY-MM-DD; include only days on or before this date").optional()
|
|
30888
|
+
});
|
|
30889
|
+
function registerCalendarTools(server2, client2) {
|
|
30890
|
+
server2.registerTool("ic_list_school_days", {
|
|
30891
|
+
description: "List a student's school days (instructional calendar) grouped by term. Returns one entry per enrollment, with term boundaries (Q1-Q4 start/end dates) and the school days inside each term \u2014 including comments like 'Teacher Workday' or 'Spring Break'. Use since/until to narrow the range.",
|
|
30892
|
+
annotations: { readOnlyHint: true },
|
|
30893
|
+
inputSchema: argsSchema8.shape
|
|
30894
|
+
}, async (rawArgs) => {
|
|
30895
|
+
const args = argsSchema8.parse(rawArgs);
|
|
30896
|
+
const student = await findStudent(client2, args.district, args.studentId);
|
|
30897
|
+
if (!student) return studentNotFound(args.studentId);
|
|
30898
|
+
const result = [];
|
|
30899
|
+
for (const enr of student.enrollments ?? []) {
|
|
30900
|
+
const [termsRaw, daysRaw] = await Promise.all([
|
|
30901
|
+
client2.request(args.district, `/campus/resources/term?structureID=${enr.structureID}`),
|
|
30902
|
+
client2.request(args.district, `/campus/resources/calendar/instructionalDay?calendarID=${enr.calendarID}`)
|
|
30903
|
+
]);
|
|
30904
|
+
const terms = toArray(termsRaw);
|
|
30905
|
+
const days = toArray(daysRaw);
|
|
30906
|
+
const filteredDays = days.filter((d) => {
|
|
30907
|
+
if (args.since && d.date < args.since) return false;
|
|
30908
|
+
if (args.until && d.date > args.until) return false;
|
|
30909
|
+
return true;
|
|
30910
|
+
});
|
|
30911
|
+
const enrollmentTerms = terms.filter((t) => t.structureID === enr.structureID).sort((a, b) => a.seq - b.seq);
|
|
30912
|
+
const trimmedTerms = enrollmentTerms.map((t) => ({
|
|
30913
|
+
termID: t.termID,
|
|
30914
|
+
termName: t.termName,
|
|
30915
|
+
startDate: t.startDate,
|
|
30916
|
+
endDate: t.endDate,
|
|
30917
|
+
days: filteredDays.filter((d) => d.date >= t.startDate && d.date <= t.endDate).map((d) => {
|
|
30918
|
+
const out = { date: d.date, requiresAttendance: d.requiresAttendance };
|
|
30919
|
+
if (d.comments) out.comments = d.comments;
|
|
30920
|
+
return out;
|
|
30921
|
+
})
|
|
30922
|
+
})).filter((t) => t.days.length > 0 || !args.since && !args.until);
|
|
30923
|
+
result.push({
|
|
30924
|
+
enrollmentID: enr.enrollmentID,
|
|
30925
|
+
calendarID: enr.calendarID,
|
|
30926
|
+
structureID: enr.structureID,
|
|
30927
|
+
calendarName: enr.calendarName,
|
|
30928
|
+
terms: trimmedTerms
|
|
30929
|
+
});
|
|
30930
|
+
}
|
|
30931
|
+
return textContent(result);
|
|
30932
|
+
});
|
|
30933
|
+
}
|
|
30934
|
+
|
|
30935
|
+
// src/tools/attendance_events.ts
|
|
30936
|
+
var argsSchema9 = external_exports3.object({
|
|
30937
|
+
district: external_exports3.string(),
|
|
30938
|
+
studentId: external_exports3.string().describe("Student personID from ic_list_students"),
|
|
30939
|
+
since: external_exports3.string().describe("YYYY-MM-DD; include only events on or after this date").optional(),
|
|
30940
|
+
until: external_exports3.string().describe("YYYY-MM-DD; include only events on or before this date").optional(),
|
|
30941
|
+
excusedOnly: external_exports3.boolean().describe("Only include events with excuse=E").optional()
|
|
30942
|
+
});
|
|
30943
|
+
var EVENT_KEYS = [
|
|
30944
|
+
"attendanceID",
|
|
30945
|
+
"date",
|
|
30946
|
+
"localDate",
|
|
30947
|
+
"code",
|
|
30948
|
+
"description",
|
|
30949
|
+
"excuse",
|
|
30950
|
+
"excuseType",
|
|
30951
|
+
"comments",
|
|
30952
|
+
"termID",
|
|
30953
|
+
"status",
|
|
30954
|
+
"periodID",
|
|
30955
|
+
"modifiedDate",
|
|
30956
|
+
"wholeDayAbsence"
|
|
30957
|
+
];
|
|
30958
|
+
var ENROLLMENT_KEYS = [
|
|
30959
|
+
"calendarID",
|
|
30960
|
+
"calendarName",
|
|
30961
|
+
"enrollmentID",
|
|
30962
|
+
"structureID",
|
|
30963
|
+
"schoolName",
|
|
30964
|
+
"crossSiteEnrollment",
|
|
30965
|
+
"endDate"
|
|
30966
|
+
];
|
|
30967
|
+
function trimSectionPlacement2(sp) {
|
|
30968
|
+
const out = {};
|
|
30969
|
+
if (sp.periodName !== void 0) out.periodName = sp.periodName;
|
|
30970
|
+
if (sp.startTime !== void 0) out.startTime = sp.startTime;
|
|
30971
|
+
if (sp.endTime !== void 0) out.endTime = sp.endTime;
|
|
30972
|
+
return out;
|
|
30973
|
+
}
|
|
30974
|
+
function trimEvent(e) {
|
|
30975
|
+
const out = {};
|
|
30976
|
+
for (const key of EVENT_KEYS) {
|
|
30977
|
+
const v = e[key];
|
|
30978
|
+
if (v !== void 0) out[key] = v;
|
|
30979
|
+
}
|
|
30980
|
+
if (e.sectionPlacements !== void 0) {
|
|
30981
|
+
out.sectionPlacements = toArray(e.sectionPlacements).map(trimSectionPlacement2);
|
|
30982
|
+
}
|
|
30983
|
+
return out;
|
|
30984
|
+
}
|
|
30985
|
+
function registerAttendanceEventsTools(server2, client2) {
|
|
30986
|
+
server2.registerTool("ic_list_attendance_events", {
|
|
30987
|
+
description: "List individual attendance events (absences, tardies, early releases) for a student. Each event has a code, description, excuse reason, and optional human-readable comments. Auto-resolves enrollmentID from the student record. Use since/until to filter by date and excusedOnly to show only excused events.",
|
|
30988
|
+
annotations: { readOnlyHint: true },
|
|
30989
|
+
inputSchema: argsSchema9.shape
|
|
30990
|
+
}, async (rawArgs) => {
|
|
30991
|
+
const args = argsSchema9.parse(rawArgs);
|
|
30992
|
+
const student = await findStudent(client2, args.district, args.studentId);
|
|
30993
|
+
if (!student) return studentNotFound(args.studentId);
|
|
30994
|
+
const enrollments = student.enrollments ?? [];
|
|
30995
|
+
const results = [];
|
|
30996
|
+
try {
|
|
30997
|
+
for (const enr of enrollments) {
|
|
30998
|
+
const raw = await client2.request(
|
|
30999
|
+
args.district,
|
|
31000
|
+
`/campus/resources/portal/attendance/events?enrollmentID=${enr.enrollmentID}&personID=${encodeURIComponent(args.studentId)}`
|
|
31001
|
+
);
|
|
31002
|
+
const entries = toArray(raw);
|
|
31003
|
+
for (const entry of entries) {
|
|
31004
|
+
const trimmed = { events: [] };
|
|
31005
|
+
for (const key of ENROLLMENT_KEYS) {
|
|
31006
|
+
const v = entry[key];
|
|
31007
|
+
if (v !== void 0) trimmed[key] = v;
|
|
31008
|
+
}
|
|
31009
|
+
const events = toArray(entry.events).filter((e) => {
|
|
31010
|
+
const d = typeof e.localDate === "string" ? e.localDate.substring(0, 10) : void 0;
|
|
31011
|
+
if (args.since && (d === void 0 || d < args.since)) return false;
|
|
31012
|
+
if (args.until && (d === void 0 || d > args.until)) return false;
|
|
31013
|
+
if (args.excusedOnly && e.excuse !== "E") return false;
|
|
31014
|
+
return true;
|
|
31015
|
+
}).map(trimEvent);
|
|
31016
|
+
trimmed.events = events;
|
|
31017
|
+
results.push(trimmed);
|
|
31018
|
+
}
|
|
30727
31019
|
}
|
|
31020
|
+
return textContent(results);
|
|
31021
|
+
} catch (e) {
|
|
31022
|
+
if (is404(e)) return featureDisabled("attendance_events", args.district);
|
|
30728
31023
|
throw e;
|
|
30729
31024
|
}
|
|
30730
31025
|
});
|
|
30731
31026
|
}
|
|
30732
31027
|
|
|
31028
|
+
// src/tools/recent_grades.ts
|
|
31029
|
+
var argsSchema10 = external_exports3.object({
|
|
31030
|
+
district: external_exports3.string(),
|
|
31031
|
+
studentId: external_exports3.string(),
|
|
31032
|
+
since: external_exports3.string().describe("YYYY-MM-DD; defaults to 14 days ago. Passed to the endpoint as an ISO timestamp (modifiedDate filter).").optional()
|
|
31033
|
+
});
|
|
31034
|
+
var KEYS = [
|
|
31035
|
+
"assignmentName",
|
|
31036
|
+
"courseName",
|
|
31037
|
+
"sectionID",
|
|
31038
|
+
"dueDate",
|
|
31039
|
+
"scoreModifiedDate",
|
|
31040
|
+
"score",
|
|
31041
|
+
"scorePoints",
|
|
31042
|
+
"scorePercentage",
|
|
31043
|
+
"totalPoints",
|
|
31044
|
+
"missing",
|
|
31045
|
+
"late",
|
|
31046
|
+
"turnedIn",
|
|
31047
|
+
"feedback",
|
|
31048
|
+
"comments"
|
|
31049
|
+
];
|
|
31050
|
+
function defaultSinceDate(now) {
|
|
31051
|
+
const d = new Date(now);
|
|
31052
|
+
d.setUTCDate(d.getUTCDate() - 14);
|
|
31053
|
+
return d.toISOString().substring(0, 10);
|
|
31054
|
+
}
|
|
31055
|
+
function registerRecentGradesTools(server2, client2) {
|
|
31056
|
+
server2.registerTool("ic_list_recent_grades", {
|
|
31057
|
+
description: "List recently-graded assignments for a student. Server-side filtered by scoreModifiedDate. Pass since=YYYY-MM-DD to set the cutoff; defaults to 14 days ago.",
|
|
31058
|
+
annotations: { readOnlyHint: true },
|
|
31059
|
+
inputSchema: argsSchema10.shape
|
|
31060
|
+
}, async (rawArgs) => {
|
|
31061
|
+
const args = argsSchema10.parse(rawArgs);
|
|
31062
|
+
const sinceDate = args.since ?? defaultSinceDate(/* @__PURE__ */ new Date());
|
|
31063
|
+
const modifiedDate = `${sinceDate}T00:00:00`;
|
|
31064
|
+
const raw = await client2.request(
|
|
31065
|
+
args.district,
|
|
31066
|
+
`/campus/api/portal/assignment/recentlyScored?modifiedDate=${encodeURIComponent(modifiedDate)}&personID=${encodeURIComponent(args.studentId)}`
|
|
31067
|
+
);
|
|
31068
|
+
const trimmed = (raw ?? []).map((g) => {
|
|
31069
|
+
const out = {};
|
|
31070
|
+
for (const key of KEYS) {
|
|
31071
|
+
const v = g[key];
|
|
31072
|
+
if (v !== void 0) out[key] = v;
|
|
31073
|
+
}
|
|
31074
|
+
return out;
|
|
31075
|
+
});
|
|
31076
|
+
return textContent(trimmed);
|
|
31077
|
+
});
|
|
31078
|
+
}
|
|
31079
|
+
|
|
31080
|
+
// src/tools/teachers.ts
|
|
31081
|
+
var argsSchema11 = external_exports3.object({
|
|
31082
|
+
district: external_exports3.string(),
|
|
31083
|
+
studentId: external_exports3.string()
|
|
31084
|
+
});
|
|
31085
|
+
var DROP_KEYS = /* @__PURE__ */ new Set([
|
|
31086
|
+
"_id",
|
|
31087
|
+
"_model",
|
|
31088
|
+
"_hashCode",
|
|
31089
|
+
"mTime",
|
|
31090
|
+
"action",
|
|
31091
|
+
"personID",
|
|
31092
|
+
"isKentucky",
|
|
31093
|
+
"pairID",
|
|
31094
|
+
"pairedEvent"
|
|
31095
|
+
]);
|
|
31096
|
+
function trimRecord(raw) {
|
|
31097
|
+
const out = {};
|
|
31098
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
31099
|
+
if (DROP_KEYS.has(k)) continue;
|
|
31100
|
+
if (v === void 0) continue;
|
|
31101
|
+
out[k] = v;
|
|
31102
|
+
}
|
|
31103
|
+
return out;
|
|
31104
|
+
}
|
|
31105
|
+
function registerTeacherTools(server2, client2) {
|
|
31106
|
+
server2.registerTool("ic_list_teachers", {
|
|
31107
|
+
description: "List a student's teachers (per enrolled section) and assigned counselor(s). Combines two endpoints (section/contacts and studentCounselor/byUser). Response field shapes may vary slightly by district \u2014 core fields (firstName, lastName, email) are consistent; additional fields are passed through.",
|
|
31108
|
+
annotations: { readOnlyHint: true },
|
|
31109
|
+
inputSchema: argsSchema11.shape
|
|
31110
|
+
}, async (rawArgs) => {
|
|
31111
|
+
const args = argsSchema11.parse(rawArgs);
|
|
31112
|
+
const personID = encodeURIComponent(args.studentId);
|
|
31113
|
+
const teachersPromise = client2.request(
|
|
31114
|
+
args.district,
|
|
31115
|
+
`/campus/resources/portal/section/contacts?personID=${personID}`
|
|
31116
|
+
).catch((e) => {
|
|
31117
|
+
if (is404(e)) return null;
|
|
31118
|
+
throw e;
|
|
31119
|
+
});
|
|
31120
|
+
const counselorsPromise = client2.request(
|
|
31121
|
+
args.district,
|
|
31122
|
+
`/campus/resources/portal/studentCounselor/byUser?personID=${personID}`
|
|
31123
|
+
).catch((e) => {
|
|
31124
|
+
if (is404(e)) return null;
|
|
31125
|
+
throw e;
|
|
31126
|
+
});
|
|
31127
|
+
const [teachersRaw, counselorsRaw] = await Promise.all([teachersPromise, counselorsPromise]);
|
|
31128
|
+
const teachers = toArray(teachersRaw).map((t) => trimRecord(t));
|
|
31129
|
+
const counselors = toArray(counselorsRaw).map((c) => trimRecord(c));
|
|
31130
|
+
return textContent({ counselors, teachers });
|
|
31131
|
+
});
|
|
31132
|
+
}
|
|
31133
|
+
|
|
31134
|
+
// src/tools/assessments.ts
|
|
31135
|
+
var argsSchema12 = external_exports3.object({
|
|
31136
|
+
district: external_exports3.string(),
|
|
31137
|
+
studentId: external_exports3.string().describe("Student personID from ic_list_students")
|
|
31138
|
+
});
|
|
31139
|
+
function registerAssessmentTools(server2, client2) {
|
|
31140
|
+
server2.registerTool("ic_list_assessments", {
|
|
31141
|
+
description: "List a student's standardized test scores (state, national, district tests). Auto-resolves calendarID from each of the student's enrollments and returns one entry per enrollment. The shape of individual test records varies by district and test type \u2014 fields are passed through unchanged.",
|
|
31142
|
+
annotations: { readOnlyHint: true },
|
|
31143
|
+
inputSchema: argsSchema12.shape
|
|
31144
|
+
}, async (rawArgs) => {
|
|
31145
|
+
const args = argsSchema12.parse(rawArgs);
|
|
31146
|
+
const student = await findStudent(client2, args.district, args.studentId);
|
|
31147
|
+
if (!student) return studentNotFound(args.studentId);
|
|
31148
|
+
const personIDEnc = encodeURIComponent(args.studentId);
|
|
31149
|
+
const result = [];
|
|
31150
|
+
let feature404 = false;
|
|
31151
|
+
for (const enr of student.enrollments ?? []) {
|
|
31152
|
+
try {
|
|
31153
|
+
const raw = await client2.request(
|
|
31154
|
+
args.district,
|
|
31155
|
+
`/campus/resources/prism/portal/assessments?personID=${personIDEnc}&calendarID=${enr.calendarID}`
|
|
31156
|
+
);
|
|
31157
|
+
result.push({
|
|
31158
|
+
enrollmentID: enr.enrollmentID,
|
|
31159
|
+
calendarID: enr.calendarID,
|
|
31160
|
+
calendarName: enr.calendarName,
|
|
31161
|
+
stateTests: toArray(raw.stateTests),
|
|
31162
|
+
nationalTests: toArray(raw.nationalTests),
|
|
31163
|
+
districtTests: {
|
|
31164
|
+
tests: toArray(raw.districtTests?.tests),
|
|
31165
|
+
typeTests: toArray(raw.districtTests?.typeTests)
|
|
31166
|
+
}
|
|
31167
|
+
});
|
|
31168
|
+
} catch (e) {
|
|
31169
|
+
if (is404(e)) {
|
|
31170
|
+
feature404 = true;
|
|
31171
|
+
continue;
|
|
31172
|
+
}
|
|
31173
|
+
throw e;
|
|
31174
|
+
}
|
|
31175
|
+
}
|
|
31176
|
+
if (feature404 && result.length === 0) {
|
|
31177
|
+
return featureDisabled("assessments", args.district);
|
|
31178
|
+
}
|
|
31179
|
+
return textContent(result);
|
|
31180
|
+
});
|
|
31181
|
+
}
|
|
31182
|
+
|
|
31183
|
+
// src/tools/fees.ts
|
|
31184
|
+
var argsSchema13 = external_exports3.object({
|
|
31185
|
+
district: external_exports3.string(),
|
|
31186
|
+
studentId: external_exports3.string().describe("Student personID from ic_list_students")
|
|
31187
|
+
});
|
|
31188
|
+
function registerFeeTools(server2, client2) {
|
|
31189
|
+
server2.registerTool("ic_list_fees", {
|
|
31190
|
+
description: "List a student's fee assignments (charges owed) and running balance/surplus. Combines two endpoints: fee assignments and totalSurplus. Returns FeatureDisabled only if both endpoints 404; if only one works, returns that with a note.",
|
|
31191
|
+
annotations: { readOnlyHint: true },
|
|
31192
|
+
inputSchema: argsSchema13.shape
|
|
31193
|
+
}, async (rawArgs) => {
|
|
31194
|
+
const args = argsSchema13.parse(rawArgs);
|
|
31195
|
+
const personIDEnc = encodeURIComponent(args.studentId);
|
|
31196
|
+
const assignmentsPromise = client2.request(
|
|
31197
|
+
args.district,
|
|
31198
|
+
`/campus/api/portal/fees/feeAssignments?personID=${personIDEnc}`
|
|
31199
|
+
).then((v) => ({ ok: true, value: v })).catch((e) => {
|
|
31200
|
+
if (is404(e)) return { ok: false, status: 404 };
|
|
31201
|
+
throw e;
|
|
31202
|
+
});
|
|
31203
|
+
const surplusPromise = client2.request(
|
|
31204
|
+
args.district,
|
|
31205
|
+
`/campus/api/portal/fees/feeTransactionDetail/totalSurplus/-1?personID=${personIDEnc}`
|
|
31206
|
+
).then((v) => ({ ok: true, value: v })).catch((e) => {
|
|
31207
|
+
if (is404(e)) return { ok: false, status: 404 };
|
|
31208
|
+
throw e;
|
|
31209
|
+
});
|
|
31210
|
+
const [assignments, surplus] = await Promise.all([assignmentsPromise, surplusPromise]);
|
|
31211
|
+
if (!assignments.ok && !surplus.ok) {
|
|
31212
|
+
return featureDisabled("fees", args.district, { totalSurplus: null, feeAssignments: [] });
|
|
31213
|
+
}
|
|
31214
|
+
const response = {
|
|
31215
|
+
totalSurplus: surplus.ok ? surplus.value : null,
|
|
31216
|
+
feeAssignments: assignments.ok ? assignments.value : []
|
|
31217
|
+
};
|
|
31218
|
+
const notes = [];
|
|
31219
|
+
if (!assignments.ok) notes.push("feeAssignments endpoint returned 404 (module may be disabled for this district)");
|
|
31220
|
+
if (!surplus.ok) notes.push("totalSurplus endpoint returned 404 (module may be disabled for this district)");
|
|
31221
|
+
if (notes.length > 0) response.notes = notes;
|
|
31222
|
+
return textContent(response);
|
|
31223
|
+
});
|
|
31224
|
+
}
|
|
31225
|
+
|
|
30733
31226
|
// src/index.ts
|
|
30734
31227
|
try {
|
|
30735
31228
|
const { config: config2 } = await import("dotenv");
|
|
@@ -30739,7 +31232,7 @@ try {
|
|
|
30739
31232
|
}
|
|
30740
31233
|
var account = loadAccount();
|
|
30741
31234
|
var client = new ICClient(account);
|
|
30742
|
-
var server = new McpServer({ name: "infinitecampus", version: "0.
|
|
31235
|
+
var server = new McpServer({ name: "infinitecampus", version: "2.0.0" });
|
|
30743
31236
|
registerDistrictTools(server, client);
|
|
30744
31237
|
registerStudentTools(server, client);
|
|
30745
31238
|
registerScheduleTools(server, client);
|
|
@@ -30750,6 +31243,12 @@ registerBehaviorTools(server, client);
|
|
|
30750
31243
|
registerFoodServiceTools(server, client);
|
|
30751
31244
|
registerMessageTools(server, client);
|
|
30752
31245
|
registerDocumentTools(server, client);
|
|
31246
|
+
registerCalendarTools(server, client);
|
|
31247
|
+
registerAttendanceEventsTools(server, client);
|
|
31248
|
+
registerRecentGradesTools(server, client);
|
|
31249
|
+
registerTeacherTools(server, client);
|
|
31250
|
+
registerAssessmentTools(server, client);
|
|
31251
|
+
registerFeeTools(server, client);
|
|
30753
31252
|
console.error(`[infinitecampus-mcp] District: ${account.name} (${account.baseUrl})`);
|
|
30754
31253
|
console.error("[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.");
|
|
30755
31254
|
var transport = new StdioServerTransport();
|