@chrischall/pickuppatrol-mcp 0.1.0 → 0.1.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +72 -66
- package/dist/tools/defaults.js +1 -2
- package/dist/tools/plans.js +1 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/dist/tools/_errors.d.ts +0 -14
- package/dist/tools/_errors.js +0 -26
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "PickUp Patrol school-dismissal tools for Claude Code",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.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": "0.1.
|
|
17
|
+
"version": "0.1.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": "0.1.
|
|
4
|
+
"version": "0.1.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/bundle.js
CHANGED
|
@@ -31000,53 +31000,6 @@ var StdioServerTransport = class {
|
|
|
31000
31000
|
}
|
|
31001
31001
|
};
|
|
31002
31002
|
|
|
31003
|
-
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
31004
|
-
async function createMcpServer(opts) {
|
|
31005
|
-
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31006
|
-
if (opts.banner !== void 0) {
|
|
31007
|
-
console.error(opts.banner);
|
|
31008
|
-
}
|
|
31009
|
-
const deps = opts.deps;
|
|
31010
|
-
for (const register of opts.tools) {
|
|
31011
|
-
await register(server, deps);
|
|
31012
|
-
}
|
|
31013
|
-
return server;
|
|
31014
|
-
}
|
|
31015
|
-
function withGracefulShutdown(server, opts = {}) {
|
|
31016
|
-
const shouldExit = opts.exit ?? true;
|
|
31017
|
-
let shuttingDown = false;
|
|
31018
|
-
const handler = (signal) => {
|
|
31019
|
-
if (shuttingDown)
|
|
31020
|
-
return;
|
|
31021
|
-
shuttingDown = true;
|
|
31022
|
-
void (async () => {
|
|
31023
|
-
try {
|
|
31024
|
-
if (opts.onSignal)
|
|
31025
|
-
await opts.onSignal(signal);
|
|
31026
|
-
await server.close();
|
|
31027
|
-
} catch (err) {
|
|
31028
|
-
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
31029
|
-
} finally {
|
|
31030
|
-
if (shouldExit)
|
|
31031
|
-
process.exit(0);
|
|
31032
|
-
}
|
|
31033
|
-
})();
|
|
31034
|
-
};
|
|
31035
|
-
process.on("SIGINT", () => handler("SIGINT"));
|
|
31036
|
-
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
31037
|
-
}
|
|
31038
|
-
async function runMcp(opts) {
|
|
31039
|
-
const server = await createMcpServer(opts);
|
|
31040
|
-
const shutdown = opts.shutdown ?? true;
|
|
31041
|
-
if (shutdown !== false) {
|
|
31042
|
-
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
31043
|
-
}
|
|
31044
|
-
const spec = opts.transport ?? "stdio";
|
|
31045
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
31046
|
-
await server.connect(transport);
|
|
31047
|
-
return server;
|
|
31048
|
-
}
|
|
31049
|
-
|
|
31050
31003
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
31051
31004
|
var McpToolError = class extends Error {
|
|
31052
31005
|
/** Actionable remediation text, when one applies. */
|
|
@@ -31105,6 +31058,75 @@ function errorResult(message) {
|
|
|
31105
31058
|
};
|
|
31106
31059
|
}
|
|
31107
31060
|
|
|
31061
|
+
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
31062
|
+
function hintResultOrRethrow(err) {
|
|
31063
|
+
if (err instanceof McpToolError && err.hint) {
|
|
31064
|
+
return errorResult(`${err.message}
|
|
31065
|
+
|
|
31066
|
+
Hint: ${err.hint}`);
|
|
31067
|
+
}
|
|
31068
|
+
throw err;
|
|
31069
|
+
}
|
|
31070
|
+
function surfaceToolHints(server) {
|
|
31071
|
+
const register = server.registerTool.bind(server);
|
|
31072
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
31073
|
+
let result;
|
|
31074
|
+
try {
|
|
31075
|
+
result = cb(...args);
|
|
31076
|
+
} catch (err) {
|
|
31077
|
+
return hintResultOrRethrow(err);
|
|
31078
|
+
}
|
|
31079
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
31080
|
+
});
|
|
31081
|
+
}
|
|
31082
|
+
async function createMcpServer(opts) {
|
|
31083
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31084
|
+
if (opts.surfaceHints !== false)
|
|
31085
|
+
surfaceToolHints(server);
|
|
31086
|
+
if (opts.banner !== void 0) {
|
|
31087
|
+
console.error(opts.banner);
|
|
31088
|
+
}
|
|
31089
|
+
const deps = opts.deps;
|
|
31090
|
+
for (const register of opts.tools) {
|
|
31091
|
+
await register(server, deps);
|
|
31092
|
+
}
|
|
31093
|
+
return server;
|
|
31094
|
+
}
|
|
31095
|
+
function withGracefulShutdown(server, opts = {}) {
|
|
31096
|
+
const shouldExit = opts.exit ?? true;
|
|
31097
|
+
let shuttingDown = false;
|
|
31098
|
+
const handler = (signal) => {
|
|
31099
|
+
if (shuttingDown)
|
|
31100
|
+
return;
|
|
31101
|
+
shuttingDown = true;
|
|
31102
|
+
void (async () => {
|
|
31103
|
+
try {
|
|
31104
|
+
if (opts.onSignal)
|
|
31105
|
+
await opts.onSignal(signal);
|
|
31106
|
+
await server.close();
|
|
31107
|
+
} catch (err) {
|
|
31108
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
31109
|
+
} finally {
|
|
31110
|
+
if (shouldExit)
|
|
31111
|
+
process.exit(0);
|
|
31112
|
+
}
|
|
31113
|
+
})();
|
|
31114
|
+
};
|
|
31115
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
31116
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
31117
|
+
}
|
|
31118
|
+
async function runMcp(opts) {
|
|
31119
|
+
const server = await createMcpServer(opts);
|
|
31120
|
+
const shutdown = opts.shutdown ?? true;
|
|
31121
|
+
if (shutdown !== false) {
|
|
31122
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
31123
|
+
}
|
|
31124
|
+
const spec = opts.transport ?? "stdio";
|
|
31125
|
+
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
31126
|
+
await server.connect(transport);
|
|
31127
|
+
return server;
|
|
31128
|
+
}
|
|
31129
|
+
|
|
31108
31130
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
31109
31131
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
31110
31132
|
function readEnvVar(key, opts = {}) {
|
|
@@ -31503,7 +31525,7 @@ var PickUpPatrolClient = class {
|
|
|
31503
31525
|
var client = new PickUpPatrolClient();
|
|
31504
31526
|
|
|
31505
31527
|
// src/version.ts
|
|
31506
|
-
var VERSION = "0.1.
|
|
31528
|
+
var VERSION = "0.1.2";
|
|
31507
31529
|
|
|
31508
31530
|
// src/dates.ts
|
|
31509
31531
|
var WEEKDAY_NAMES = [
|
|
@@ -31861,22 +31883,6 @@ function previewUnlessConfirmed(confirm, action, method, dto, body) {
|
|
|
31861
31883
|
});
|
|
31862
31884
|
}
|
|
31863
31885
|
|
|
31864
|
-
// src/tools/_errors.ts
|
|
31865
|
-
function withHints(handler) {
|
|
31866
|
-
return async (args) => {
|
|
31867
|
-
try {
|
|
31868
|
-
return await handler(args);
|
|
31869
|
-
} catch (err) {
|
|
31870
|
-
if (err instanceof McpToolError && err.hint) {
|
|
31871
|
-
return errorResult(`${err.message}
|
|
31872
|
-
|
|
31873
|
-
Hint: ${err.hint}`);
|
|
31874
|
-
}
|
|
31875
|
-
throw err;
|
|
31876
|
-
}
|
|
31877
|
-
};
|
|
31878
|
-
}
|
|
31879
|
-
|
|
31880
31886
|
// src/tools/plans.ts
|
|
31881
31887
|
function proofsMatch(a, b) {
|
|
31882
31888
|
return a.transportationId === b.transportationId && (a.note ?? "").trim() === (b.note ?? "").trim();
|
|
@@ -31935,7 +31941,7 @@ function registerPlanTools(server, client2) {
|
|
|
31935
31941
|
confirm: schemaConfirm
|
|
31936
31942
|
}
|
|
31937
31943
|
},
|
|
31938
|
-
|
|
31944
|
+
(async ({ student_id, dates, transportation_id, note, early_dismissal_time, car_number, confirm }) => {
|
|
31939
31945
|
const student = await client2.getStudent(student_id);
|
|
31940
31946
|
const transportation = transportation_id === null ? null : await resolveTransportation(client2, student.SchoolId, transportation_id);
|
|
31941
31947
|
const plans = buildPlanUpdates({
|
|
@@ -32048,7 +32054,7 @@ function registerDefaultPlanTools(server, client2) {
|
|
|
32048
32054
|
confirm: schemaConfirm
|
|
32049
32055
|
}
|
|
32050
32056
|
},
|
|
32051
|
-
|
|
32057
|
+
(async ({ student_id, days, transportation_id, note, early_dismissal_time, clear_all, confirm }) => {
|
|
32052
32058
|
const student = await client2.getStudent(student_id);
|
|
32053
32059
|
if (clear_all === true) {
|
|
32054
32060
|
const payload2 = clearDefaultPlans(student);
|
package/dist/tools/defaults.js
CHANGED
|
@@ -5,7 +5,6 @@ import { dayIdToName, nameToDayId } from '../dates.js';
|
|
|
5
5
|
import { summarizeDefaultPlans } from './account.js';
|
|
6
6
|
import { proofsMatch, resolveTransportation } from './plans.js';
|
|
7
7
|
import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
|
|
8
|
-
import { withHints } from './_errors.js';
|
|
9
8
|
/**
|
|
10
9
|
* Accept weekdays as names ("Monday") or ids (1 = Sunday … 7 = Saturday).
|
|
11
10
|
* A name that is not a weekday is rejected rather than coerced — silently
|
|
@@ -78,7 +77,7 @@ export function registerDefaultPlanTools(server, client) {
|
|
|
78
77
|
.describe('Remove every weekday default instead of setting one (days is ignored)'),
|
|
79
78
|
confirm: schemaConfirm,
|
|
80
79
|
},
|
|
81
|
-
},
|
|
80
|
+
}, (async ({ student_id, days, transportation_id, note, early_dismissal_time, clear_all, confirm }) => {
|
|
82
81
|
// Read-modify-write: PickUp Patrol has no default-plans endpoint, so the
|
|
83
82
|
// whole student record round-trips. Reading it here (before the confirm
|
|
84
83
|
// gate) is what makes the dry-run show the real payload; it mutates
|
package/dist/tools/plans.js
CHANGED
|
@@ -3,7 +3,6 @@ import { McpToolError, textResult } 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';
|
|
6
|
-
import { withHints } from './_errors.js';
|
|
7
6
|
/** Compare two proofs, ignoring whitespace the service may normalise off a note. */
|
|
8
7
|
export function proofsMatch(a, b) {
|
|
9
8
|
return a.transportationId === b.transportationId && (a.note ?? '').trim() === (b.note ?? '').trim();
|
|
@@ -79,7 +78,7 @@ export function registerPlanTools(server, client) {
|
|
|
79
78
|
.describe('Car number, for options where usesCarNumbers is true'),
|
|
80
79
|
confirm: schemaConfirm,
|
|
81
80
|
},
|
|
82
|
-
},
|
|
81
|
+
}, (async ({ student_id, dates, transportation_id, note, early_dismissal_time, car_number, confirm }) => {
|
|
83
82
|
// The reads below resolve and validate the payload; they mutate nothing.
|
|
84
83
|
// Running them before the confirm gate is deliberate: it makes the
|
|
85
84
|
// dry-run show the exact bytes that would be sent, already checked
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/pickuppatrol-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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)",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"test:watch": "vitest"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@chrischall/mcp-utils": "^0.
|
|
59
|
+
"@chrischall/mcp-utils": "^0.15.0",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
61
|
"dotenv": "^17.4.0",
|
|
62
62
|
"zod": "^4.4.3"
|
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.
|
|
9
|
+
"version": "0.1.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/pickuppatrol-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
package/dist/tools/_errors.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Surface an `McpToolError`'s `hint` to the caller.
|
|
4
|
-
*
|
|
5
|
-
* The MCP tool boundary turns a thrown error into a result carrying only
|
|
6
|
-
* `message`, so a `hint` — which is where the actionable half lives here ("the
|
|
7
|
-
* available options are …", "the school describes the note as …") — is
|
|
8
|
-
* otherwise dropped on the floor. Wrapping the handler folds it into the text
|
|
9
|
-
* the caller actually sees.
|
|
10
|
-
*
|
|
11
|
-
* Only `McpToolError` is handled: an unexpected error keeps propagating, so a
|
|
12
|
-
* genuine bug still reads as one instead of being flattened into advice.
|
|
13
|
-
*/
|
|
14
|
-
export declare function withHints<A>(handler: (args: A) => Promise<CallToolResult>): (args: A) => Promise<CallToolResult>;
|
package/dist/tools/_errors.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { McpToolError, errorResult } from '@chrischall/mcp-utils';
|
|
2
|
-
/**
|
|
3
|
-
* Surface an `McpToolError`'s `hint` to the caller.
|
|
4
|
-
*
|
|
5
|
-
* The MCP tool boundary turns a thrown error into a result carrying only
|
|
6
|
-
* `message`, so a `hint` — which is where the actionable half lives here ("the
|
|
7
|
-
* available options are …", "the school describes the note as …") — is
|
|
8
|
-
* otherwise dropped on the floor. Wrapping the handler folds it into the text
|
|
9
|
-
* the caller actually sees.
|
|
10
|
-
*
|
|
11
|
-
* Only `McpToolError` is handled: an unexpected error keeps propagating, so a
|
|
12
|
-
* genuine bug still reads as one instead of being flattened into advice.
|
|
13
|
-
*/
|
|
14
|
-
export function withHints(handler) {
|
|
15
|
-
return async (args) => {
|
|
16
|
-
try {
|
|
17
|
-
return await handler(args);
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
if (err instanceof McpToolError && err.hint) {
|
|
21
|
-
return errorResult(`${err.message}\n\nHint: ${err.hint}`);
|
|
22
|
-
}
|
|
23
|
-
throw err;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|