mathter-mcp 0.1.0 → 0.2.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 CHANGED
@@ -69,6 +69,7 @@ so the first start takes a little longer than later ones. You need Node 18 or ne
69
69
  > "What math skills can Mathter do for grade 3?"
70
70
  > "Make a 5-page grade 3 subtraction pack called Warm-Ups, easy difficulty."
71
71
  > "A blank multiplication chart for grade 4."
72
+ > "How many Mathter packs do I have left this month?"
72
73
  > "Ten worksheets of grade 6 fractions, hard, with worked solutions."
73
74
 
74
75
  The assistant replies with the path to the PDF. Open it and print.
@@ -83,6 +84,11 @@ Asks mathter.ca which skills exist right now and which grades each one covers. Y
83
84
  should call this before generating, so it picks a real skill rather than guessing. No key is sent
84
85
  — that list is public.
85
86
 
87
+ ### `check_allowance`
88
+
89
+ Tells you how many packs your account has left, which plan it is on, and the date the allowance
90
+ resets. It uses nothing, so ask any time: "How many Mathter packs do I have left?"
91
+
86
92
  ### `generate_worksheet_pack`
87
93
 
88
94
  Makes the pack. Only `skill` and `grade` are required; everything else has a sensible default.
package/dist/index.d.ts CHANGED
@@ -76,6 +76,7 @@ export declare function buildGenerateBody(args: Args): Record<string, unknown>;
76
76
  export declare function packFilename(skill: string, grade: number, now?: Date): string;
77
77
  export declare function generateWorksheetPack(args: Args, cfg: MathterConfig): Promise<string>;
78
78
  export declare function listSkills(cfg: MathterConfig): Promise<string>;
79
+ export declare function checkAllowance(cfg: MathterConfig): Promise<string>;
79
80
  export declare function handleCallTool(name: string, args: Args, cfg: MathterConfig): Promise<CallToolResult>;
80
81
  export declare function createMcpServer(cfg: MathterConfig): Server;
81
82
  export interface MainIO {
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import { homedir, tmpdir } from "node:os";
32
32
  import { join } from "node:path";
33
33
  import { pathToFileURL } from "node:url";
34
34
  /** Kept in step with package.json by hand; it is only ever reported, never compared. */
35
- const VERSION = "0.1.0";
35
+ const VERSION = "0.2.0";
36
36
  const DEFAULT_BASE_URL = "https://mathter.ca";
37
37
  /**
38
38
  * A pack render drives headless Chrome on the server, so it is slow by nature — minutes, not
@@ -230,16 +230,26 @@ export const TOOLS = [
230
230
  name: "generate_worksheet_pack",
231
231
  title: "Generate a Mathter worksheet pack",
232
232
  description: "Generate a printable math worksheet pack as a PDF and save it to this computer, then " +
233
- "return the file path. The pack is billed to the Mathter account that owns MATHTER_API_KEY, " +
234
- "under that account's normal monthly allowance — the same as making it on mathter.ca. " +
235
- "Call list_skills first to choose a valid skill for the grade. Logo and branding come from " +
236
- "the account itself and cannot be set here.",
233
+ "return the file path. Each pack uses one pack from the monthly allowance of the Mathter " +
234
+ "account that owns MATHTER_API_KEY, the same as making it on mathter.ca; call " +
235
+ "check_allowance to see how many are left. Call list_skills first to choose a valid skill " +
236
+ "for the grade. A Premium account's logo prints automatically: the teacher uploads it once " +
237
+ "at https://mathter.ca/account, so there is nothing to pass here for it.",
237
238
  // `as const` above keeps the literal types (so a typo in an enum value is a compile error
238
239
  // here rather than a silent runtime oddity); the SDK's Tool type wants a mutable JSON Schema
239
240
  // object, hence the one cast.
240
241
  inputSchema: GENERATE_SCHEMA,
241
242
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
242
243
  },
244
+ {
245
+ name: "check_allowance",
246
+ title: "Check the Mathter allowance",
247
+ description: "How many worksheet packs the account that owns MATHTER_API_KEY has left this period, its " +
248
+ "plan, and the date the allowance resets. Free to call; it uses nothing. Check it before " +
249
+ "making several packs, or when the teacher asks how many they have left.",
250
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
251
+ annotations: { readOnlyHint: true, openWorldHint: true },
252
+ },
243
253
  {
244
254
  name: "list_skills",
245
255
  title: "List Mathter skills",
@@ -522,6 +532,37 @@ export async function listSkills(cfg) {
522
532
  });
523
533
  return `${skills.length} skills available (grade 0 is kindergarten):\n${lines.join("\n")}`;
524
534
  }
535
+ export async function checkAllowance(cfg) {
536
+ const url = `${cfg.baseUrl}/api/usage`;
537
+ const res = await request(url, {
538
+ method: "GET",
539
+ headers: { Authorization: `Bearer ${cfg.apiKey}`, Accept: "application/json", "User-Agent": `mathter-mcp/${VERSION}` },
540
+ }, cfg);
541
+ if (!res.ok) {
542
+ const { parsed, text } = await readErrorBody(res);
543
+ // describeHttpError's 503 is about the PDF renderer's queue; here it means the usage count
544
+ // itself could not be read, and nothing about a renderer applies.
545
+ if (res.status === 503) {
546
+ throw new ToolError("Mathter could not check the allowance right now. Try again in a minute." + serverSaid(text));
547
+ }
548
+ throw new ToolError(describeHttpError(res.status, parsed, res.headers.get("retry-after"), { caller: "key", url }) + serverSaid(text));
549
+ }
550
+ const u = (await res.json().catch(() => null));
551
+ if (!u || typeof u.remaining !== "number" || typeof u.quota !== "number") {
552
+ throw new ToolError(`Mathter answered ${url} without allowance figures. Try again shortly.`);
553
+ }
554
+ const reset = u.resetsAt ? new Date(u.resetsAt) : null;
555
+ const resetText = reset && !Number.isNaN(reset.getTime())
556
+ ? reset.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "America/Toronto" })
557
+ : "the start of the next period";
558
+ const plan = `${u.plan ?? "Mathter"} plan${u.onTrial ? " (free trial)" : ""}`;
559
+ const perPack = typeof u.maxWorksheets === "number" ? ` Each pack can hold up to ${u.maxWorksheets} worksheets.` : "";
560
+ if (u.remaining <= 0) {
561
+ return (`No packs left: all ${u.quota} on the ${plan} are used. The allowance resets on ${resetText}; ` +
562
+ "to make more before then, upgrade at https://mathter.ca/pricing.");
563
+ }
564
+ return `${u.remaining} of ${u.quota} packs left on the ${plan}, resetting on ${resetText}.${perPack}`;
565
+ }
525
566
  /**
526
567
  * The single outbound filter for tool results. Every text block of every result — success,
527
568
  * mapped error, unexpected throw, unknown tool name — passes through here on its way to the
@@ -548,11 +589,14 @@ async function runTool(name, args, cfg) {
548
589
  if (name === "list_skills") {
549
590
  return { content: [{ type: "text", text: await listSkills(cfg) }] };
550
591
  }
592
+ if (name === "check_allowance") {
593
+ return { content: [{ type: "text", text: await checkAllowance(cfg) }] };
594
+ }
551
595
  return {
552
596
  isError: true,
553
597
  content: [{
554
598
  type: "text",
555
- text: `mathter-mcp has no tool called "${name}". It offers generate_worksheet_pack and list_skills.`,
599
+ text: `mathter-mcp has no tool called "${name}". It offers generate_worksheet_pack, list_skills and check_allowance.`,
556
600
  }],
557
601
  };
558
602
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "mathter-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
+ "mcpName": "ca.mathter/mathter",
4
5
  "description": "MCP server for Mathter \u2014 ask your AI assistant for a printable math worksheet pack and get a PDF on disk.",
5
6
  "license": "MIT",
6
7
  "author": "Mathter (https://mathter.ca)",