{
  "markdown": "# pickuppatrol-mcp\n\nAn MCP server for [PickUp Patrol](https://www.pickuppatrol.net/) — the school\ndismissal app. Read and change how your children leave school: their weekly\ndefault plan, one-off changes for specific dates, and the school's dismissal\noptions and cutoff times.\n\n> Developed and maintained by AI (Claude Code). Use at your own discretion.\n\n## What it talks to\n\n`app.pickuppatrol.net` runs a [ServiceStack](https://servicestack.net/) JSON API\nbehind an Ionic/Vue SPA. There is no published API, but the service is reachable\nserver-side with an ordinary HTTPS request — no browser extension, no bot wall,\nno captcha. The server signs in with your own email and password and holds the\nresulting session in memory.\n\nEvery request shape is captured in [`docs/PICKUPPATROL-API.md`](docs/PICKUPPATROL-API.md),\nread off the shipped client rather than guessed, and exercised against a real\naccount — reads, a write, and its restore.\n\n## Install\n\n```jsonc\n// .mcp.json\n{\n  \"mcpServers\": {\n    \"pickuppatrol\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@chrischall/pickuppatrol-mcp\"],\n      \"env\": {\n        \"PICKUPPATROL_USERNAME\": \"you@example.com\",\n        \"PICKUPPATROL_PASSWORD\": \"…\"\n      }\n    }\n  }\n}\n```\n\nFor local development, copy `.env.example` to `.env` and fill it in.\n\nThe server starts without credentials — it answers the host's install-time probe\nand only reports the configuration error on the first tool call.\n\n## Tools\n\n**Reads**\n\n| Tool | What it gives you |\n|---|---|\n| `pup_get_session` | The signed-in account and the students linked to it |\n| `pup_list_students` | Every student with their weekly defaults and review flag |\n| `pup_get_student` | One student in full |\n| `pup_get_default_plans` | A student's weekly default plan, day by day |\n| `pup_list_plans` | Day-by-day plans across a date range |\n| `pup_get_plan` | One student, one date — including whether it is locked |\n| `pup_list_transportations` | A school's dismissal options and the rules each imposes |\n| `pup_get_school` | School profile, notify times, cutoff times, settings |\n| `pup_list_non_school_days` | Dates no plan can be set for, and dates already changed |\n| `pup_list_car_numbers` | Car numbers the school issued to this account |\n| `pup_healthcheck` | Credentials sign in and the API answers |\n\n**Writes** — every one requires `confirm: true`. Without it the tool makes no\nchange and returns a dry-run of the exact payload it would send.\n\n| Tool | What it changes |\n|---|---|\n| `pup_set_plan` | Dismissal for one or more specific dates, or clears them back to the default |\n| `pup_set_default_plans` | The weekly default plan, or clears every default |\n| `pup_mark_defaults_reviewed` | The school's \"defaults need review\" prompt |\n\n### Two things the tools do that the API does not\n\n**Rules are enforced before anything is sent.** Each dismissal option carries its\nown requirements — a note, a car number, an early-dismissal time, or a\nrestriction to particular students. `pup_set_plan` checks them against the\nschool's own list and refuses with the school's wording, so a rejected write is\na validation message rather than an opaque 400.\n\n**Writes are verified by re-reading.** A 2xx from PickUp Patrol is not proof: a\nchange made after the school's cutoff is accepted and silently ignored. Every\nwrite re-reads the affected dates and compares the transportation id **and the\nnote** — never `ModifiedDate`, which advances on its own and would make every\nwrite look successful. The note is part of the proof because every dismissal\noption seen so far requires one, which makes a note-only edit ordinary: an\nid-only comparison would report success from a field that never had to move.\nThe result says `verified: true/false`, and names the dates that did not move.\n\n## Without the MCP server\n\n`skills/pickuppatrol-api/` is a shell-out skill covering the same API with\n`curl` and `jq`, for scripts or a machine where the server is not installed.\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm test              # fast\nnpm run test:coverage # coverage-enforced at 100%\n```\n\nTests never touch the network: the transport is injected, and the MCP tools run\nthrough a real in-memory client/server pair.\n\n## Two things about the API worth knowing\n\n**`GetPlanEdit` returns a date's override, not the effective plan.** A date with\nno specific plan reads back `TransportationId: null` even when the student has a\nweekly default for that weekday. `pup_get_plan` passes that through as-is;\n`pup_list_students` is where the weekly defaults live.\n\n**Sign-in is a session cookie, not a JWT.** `Authenticate` returns\n`BearerToken: null` on this deployment and sets `ss-id`/`ss-pid`/`ss-opt`. The\nclient keeps whichever the server returns and sends both, so a future switch to\nJWTs needs no change here.\n\n## Safety notes\n\n- A rejected sign-in is **never retried**. PickUp Patrol counts failed attempts\n  against the account and a lockout clears only through their support desk, so\n  the error is cached and every later call fails instantly with the same message.\n- The weekly-default write is a read-modify-write of the whole student record,\n  because the API has no default-plans endpoint. The server always reads the\n  student immediately before writing, and changes only `DefaultPlans`.\n- Credentials live in `.env` (gitignored) or the MCP host's config, and are\n  never written to a result or a log.\n\n## License\n\nMIT\n",
  "bytes": 5422,
  "sha": "18973f72a959b7d173ed871b31cc4b3007b29425c6d67de61a3ffa043d111c39",
  "repo_slug": "chrischall/pickuppatrol-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_chrischall_pickuppatrol_mcp_84e009ed/readme"
}