@chrischall/pickuppatrol-mcp 0.1.2 → 0.2.1

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.
@@ -1,4 +1,4 @@
1
- import { schemaConfirm, textResult } from '@chrischall/mcp-utils';
1
+ import { minifiedResult, schemaConfirm } from '@chrischall/mcp-utils';
2
2
  export { schemaConfirm };
3
3
  /**
4
4
  * Confirm-gate for a mutating tool (the fleet convention). Without
@@ -12,7 +12,7 @@ export { schemaConfirm };
12
12
  export function previewUnlessConfirmed(confirm, action, method, dto, body) {
13
13
  if (confirm === true)
14
14
  return null;
15
- return textResult({
15
+ return minifiedResult({
16
16
  dryRun: true,
17
17
  action,
18
18
  method,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { textResult, messageOf } from '@chrischall/mcp-utils';
2
+ import { messageOf, minifiedResult } from '@chrischall/mcp-utils';
3
3
  import { dayIdToName } from '../dates.js';
4
4
  import { VERSION } from '../version.js';
5
5
  /** Project a student down to the fields a parent actually asks about. */
@@ -36,7 +36,7 @@ export function registerAccountTools(server, client) {
36
36
  annotations: { readOnlyHint: true },
37
37
  }, async () => {
38
38
  const session = await client.getSession();
39
- return textResult({
39
+ return minifiedResult({
40
40
  userId: session.UserId ?? null,
41
41
  name: session.DisplayName ?? [session.FirstName, session.LastName].filter(Boolean).join(' '),
42
42
  email: session.Email ?? session.PrimaryEmail ?? null,
@@ -55,7 +55,7 @@ export function registerAccountTools(server, client) {
55
55
  client.getDefaultPlansReviewNeeded(),
56
56
  ]);
57
57
  const needsReview = new Map(review.map((r) => [r.StudentId, r.NeedsReview]));
58
- return textResult(students.map((student) => ({
58
+ return minifiedResult(students.map((student) => ({
59
59
  ...summarizeStudent(student),
60
60
  needsDefaultsReview: needsReview.get(student.StudentId) ?? false,
61
61
  })));
@@ -72,7 +72,7 @@ export function registerAccountTools(server, client) {
72
72
  },
73
73
  }, async ({ student_id, raw }) => {
74
74
  const student = await client.getStudent(student_id);
75
- return textResult(raw === true ? student : summarizeStudent(student));
75
+ return minifiedResult(raw === true ? student : summarizeStudent(student));
76
76
  });
77
77
  server.registerTool('pup_healthcheck', {
78
78
  description: 'Verify the configured credentials sign in and the PickUp Patrol API answers. Reports the server version and the students the account can see.',
@@ -80,7 +80,7 @@ export function registerAccountTools(server, client) {
80
80
  }, async () => {
81
81
  try {
82
82
  const session = await client.getSession();
83
- return textResult({
83
+ return minifiedResult({
84
84
  ok: true,
85
85
  version: VERSION,
86
86
  signedInAs: session.Email ?? session.PrimaryEmail ?? session.DisplayName ?? null,
@@ -91,7 +91,7 @@ export function registerAccountTools(server, client) {
91
91
  // A healthcheck reports rather than throws: the whole point is to say
92
92
  // what is wrong, and an exception here reads to the host as the tool
93
93
  // itself being broken.
94
- return textResult({ ok: false, version: VERSION, error: messageOf(err) });
94
+ return minifiedResult({ ok: false, version: VERSION, error: messageOf(err) });
95
95
  }
96
96
  });
97
97
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { McpToolError, textResult } from '@chrischall/mcp-utils';
2
+ import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
3
3
  import { applyDefaultPlans, clearDefaultPlans } from '../plans.js';
4
4
  import { dayIdToName, nameToDayId } from '../dates.js';
5
5
  import { summarizeDefaultPlans } from './account.js';
@@ -42,7 +42,7 @@ export function registerDefaultPlanTools(server, client) {
42
42
  client.getStudent(student_id),
43
43
  client.getDefaultPlansReviewNeeded(),
44
44
  ]);
45
- return textResult({
45
+ return minifiedResult({
46
46
  studentId: student.StudentId,
47
47
  name: [student.FirstName, student.LastName].filter(Boolean).join(' '),
48
48
  schoolId: student.SchoolId,
@@ -91,7 +91,7 @@ export function registerDefaultPlanTools(server, client) {
91
91
  await client.updateStudent(payload);
92
92
  const after = await client.getStudent(student_id);
93
93
  const remaining = after.DefaultPlans ?? [];
94
- return textResult({
94
+ return minifiedResult({
95
95
  action: 'Cleared every weekday default',
96
96
  defaultPlans: summarizeDefaultPlans(remaining),
97
97
  verified: remaining.length === 0,
@@ -138,7 +138,7 @@ export function registerDefaultPlanTools(server, client) {
138
138
  return true;
139
139
  return !proofsMatch({ transportationId: plan.TransportationId ?? null, note: plan.Note ?? null }, { transportationId: transportation.TransportationId, note: sentNote });
140
140
  });
141
- return textResult({
141
+ return minifiedResult({
142
142
  action,
143
143
  defaultPlans: summarizeDefaultPlans(after.DefaultPlans),
144
144
  verified: unchanged.length === 0,
@@ -165,7 +165,7 @@ export function registerDefaultPlanTools(server, client) {
165
165
  await client.setDefaultsReviewed(student_id, value);
166
166
  const review = await client.getDefaultPlansReviewNeeded();
167
167
  const needsReview = review.find((r) => r.StudentId === student_id)?.NeedsReview ?? false;
168
- return textResult({
168
+ return minifiedResult({
169
169
  studentId: student_id,
170
170
  needsDefaultsReview: needsReview,
171
171
  verified: needsReview === !value,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { McpToolError, textResult } from '@chrischall/mcp-utils';
2
+ import { McpToolError, minifiedResult } from '@chrischall/mcp-utils';
3
3
  import { buildPlanUpdates } from '../plans.js';
4
4
  import { weekdayOf } from '../dates.js';
5
5
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
@@ -45,7 +45,7 @@ export function registerPlanTools(server, client) {
45
45
  start_date: z.string().describe('YYYY-MM-DD'),
46
46
  end_date: z.string().describe('YYYY-MM-DD'),
47
47
  },
48
- }, async ({ start_date, end_date }) => textResult(await client.getParentPlans(start_date, end_date)));
48
+ }, async ({ start_date, end_date }) => minifiedResult(await client.getParentPlans(start_date, end_date)));
49
49
  server.registerTool('pup_get_plan', {
50
50
  description: 'The dismissal plan for one student on one date — the option in force, any note, the early-dismissal time, and whether the date is locked because the cutoff has passed.',
51
51
  annotations: { readOnlyHint: true },
@@ -53,7 +53,7 @@ export function registerPlanTools(server, client) {
53
53
  student_id: z.number().int().describe('Student id, from pup_list_students'),
54
54
  date: z.string().describe('YYYY-MM-DD'),
55
55
  },
56
- }, async ({ student_id, date }) => textResult(await client.getPlanEdit(date, student_id)));
56
+ }, async ({ student_id, date }) => minifiedResult(await client.getPlanEdit(date, student_id)));
57
57
  server.registerTool('pup_set_plan', {
58
58
  description: "Change how a student is dismissed on one or more specific dates, or clear those dates back to the student's weekly default. This changes how a child actually leaves school, so it requires confirm: true; without it you get a dry-run of the exact payload. Read pup_list_transportations first — options differ in whether they require a note, a car number or an early-dismissal time.",
59
59
  inputSchema: {
@@ -131,7 +131,7 @@ export function registerPlanTools(server, client) {
131
131
  };
132
132
  }));
133
133
  const failed = verification.filter((v) => !v.verified);
134
- return textResult({
134
+ return minifiedResult({
135
135
  action,
136
136
  applied: verification,
137
137
  verified: failed.length === 0,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { textResult } from '@chrischall/mcp-utils';
2
+ import { minifiedResult } from '@chrischall/mcp-utils';
3
3
  /**
4
4
  * Project a transportation to its identity plus the rules that decide what a
5
5
  * plan using it must carry — an agent choosing an option needs those rules up
@@ -32,7 +32,7 @@ export function registerSchoolTools(server, client) {
32
32
  }, async ({ school_id, include_inactive }) => {
33
33
  const options = await client.getTransportations(school_id);
34
34
  const visible = include_inactive === true ? options : options.filter((o) => o.IsActive !== false);
35
- return textResult(visible.map(summarizeTransportation));
35
+ return minifiedResult(visible.map(summarizeTransportation));
36
36
  });
37
37
  server.registerTool('pup_get_school', {
38
38
  description: 'A school profile together with its per-weekday notify times and plan cutoff times, and the settings that decide whether parents may set plans at all.',
@@ -46,7 +46,7 @@ export function registerSchoolTools(server, client) {
46
46
  client.getSchoolNotifyTimes(school_id),
47
47
  client.getSchoolSettings(school_id),
48
48
  ]);
49
- return textResult({ school, notifyTimes, settings });
49
+ return minifiedResult({ school, notifyTimes, settings });
50
50
  });
51
51
  server.registerTool('pup_list_non_school_days', {
52
52
  description: 'Dates a plan cannot be set for at a school (holidays, closures, weekends), and optionally the dates in a range that already differ from the student defaults.',
@@ -62,10 +62,10 @@ export function registerSchoolTools(server, client) {
62
62
  }, async ({ school_id, start_date, end_date }) => {
63
63
  const invalidDates = await client.getInvalidPlanDates(school_id);
64
64
  if (start_date === undefined || end_date === undefined) {
65
- return textResult({ invalidDates });
65
+ return minifiedResult({ invalidDates });
66
66
  }
67
67
  const changedDates = await client.getBoldedDates(start_date, end_date);
68
- return textResult({ invalidDates, changedDates });
68
+ return minifiedResult({ invalidDates, changedDates });
69
69
  });
70
70
  server.registerTool('pup_list_car_numbers', {
71
71
  description: 'The car numbers a school has issued to this account, for dismissal options where usesCarNumbers is true.',
@@ -73,5 +73,5 @@ export function registerSchoolTools(server, client) {
73
73
  inputSchema: {
74
74
  school_id: z.number().int().describe('School id, from pup_list_students'),
75
75
  },
76
- }, async ({ school_id }) => textResult(await client.getCarNumbers(school_id)));
76
+ }, async ({ school_id }) => minifiedResult(await client.getCarNumbers(school_id)));
77
77
  }
package/dist/version.d.ts CHANGED
@@ -7,4 +7,4 @@
7
7
  * annotation, so there is exactly one line to keep in sync and
8
8
  * `versionSyncTest` has exactly one line to check.
9
9
  */
10
- export declare const VERSION = "0.1.2";
10
+ export declare const VERSION = "0.2.1";
package/dist/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  * annotation, so there is exactly one line to keep in sync and
8
8
  * `versionSyncTest` has exactly one line to check.
9
9
  */
10
- export const VERSION = '0.1.2'; // x-release-please-version
10
+ export const VERSION = '0.2.1'; // x-release-please-version
package/mint.yaml ADDED
@@ -0,0 +1,22 @@
1
+ version: 1
2
+ name: PickUp Patrol
3
+ slug: pickuppatrol
4
+ summary: >-
5
+ Read and change your children's school dismissal plans in PickUp Patrol —
6
+ defaults, day-by-day changes and school cutoff times
7
+ env:
8
+ - name: PICKUPPATROL_USERNAME
9
+ required: false
10
+ help: >-
11
+ The email address you sign in to app.pickuppatrol.net with.
12
+ - name: PICKUPPATROL_PASSWORD
13
+ secret: true
14
+ required: false
15
+ help: >-
16
+ Your PickUp Patrol password. Stored by the host, never logged by this
17
+ server.
18
+ egress:
19
+ allow:
20
+ # Only hosts the SERVER process actually fetches. Hosts that appear
21
+ # solely in a URL this server BUILDS and returns are excluded.
22
+ - app.pickuppatrol.net
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/pickuppatrol-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "license": "MIT",
5
5
  "mcpName": "io.github.chrischall/pickuppatrol-mcp",
6
6
  "description": "PickUp Patrol MCP server for Claude — developed and maintained by AI (Claude Code)",
@@ -44,28 +44,29 @@
44
44
  ".claude-plugin",
45
45
  "skills",
46
46
  ".mcp.json",
47
- "server.json"
47
+ "server.json",
48
+ "mint.yaml"
48
49
  ],
49
50
  "scripts": {
50
51
  "build": "tsc && npm run bundle",
51
52
  "bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --outfile=dist/bundle.js",
52
53
  "dev": "node dist/index.js",
53
54
  "typecheck": "tsc --noEmit",
54
- "test": "vitest run",
55
- "test:coverage": "vitest run --coverage",
55
+ "test": "npm run typecheck && vitest run",
56
+ "test:coverage": "npm run typecheck && vitest run --coverage",
56
57
  "test:watch": "vitest"
57
58
  },
58
59
  "dependencies": {
59
- "@chrischall/mcp-utils": "^0.15.0",
60
- "@modelcontextprotocol/sdk": "^1.29.0",
60
+ "@chrischall/mcp-utils": "^0.26.1",
61
+ "@modelcontextprotocol/sdk": "^1.30.0",
61
62
  "dotenv": "^17.4.0",
62
- "zod": "^4.4.3"
63
+ "zod": "^4.5.4"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@types/node": "^26.1.1",
66
- "@vitest/coverage-v8": "^4.1.10",
67
+ "@vitest/coverage-v8": "^5.0.0",
67
68
  "esbuild": "^0.28.0",
68
69
  "typescript": "^7.0.2",
69
- "vitest": "^4.1.7"
70
+ "vitest": "^5.0.0"
70
71
  }
71
72
  }
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/pickuppatrol-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.1.2",
9
+ "version": "0.2.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/pickuppatrol-mcp",
14
- "version": "0.1.2",
14
+ "version": "0.2.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }