@chrischall/pickuppatrol-mcp 1.2.1 → 1.2.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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "PickUp Patrol school-dismissal tools for Claude Code",
9
- "version": "1.2.1"
9
+ "version": "1.2.2"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -14,7 +14,7 @@
14
14
  "displayName": "PickUp Patrol",
15
15
  "source": "./",
16
16
  "description": "Read and change your children's school dismissal plans in PickUp Patrol — defaults, day-by-day changes and school cutoff times — via MCP",
17
- "version": "1.2.1",
17
+ "version": "1.2.2",
18
18
  "author": {
19
19
  "name": "Chris Chall"
20
20
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pickuppatrol",
3
3
  "displayName": "PickUp Patrol",
4
- "version": "1.2.1",
4
+ "version": "1.2.2",
5
5
  "description": "Read and change your children's school dismissal plans in PickUp Patrol — defaults, day-by-day changes and school cutoff times — via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall"
package/dist/auth.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readEnvVar, McpToolError, CookieJar, EdgeBlockedError, detectEdgeBlock, } from '@chrischall/mcp-utils';
1
+ import { readEnvVar, McpToolError, CookieJar, EdgeBlockedError, detectEdgeBlock, withAmbientCancellation, } from '@chrischall/mcp-utils';
2
2
  export const BASE_URL = 'https://app.pickuppatrol.net';
3
3
  export const BASE_PATH = '/api/json/reply';
4
4
  /** Upper bound on any one request to PickUp Patrol, sign-in included. */
@@ -215,7 +215,10 @@ export class PickUpPatrolAuth {
215
215
  // sign-in would otherwise stall every tool — the healthcheck included —
216
216
  // for as long as undici's own ~300s default. The signal also covers the
217
217
  // body read below.
218
- const signal = AbortSignal.timeout(this.timeoutMs);
218
+ // The running call's cancellation is folded in too, so a cancelled call
219
+ // does not leave the shared sign-in running for the full deadline.
220
+ const deadline = AbortSignal.timeout(this.timeoutMs);
221
+ const signal = withAmbientCancellation(deadline);
219
222
  let res;
220
223
  let text;
221
224
  try {
@@ -240,7 +243,7 @@ export class PickUpPatrolAuth {
240
243
  catch (err) {
241
244
  // Transient: a timeout says nothing about the credentials, so it is
242
245
  // never cached as permanentError and the next call signs in afresh.
243
- if (signal.aborted) {
246
+ if (deadline.aborted) {
244
247
  throw new McpToolError(`PickUp Patrol did not answer the sign-in within ${this.timeoutMs / 1000}s`, { hint: 'The service may be slow or down. Try again shortly.' });
245
248
  }
246
249
  throw err;
package/dist/bundle.js CHANGED
@@ -84,6 +84,7 @@ var require_util = __commonJS({
84
84
  exports.explicitlyAborted = explicitlyAborted2;
85
85
  exports.prefixIssues = prefixIssues2;
86
86
  exports.unwrapMessage = unwrapMessage2;
87
+ exports.sizeCaps = sizeCaps2;
87
88
  exports.attachSchema = attachSchema2;
88
89
  exports.finalizeIssue = finalizeIssue2;
89
90
  exports.getSizableOrigin = getSizableOrigin2;
@@ -618,6 +619,18 @@ var require_util = __commonJS({
618
619
  function unwrapMessage2(message) {
619
620
  return typeof message === "string" ? message : message?.message;
620
621
  }
622
+ function sizeCaps2(defChecks) {
623
+ const caps = [];
624
+ for (const ch of defChecks ?? []) {
625
+ const d = ch._zod.def;
626
+ if (!/_(length|size)$|_equals$/.test(d.check))
627
+ break;
628
+ const max = d.maximum ?? d.length ?? d.size;
629
+ if (max >= 0)
630
+ caps.push([max, ch]);
631
+ }
632
+ return caps;
633
+ }
621
634
  function attachSchema2(issues, start, inst) {
622
635
  var _a3;
623
636
  for (let i = start; i < issues.length; i++) {
@@ -2282,7 +2295,7 @@ var require_versions = __commonJS({
2282
2295
  exports.version = {
2283
2296
  major: 4,
2284
2297
  minor: 6,
2285
- patch: 5
2298
+ patch: 7
2286
2299
  };
2287
2300
  (function() {
2288
2301
  var keys = Object.getOwnPropertyNames(exports);
@@ -3116,6 +3129,31 @@ var require_schemas = __commonJS({
3116
3129
  return payload;
3117
3130
  };
3118
3131
  });
3132
+ function capSize2(inst, size) {
3133
+ const caps = util.sizeCaps(inst._zod.def.checks);
3134
+ if (!caps.length)
3135
+ return;
3136
+ const cap = Math.min(...caps.map((c) => c[0]));
3137
+ const parse3 = inst._zod.parse;
3138
+ inst._zod.parse = (payload, ctx) => {
3139
+ const n = size(payload.value);
3140
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
3141
+ for (const [max, ch] of util.sizeCaps(inst._zod.def.checks)) {
3142
+ if (n > max && ch._zod.def.when(payload)) {
3143
+ const start = payload.issues.length;
3144
+ ch._zod.check(payload);
3145
+ payload.issues[start].continue = false;
3146
+ util.attachSchema(payload.issues, start, inst);
3147
+ return payload;
3148
+ }
3149
+ }
3150
+ }
3151
+ return parse3(payload, ctx);
3152
+ };
3153
+ }
3154
+ var arraySize2 = (input2) => Array.isArray(input2) ? input2.length : -1;
3155
+ var mapSize2 = (input2) => input2 instanceof Map ? input2.size : -1;
3156
+ var setSize2 = (input2) => input2 instanceof Set ? input2.size : -1;
3119
3157
  function handleArrayResult2(result, final, index) {
3120
3158
  if (result.issues.length) {
3121
3159
  final.issues.push(...util.prefixIssues(index, result.issues));
@@ -3159,6 +3197,7 @@ var require_schemas = __commonJS({
3159
3197
  }
3160
3198
  return payload;
3161
3199
  };
3200
+ capSize2(inst, arraySize2);
3162
3201
  });
3163
3202
  function handlePropertyResult2(result, final, key, input2, optin, optout) {
3164
3203
  const isPresent = key in input2;
@@ -4115,6 +4154,7 @@ var require_schemas = __commonJS({
4115
4154
  return Promise.all(proms).then(() => payload);
4116
4155
  return payload;
4117
4156
  };
4157
+ capSize2(inst, mapSize2);
4118
4158
  });
4119
4159
  function handleMapResult2(keyResult, valueResult, final, key, input2, inst, ctx) {
4120
4160
  if (keyResult.issues.length) {
@@ -4181,6 +4221,7 @@ var require_schemas = __commonJS({
4181
4221
  return Promise.all(proms).then(() => payload);
4182
4222
  return payload;
4183
4223
  };
4224
+ capSize2(inst, setSize2);
4184
4225
  });
4185
4226
  function handleSetResult2(result, final) {
4186
4227
  if (result.issues.length) {
@@ -16285,9 +16326,15 @@ ${code2}
16285
16326
  doc.write(`}`);
16286
16327
  return outputVar;
16287
16328
  }
16329
+ function generateSizeGate2(doc, schema, size) {
16330
+ for (const [max, check2] of util.sizeCaps(schema._zod.def.checks)) {
16331
+ doc.write(`if (${size} > ${numericOperand2(max, check2._zod.def.check)}) return INVALID;`);
16332
+ }
16333
+ }
16288
16334
  function generateArrayCheck2(doc, ctx, schema, accessor, buildsValue = true) {
16289
16335
  const def = schema._zod.def;
16290
16336
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
16337
+ generateSizeGate2(doc, schema, `${accessor}.length`);
16291
16338
  const outputVar = buildsValue ? newVar2(ctx) : null;
16292
16339
  const iVar = newVar2(ctx);
16293
16340
  const elemVar = newVar2(ctx);
@@ -16687,6 +16734,7 @@ ${code2}
16687
16734
  function generateMapCheck2(doc, ctx, schema, accessor) {
16688
16735
  const def = schema._zod.def;
16689
16736
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
16737
+ generateSizeGate2(doc, schema, `${accessor}.size`);
16690
16738
  const outputVar = newVar2(ctx);
16691
16739
  const kVar = newVar2(ctx);
16692
16740
  const valVar = newVar2(ctx);
@@ -16703,6 +16751,7 @@ ${code2}
16703
16751
  function generateSetCheck2(doc, ctx, schema, accessor) {
16704
16752
  const def = schema._zod.def;
16705
16753
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
16754
+ generateSizeGate2(doc, schema, `${accessor}.size`);
16706
16755
  const outputVar = newVar2(ctx);
16707
16756
  const valVar = newVar2(ctx);
16708
16757
  doc.write(`const ${outputVar} = new Set();`);
@@ -23308,6 +23357,17 @@ var storage = new AsyncLocalStorage();
23308
23357
  function withCallSignal(signal, fn, request) {
23309
23358
  return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
23310
23359
  }
23360
+ function currentCallSignal() {
23361
+ return storage.getStore()?.signal;
23362
+ }
23363
+ function withAmbientCancellation(own2) {
23364
+ const ambient = currentCallSignal();
23365
+ if (!ambient)
23366
+ return own2;
23367
+ if (!own2)
23368
+ return ambient;
23369
+ return AbortSignal.any([own2, ambient]);
23370
+ }
23311
23371
 
23312
23372
  // node_modules/@chrischall/mcp-utils/dist/caller/index.js
23313
23373
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -23400,7 +23460,7 @@ function declaredDialect(schema, remedy) {
23400
23460
  throw new Error(`JSON Schema declares an unsupported dialect ("$schema": "${schema.$schema.slice(0, 200)}"). The default validator supports JSON Schema 2020-12, 2019-09, draft-07, and draft-06; ${remedy}`);
23401
23461
  }
23402
23462
 
23403
- // node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
23463
+ // node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
23404
23464
  var z = __toESM(require_v4(), 1);
23405
23465
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
23406
23466
  var SUPPORTED_PROTOCOL_VERSIONS = [
@@ -38224,7 +38284,7 @@ var API_KEY_RE = new RegExp([
38224
38284
  // webhook signing secret (Stripe-style)
38225
38285
  ].map((p) => `\\b${p}`).join("|"), "g");
38226
38286
  var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
38227
- var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
38287
+ var QUERY_SECRET_RE = new RegExp(`([?&#;](?:(?:access|refresh|id|auth|session|csrf|xsrf|api)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|secret|token|auth|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
38228
38288
  var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
38229
38289
  var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
38230
38290
  var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
@@ -38235,6 +38295,8 @@ var JSON_SECRET_KEYS = [
38235
38295
  "api[_-]?secret",
38236
38296
  "(?:x[_-])?api[_-]?key",
38237
38297
  "private[_-]?key",
38298
+ "aws[_-]?secret[_-]?access[_-]?key",
38299
+ // fleet-audit#1297
38238
38300
  "code[_-]?verifier",
38239
38301
  // PKCE; a bare `verifier` key is too generic for JSON
38240
38302
  `x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
@@ -38932,6 +38994,7 @@ __export(util_exports, {
38932
38994
  required: () => required,
38933
38995
  safeExtend: () => safeExtend,
38934
38996
  shallowClone: () => shallowClone,
38997
+ sizeCaps: () => sizeCaps,
38935
38998
  slugify: () => slugify,
38936
38999
  stringifyPrimitive: () => stringifyPrimitive,
38937
39000
  toZod: () => toZod,
@@ -39451,6 +39514,18 @@ function prefixIssues(path, issues) {
39451
39514
  function unwrapMessage(message) {
39452
39515
  return typeof message === "string" ? message : message?.message;
39453
39516
  }
39517
+ function sizeCaps(defChecks) {
39518
+ const caps = [];
39519
+ for (const ch of defChecks ?? []) {
39520
+ const d = ch._zod.def;
39521
+ if (!/_(length|size)$|_equals$/.test(d.check))
39522
+ break;
39523
+ const max = d.maximum ?? d.length ?? d.size;
39524
+ if (max >= 0)
39525
+ caps.push([max, ch]);
39526
+ }
39527
+ return caps;
39528
+ }
39454
39529
  function attachSchema(issues, start, inst) {
39455
39530
  var _a3;
39456
39531
  for (let i = start; i < issues.length; i++) {
@@ -40871,7 +40946,7 @@ ${content.join("\n")}
40871
40946
  var version = {
40872
40947
  major: 4,
40873
40948
  minor: 6,
40874
- patch: 5
40949
+ patch: 7
40875
40950
  };
40876
40951
 
40877
40952
  // node_modules/zod/v4/core/schemas.js
@@ -41627,6 +41702,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
41627
41702
  return payload;
41628
41703
  };
41629
41704
  });
41705
+ function capSize(inst, size) {
41706
+ const caps = sizeCaps(inst._zod.def.checks);
41707
+ if (!caps.length)
41708
+ return;
41709
+ const cap = Math.min(...caps.map((c) => c[0]));
41710
+ const parse3 = inst._zod.parse;
41711
+ inst._zod.parse = (payload, ctx) => {
41712
+ const n = size(payload.value);
41713
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
41714
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
41715
+ if (n > max && ch._zod.def.when(payload)) {
41716
+ const start = payload.issues.length;
41717
+ ch._zod.check(payload);
41718
+ payload.issues[start].continue = false;
41719
+ attachSchema(payload.issues, start, inst);
41720
+ return payload;
41721
+ }
41722
+ }
41723
+ }
41724
+ return parse3(payload, ctx);
41725
+ };
41726
+ }
41727
+ var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
41728
+ var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
41729
+ var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
41630
41730
  function handleArrayResult(result, final, index) {
41631
41731
  if (result.issues.length) {
41632
41732
  final.issues.push(...prefixIssues(index, result.issues));
@@ -41670,6 +41770,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
41670
41770
  }
41671
41771
  return payload;
41672
41772
  };
41773
+ capSize(inst, arraySize);
41673
41774
  });
41674
41775
  function handlePropertyResult(result, final, key, input2, optin, optout) {
41675
41776
  const isPresent = key in input2;
@@ -42626,6 +42727,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
42626
42727
  return Promise.all(proms).then(() => payload);
42627
42728
  return payload;
42628
42729
  };
42730
+ capSize(inst, mapSize);
42629
42731
  });
42630
42732
  function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
42631
42733
  if (keyResult.issues.length) {
@@ -42692,6 +42794,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
42692
42794
  return Promise.all(proms).then(() => payload);
42693
42795
  return payload;
42694
42796
  };
42797
+ capSize(inst, setSize);
42695
42798
  });
42696
42799
  function handleSetResult(result, final) {
42697
42800
  if (result.issues.length) {
@@ -52222,9 +52325,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
52222
52325
  doc.write(`}`);
52223
52326
  return outputVar;
52224
52327
  }
52328
+ function generateSizeGate(doc, schema, size) {
52329
+ for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
52330
+ doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
52331
+ }
52332
+ }
52225
52333
  function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
52226
52334
  const def = schema._zod.def;
52227
52335
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
52336
+ generateSizeGate(doc, schema, `${accessor}.length`);
52228
52337
  const outputVar = buildsValue ? newVar(ctx) : null;
52229
52338
  const iVar = newVar(ctx);
52230
52339
  const elemVar = newVar(ctx);
@@ -52624,6 +52733,7 @@ function literalPropertyKey(ctx, key) {
52624
52733
  function generateMapCheck(doc, ctx, schema, accessor) {
52625
52734
  const def = schema._zod.def;
52626
52735
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
52736
+ generateSizeGate(doc, schema, `${accessor}.size`);
52627
52737
  const outputVar = newVar(ctx);
52628
52738
  const kVar = newVar(ctx);
52629
52739
  const valVar = newVar(ctx);
@@ -52640,6 +52750,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
52640
52750
  function generateSetCheck(doc, ctx, schema, accessor) {
52641
52751
  const def = schema._zod.def;
52642
52752
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
52753
+ generateSizeGate(doc, schema, `${accessor}.size`);
52643
52754
  const outputVar = newVar(ctx);
52644
52755
  const valVar = newVar(ctx);
52645
52756
  doc.write(`const ${outputVar} = new Set();`);
@@ -58830,10 +58941,34 @@ import { constants as fsConstants2, realpathSync } from "node:fs";
58830
58941
  var O_NOFOLLOW2 = fsConstants2.O_NOFOLLOW ?? 0;
58831
58942
  var O_NONBLOCK = fsConstants2.O_NONBLOCK ?? 0;
58832
58943
 
58944
+ // node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
58945
+ function asciiLower(s) {
58946
+ return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
58947
+ }
58948
+
58833
58949
  // node_modules/@chrischall/mcp-utils/dist/scrape/index.js
58834
58950
  var NEXT_DATA_MAX_CHARS = 16 * 1024 * 1024;
58835
58951
  function isCloudflareChallenge(html) {
58836
- return /_cf_chl_opt/.test(html) || /<title[^>]*>\s*Just a moment/i.test(html);
58952
+ return html.includes("_cf_chl_opt") || hasJustAMomentTitle(html);
58953
+ }
58954
+ var JUST_A_MOMENT = "just a moment";
58955
+ function hasJustAMomentTitle(html) {
58956
+ const lower = asciiLower(html);
58957
+ let i = 0;
58958
+ for (; ; ) {
58959
+ const open2 = lower.indexOf("<title", i);
58960
+ if (open2 < 0)
58961
+ return false;
58962
+ const gt = lower.indexOf(">", open2 + 6);
58963
+ if (gt < 0)
58964
+ return false;
58965
+ let j = gt + 1;
58966
+ while (j < lower.length && /\s/.test(lower[j]))
58967
+ j++;
58968
+ if (lower.startsWith(JUST_A_MOMENT, j))
58969
+ return true;
58970
+ i = gt + 1;
58971
+ }
58837
58972
  }
58838
58973
 
58839
58974
  // node_modules/@chrischall/mcp-utils/dist/http/index.js
@@ -59184,7 +59319,8 @@ var PickUpPatrolAuth = class {
59184
59319
  }
59185
59320
  }
59186
59321
  async login() {
59187
- const signal = AbortSignal.timeout(this.timeoutMs);
59322
+ const deadline = AbortSignal.timeout(this.timeoutMs);
59323
+ const signal = withAmbientCancellation(deadline);
59188
59324
  let res;
59189
59325
  let text;
59190
59326
  try {
@@ -59205,7 +59341,7 @@ var PickUpPatrolAuth = class {
59205
59341
  return "";
59206
59342
  });
59207
59343
  } catch (err) {
59208
- if (signal.aborted) {
59344
+ if (deadline.aborted) {
59209
59345
  throw new McpToolError(
59210
59346
  `PickUp Patrol did not answer the sign-in within ${this.timeoutMs / 1e3}s`,
59211
59347
  { hint: "The service may be slow or down. Try again shortly." }
@@ -59286,7 +59422,9 @@ var PickUpPatrolClient = class {
59286
59422
  method,
59287
59423
  headers: this.headers(session),
59288
59424
  body: method === "GET" || args === void 0 ? void 0 : JSON.stringify(args),
59289
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
59425
+ // Deadline plus the running call's cancellation: a call the client gave
59426
+ // up on (e.g. a multi-date verify loop) stops issuing requests.
59427
+ signal: withAmbientCancellation(AbortSignal.timeout(REQUEST_TIMEOUT_MS))
59290
59428
  })
59291
59429
  );
59292
59430
  return this.parse(res, method, dto);
@@ -59413,7 +59551,7 @@ var PickUpPatrolClient = class {
59413
59551
  var client = new PickUpPatrolClient();
59414
59552
 
59415
59553
  // src/version.ts
59416
- var VERSION = "1.2.1";
59554
+ var VERSION = "1.2.2";
59417
59555
 
59418
59556
  // node_modules/@chrischall/mcp-utils/dist/internal/edge-block.js
59419
59557
  function objectField(value) {
@@ -59830,8 +59968,8 @@ function registerSchoolTools(server, client2) {
59830
59968
  annotations: { readOnlyHint: true, openWorldHint: true },
59831
59969
  inputSchema: external_exports.object({
59832
59970
  school_id: external_exports.number().int().describe("School id, from pup_list_students"),
59833
- start_date: external_exports.string().optional().describe("YYYY-MM-DD; with end_date, also return dates that differ from the default"),
59834
- end_date: external_exports.string().optional().describe("YYYY-MM-DD")
59971
+ start_date: IsoDate.optional().describe("YYYY-MM-DD; with end_date, also return dates that differ from the default"),
59972
+ end_date: IsoDate.optional().describe("YYYY-MM-DD")
59835
59973
  })
59836
59974
  },
59837
59975
  async ({ school_id, start_date, end_date }) => {
@@ -60087,8 +60225,8 @@ function registerPlanTools(server, client2) {
60087
60225
  description: `Day-by-day dismissal plans across a date range for every student on the account: the option in force, its note, and the weekly default it overrides. Pass raw: true for the untouched API records. ${UNTRUSTED_DESCRIPTION_SUFFIX}`,
60088
60226
  annotations: { readOnlyHint: true, openWorldHint: true },
60089
60227
  inputSchema: external_exports.object({
60090
- start_date: external_exports.string().describe("YYYY-MM-DD"),
60091
- end_date: external_exports.string().describe("YYYY-MM-DD"),
60228
+ start_date: IsoDate.describe("YYYY-MM-DD"),
60229
+ end_date: IsoDate.describe("YYYY-MM-DD"),
60092
60230
  raw: rawParam
60093
60231
  })
60094
60232
  },
@@ -60107,7 +60245,7 @@ function registerPlanTools(server, client2) {
60107
60245
  annotations: { readOnlyHint: true, openWorldHint: true },
60108
60246
  inputSchema: external_exports.object({
60109
60247
  student_id: external_exports.number().int().describe("Student id, from pup_list_students"),
60110
- date: external_exports.string().describe("YYYY-MM-DD"),
60248
+ date: IsoDate.describe("YYYY-MM-DD"),
60111
60249
  raw: rawParam
60112
60250
  })
60113
60251
  },
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { dirname, join } from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- import { loadDotenvSafely, McpToolError, buildQueryString, EdgeBlockedError, detectEdgeBlock, } from '@chrischall/mcp-utils';
3
+ import { loadDotenvSafely, McpToolError, buildQueryString, EdgeBlockedError, detectEdgeBlock, withAmbientCancellation, } from '@chrischall/mcp-utils';
4
4
  import { PickUpPatrolAuth, BASE_URL, BASE_PATH, REQUEST_TIMEOUT_MS, SERVICE, describeResponseStatus, } from './auth.js';
5
5
  // Load .env for local dev; silently skip when dotenv is unavailable (the mcpb
6
6
  // bundle externalises it). The try/catch guards a runtime where
@@ -45,7 +45,9 @@ export class PickUpPatrolClient {
45
45
  method,
46
46
  headers: this.headers(session),
47
47
  body: method === 'GET' || args === undefined ? undefined : JSON.stringify(args),
48
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
48
+ // Deadline plus the running call's cancellation: a call the client gave
49
+ // up on (e.g. a multi-date verify loop) stops issuing requests.
50
+ signal: withAmbientCancellation(AbortSignal.timeout(REQUEST_TIMEOUT_MS)),
49
51
  }));
50
52
  return this.parse(res, method, dto);
51
53
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { CONFIRM_FLOW_SENTENCE, confirmTokenParam, confirmWrite, McpToolError, minifiedResult, UNTRUSTED_DESCRIPTION_SUFFIX, untrustedResult, } from '@chrischall/mcp-utils';
2
+ import { CONFIRM_FLOW_SENTENCE, confirmTokenParam, confirmWrite, IsoDate, McpToolError, minifiedResult, UNTRUSTED_DESCRIPTION_SUFFIX, untrustedResult, } from '@chrischall/mcp-utils';
3
3
  import { BASE_PATH } from '../auth.js';
4
4
  import { buildPlanUpdates } from '../plans.js';
5
5
  import { weekdayOf } from '../dates.js';
@@ -142,8 +142,8 @@ export function registerPlanTools(server, client) {
142
142
  description: `Day-by-day dismissal plans across a date range for every student on the account: the option in force, its note, and the weekly default it overrides. Pass raw: true for the untouched API records. ${UNTRUSTED_DESCRIPTION_SUFFIX}`,
143
143
  annotations: { readOnlyHint: true, openWorldHint: true },
144
144
  inputSchema: z.object({
145
- start_date: z.string().describe('YYYY-MM-DD'),
146
- end_date: z.string().describe('YYYY-MM-DD'),
145
+ start_date: IsoDate.describe('YYYY-MM-DD'),
146
+ end_date: IsoDate.describe('YYYY-MM-DD'),
147
147
  raw: rawParam,
148
148
  }),
149
149
  }, async ({ start_date, end_date, raw }) => {
@@ -155,7 +155,7 @@ export function registerPlanTools(server, client) {
155
155
  annotations: { readOnlyHint: true, openWorldHint: true },
156
156
  inputSchema: z.object({
157
157
  student_id: z.number().int().describe('Student id, from pup_list_students'),
158
- date: z.string().describe('YYYY-MM-DD'),
158
+ date: IsoDate.describe('YYYY-MM-DD'),
159
159
  raw: rawParam,
160
160
  }),
161
161
  }, async ({ student_id, date, raw }) => {
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { minifiedResult, UNTRUSTED_DESCRIPTION_SUFFIX, untrustedResult } from '@chrischall/mcp-utils';
2
+ import { IsoDate, minifiedResult, UNTRUSTED_DESCRIPTION_SUFFIX, untrustedResult } from '@chrischall/mcp-utils';
3
3
  import { WEEKDAY_NAMES } from '../dates.js';
4
4
  import { PUP_UNTRUSTED } from './untrusted.js';
5
5
  /**
@@ -109,11 +109,10 @@ export function registerSchoolTools(server, client) {
109
109
  annotations: { readOnlyHint: true, openWorldHint: true },
110
110
  inputSchema: z.object({
111
111
  school_id: z.number().int().describe('School id, from pup_list_students'),
112
- start_date: z
113
- .string()
112
+ start_date: IsoDate
114
113
  .optional()
115
114
  .describe('YYYY-MM-DD; with end_date, also return dates that differ from the default'),
116
- end_date: z.string().optional().describe('YYYY-MM-DD'),
115
+ end_date: IsoDate.optional().describe('YYYY-MM-DD'),
117
116
  }),
118
117
  }, async ({ school_id, start_date, end_date }) => {
119
118
  const invalidDates = await client.getInvalidPlanDates(school_id);
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 = "1.2.1";
10
+ export declare const VERSION = "1.2.2";
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 = '1.2.1'; // x-release-please-version
10
+ export const VERSION = '1.2.2'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/pickuppatrol-mcp",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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)",
@@ -57,7 +57,7 @@
57
57
  "test:watch": "vitest"
58
58
  },
59
59
  "dependencies": {
60
- "@chrischall/mcp-utils": "^3.0.0",
60
+ "@chrischall/mcp-utils": "^3.0.3",
61
61
  "@modelcontextprotocol/server": "^2.3.0",
62
62
  "dotenv": "^18.0.2",
63
63
  "zod": "^4.6.5"
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": "1.2.1",
9
+ "version": "1.2.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/pickuppatrol-mcp",
14
- "version": "1.2.1",
14
+ "version": "1.2.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },